Page 2 of 3

Re: event driven: how to specify the contract?

Posted: Sun Aug 09, 2009 12:46 pm
by josh
The form does not have to know about the internal state, I choose to make the decision that the model should hold data and behavior that describes & supports a conceptual domain model, and that forms should be responsible for user interaction & validation. If the model is responsible for populating forms I'd have actions like

PopulateToAjaxFormTypeA
PopulateToAjaxFormTypeB
PopulateToFormTypeA

etc...

littering my model

It is the forms that have multiple "types" not the models, so the form does not have to know that the user is a "type A".

Rather, the model does not have to know the form is a Type A, if I am using a Type A form its not going to try and pull fields that only exist in type B forms.

Also my forms are composed a lot, I'll have a user form with sub-forms like address, contact information, etc.. and these forms may or may not be present, instead of hard coding that in the model the forms just compose together without any new code required,

basically my forms ( by defualt ) loop thru their fields, either setting the value to the value from the model, or visa versa. Then it passes off control to it's sub-forms to do the same.

Re: event driven: how to specify the contract?

Posted: Sun Aug 09, 2009 1:09 pm
by koen.h
The 'inversion' I had in mind is more like this:

Code: Select all

class SomeModel
{
    private $a = 'x';
    private $b = 'y';
    private $database = 'db object';
 
    public function acceptSomeForm_PunNotIntended_OfTemplateOrResponse(IForm $form)
    {
        $form->add('a', $this->a);
        $form->add('b', $this->b);
    }
}
 
interface IForm
{
    public function add($name, $value);
}
 
abstract class FormTemplate
{
    public function add($name, $value)
    {
        $this->$name($value);
    }
 
    public function __call($method, $arguments)
    {
        // do nothing
    }
 
    public function render()
    {
        // render template
    }
}
 
class FormTypeA extends FormTemplate
{
    public function a($name)
    {
        // ...  
    }
}
There's no littering of methods for each special case here and output can be changed on the fly. Subforms are also easy to add (without conditional logic). Each form can implement the methods for the fields they need, or delegate (IForm doesn't specify there can only be one IForm, eg you could chain them or use some other pattern).

Re: event driven: how to specify the contract?

Posted: Sun Aug 09, 2009 1:27 pm
by josh
Hmm yea, so like if you had a "sub-model" and sub-form, you'd pass the appropriate sub-form to that related model? I guess that does make sense aside from having calls in there that could do nothing in some cases, I guess your method is kinda like single table inheritance is to databases? If the field does not exist in the form being passed, the form's implicit interface just ignores it?

Also I was skimming back over the thread and....
koen.h wrote:
josh wrote:

Code: Select all

 
$form->getElement( 'person' )->populateTo( $user );
 
and have the element call setPerson() with the new object. Doesn't really violate the law of demeter IMO, unless you see anyway around this?
Maybe not Demeter but I don't think it's good design. The place where you would have this code will need to be passed the $persone object, only to pass it to the form element. Ofcourse this may be not true as I can't see this context but these constructs are common.
Actually here $form would be a form of type User_Form, it would be passed the user and would be doing more then just setting the user's person to the person element.

But here is what I guess I'm not following on you on, lets say person->getId() is 4 and you want to lookup person # 10 and set it to the User object, in your example would that be like

Code: Select all

 
class User {
function poupulateTo( $form )
{
$this->setPerson( $form->getElement( 'person' )->getPouplateModel() );
}
}
 
? I dont see how you get the right person into the user, without enumerating all of the person's setters in the user object or putting the method on the element itself, you cant just delegate to the person if the id is actually immutable

Re: event driven: how to specify the contract?

Posted: Sun Aug 09, 2009 2:20 pm
by koen.h
josh wrote:Hmm yea, so like if you had a "sub-model" and sub-form, you'd pass the appropriate sub-form to that related model? I guess that does make sense aside from having calls in there that could do nothing in some cases, I guess your method is kinda like single table inheritance is to databases? If the field does not exist in the form being passed, the form's implicit interface just ignores it?
I guess you could create a hierarchy of forms that way, yes. I'm still forming my opinion/idea with the help of the replies you give me, so I'm not confident in the end it would be able to create what you have now. The sub-model/form could be like this:

Code: Select all

class User {
    private $id;
    private $contactInformation;
    public function __construct($id, ContactInfo $contactInfo = null) {
        $this->contactInformation = $contactInfo;
    }
    public function acceptForm(IForm $form)
    {
        $form->add('id', $this->id);
        $form->add('contactInfo', $this->contactInformation->acceptForm($form));
    }
}
 
// added 2 methods and changed 1
abstract class AForm {
    public function addSubForm(IForm $form) {
        $this->subForms[] = $form;
    }
    public function render() {
        // render $this and each of the subforms->render()
    }
    public function __call($method, $arguments)
    {
        foreach ($this->subForms as $subForm)
        {
            call_user_func_array(array($subForm, $method), $arguments);
        }
    }
}
I'm not happy with this to be honest. The parent Model would have to know it needs to call acceptForm on sub Models. A composite model doesn't seem an alternative. Hmm.
But here is what I guess I'm not following on you on, lets say person->getId() is 4 and you want to lookup person # 10 and set it to the User object, in your example would that be like
I'm a bit lost here. Did you mean $user->getId()?

Re: event driven: how to specify the contract?

Posted: Sun Aug 09, 2009 2:45 pm
by koen.h
koen.h wrote:I'm not happy with this to be honest. The parent Model would have to know it needs to call acceptForm on sub Models. A composite model doesn't seem an alternative. Hmm.
I'm more happy with this:

Code: Select all

class UserContactInfoForm extends FormTemplate
{
    public function contactInfo($contactInfo)
    {
        $contactInfo->acceptFrom($this);
    }
}
 

Re: event driven: how to specify the contract?

Posted: Sun Aug 09, 2009 4:48 pm
by josh
Yeah except I found I needed 2 methods, from and to ( one for loading data into a form at first, one to get the data back after the user edits it ). The issue is specifically this:

Imagine you make a re-usable form element that you can subclass and point to any table, it'll render a drop down allowing the user to update the foreign key for the 1st model's table. When they change the model the whole model needs to be replaced, so whatever is moving data FROM the form to the model, most likely needs access to the 1st models interface... for instance say we had this interface

$person->getLocation() $person->setLocation() and our locations were objects not strings, when I need to move data from the person form to the person object, I'm likely calling up the location data mapper, and asking it find location #55 or some arbitrary #, then I have to update the person object with the new location.

I thought this was kinda relevant problem to bring up here when you decide how to architect the signatures of your event handlers, you dont want to violate law of demeter but keep in mind theres no way to re-assign references

Re: event driven: how to specify the contract?

Posted: Sun Aug 09, 2009 8:37 pm
by Jenk
To drag this back a bit to the example problem:

Code: Select all

$form->getElement( 'person' )->populateTo( $user->getPerson() );
Why not..

Code: Select all

$person = $user->getPerson($form->getElement('person')->identifyPerson());
$form->getElement('person')->populateTo($person);
Though I'd rather pull over push:

Code: Select all

$user->getPerson( $form->getElement('person')->identifyPerson() )->populateFrom( $form->getElement('person') );
Where identifyPerson() is the unique identifer for the person required according to the form.

Re: event driven: how to specify the contract?

Posted: Sun Aug 09, 2009 10:20 pm
by josh
I think user / person is a bad example, more specifically what if you had like

$company->getPresident()->getId() // 3

the president of the company is person #3....

lets say the president resigns, a new president is elected, happens to be person #4, so I pretty much have to have the $company to call setPresident() with the new person object. If references worked differently you could just do...

$person = $company->getPerson();

/// then pass only the person and in your event handler youd do:
$person = new Person( 4 );

however that would just update the $person variable to "point" the new object, it wouldn't go find all my other references to person #3 and update them to point to person #4, if you backed the stack back out of the event handler or "stepped out" and called $company->getPerson() you would get back 3 not 4, its almost like references and law of demeter are opposing forces here.

If your ids were not immutable it would be different, BUT, even then youd have 2 objects that are equal when you really wanted them to be identical ( reference the same object ). In my application if I update person #3 with a new name, and then call for that person's name later on, I expect to get the new name you know.

By the way this particular form element uses the ids for it's values ( passed in $_POST ), so $element->getValue() would return the id, ->setValue() would set the new id

I tried to do it differently for weeks and weeks and this is just the only design that worked for me. As a side benefit I use this "person" element for lots of things other then users and companies, literally all over the app I use it, so the populateFrom and To routines only affect 1 element instead of N models. Not to get too off topic but I am enjoying discussing this, I have seen some suggestions I didn't think of that I would prefer If I get it into a couple extra "tight corners", I am open minded there are alternate designs that might work too ( like the last code block viewtopic.php?f=19&t=103840&p=558656#p558587 )

Re: event driven: how to specify the contract?

Posted: Mon Aug 10, 2009 5:02 am
by Jenk
I see what you mean, i.e. replacing all references. Though in that particular example it wouldn't be the $company calling on setPresident(), it'd only be in one place (i.e. $company->setPresident($person)) but still, what if the company changed, etc.

Something could be done with Actions:

Code: Select all

abstract class Observer {
  public $action;
  public $message;
  public function __construct($message, $action) {
    $this->message = $message;
    $this->action = $action;
  }
 
  public function isForMe($message) {
    return $this->message == $message;
  }
 
  public function perform($args) {
    $this->action->perform($args);
  }
 
  abstract public function notify($message, $args);
}
 
class PersonObserver extends Observer {
  public function __construct() {
    parent::__construct("person", new PersonAction());
  }
 
  public function notify($message, $args) {
    if ($this->isForMe($message)) {
      $this->perform($args);
    }
  }
}
 
class PersonAction {
  public function perform($args) {
    if ($args[0] == "replace") {
      $args[1]->getParent()->setPresident($args[2]);
    }
  }
}
 
 
so that'd be done with:

Code: Select all

$args = array("replace", $company, new Person(2));
$observer->notifyAll("person", $args);
could also extrapolate the $args array to it's own object with getMessage(), getArgs(), etc, or perhaps even have the $action carry the args with it, and use that to notify.. though that would be a bit.. pointless.

Although, in the event of there being unique entites (i.e. Persons in this example) then a registry of said persons would be better used instead of multiple references :)

Re: event driven: how to specify the contract?

Posted: Mon Aug 10, 2009 5:45 am
by koen.h
Can you correct what I can make of the company/president example (below) and add the problematic observer part in it?

Code: Select all

class Company {
    public function setPresident(President $president) {
        $this->president = $president;
    }
    public function populateTo(IForm $form) {
        $form->add('president', $this->president);
    }
}
 
class President {
    public function set(Person $person) {
        $this->person = $person;
    }
    public function populateTo(IForm $form) {
        $form->add('person', $this->person);
    }
}
 
class Person {}
 
class PersonTable {
    public function getById($id) {}
}
 
// controller parts
$person3 = $personTable->getById(3);
$president->set($person3);
$company->setPresident($president);
 
$person4 = $personTable->getById(4);
$president->set($person4);

Re: event driven: how to specify the contract?

Posted: Mon Aug 10, 2009 6:32 am
by Jenk
well the simplest thing to do, would be to loosely couple the person/president/company relationships.

Instead of:

Code: Select all

$form->add("president", $company->president);
it'd be:

Code: Select all

$form->add("president", $company->getPresident());
and likewise for anywhere that relationship is required which immediately drops the reference count, so injecting a new president on the company would reflect across the whole system. You'd need to redraw your forms of course.

Re: event driven: how to specify the contract?

Posted: Mon Aug 10, 2009 11:30 am
by josh
It would affect the populateFrom() not the populateTo(), pulling the scalars from the model to populate the form is no sweat its just updating the reference if the user changes the person on the form that makes it awkward, these are some interesting ideas though.

This conversation is also making me second guess using populateFrom and To as method names. Probably modelToForm and formToModel are more accurate

Re: event driven: how to specify the contract?

Posted: Tue Aug 11, 2009 7:03 am
by Jenk
The "populate" behaviour would then be extracted, and have a "Populator" perhaps.

Code: Select all

class PersonFormPopulator {
  public $form;
  public $person;
  public function __construct ($person, $form) {
    $this->person = $person;
    $this->form = $form;
  }
 
  public function populate() {
    $this->form->name = $person->name;
    $this->form->address = $person->address;
    //etc
  }
}
or, how about lazy references within the form (or person if pushing) I'm assuming basic html here:

Code: Select all

class PersonForm {
  public $person;
  public function populate() {
    // snip..
    echo "<input type=\"text\" name=\"name\" value=\"{$this->getPerson()->name}\" />";
    // snip..
  }
 
  public function getPerson() {
    return $this->person;
  }
}
then just reset the reference on the form and populate(). If the user changes this person's name in the above.. well, back to what this topic is about :P but at least you now have the seam in which to change.. i.e. getPerson() could do more than just return an instance variable.. it could poll some observer that any changes made need saving, and then return the resulting person, or there could be a "hasModified()" method to do the notification that everything else needs to update. Doesn't have to be an observer, either.. could be the controller directly, or whatever. I prefer Observer due to the explicit loose coupling though :)

Re: event driven: how to specify the contract?

Posted: Wed Aug 12, 2009 1:56 pm
by josh
Well lets forget the form example then.

The problem is, you couldn't implement a setPerson() in your event handler, when you called it it would set the event handler's person but it would not update the company, the issue is specifically when the event handler needs to do the reverse of this populate method.. but yeah original question was to know what arguments to expect, here if you expected the $person and not the $company there is no way to go update that company unless

1) you always implement bi-directional mappings, that is you can ask for a person when you really wanted a company, and call ->getCompany() then call ->setPerson() on that, which is even more demeter violation in your handler

2) you pass higher level objects to avoid trapping yourself in a corner later on, to ensure you are at least passing objects that you need to do your work, at the cost of all your other event handlers that really wanted a person now have to accept a company and repeat ->getPerson() calls on that.

3) you have more than one interface for accepting the same kind of events?

It's kind of a catch 22 because you either risk trapping yourself in a corner later on, or violating the law of demeter, or accepting inconsistency

Re: event driven: how to specify the contract?

Posted: Tue Aug 18, 2009 10:53 am
by Jenk
Then back to the observer pattern.. everything that can have one or more of its attributes change, must observe and accept notifications of change.. i.e.

Code: Select all

class Company {
  public $president;
 
  public function notify($notification) {
    if ($notification->getMessage() == "change president") {
      $this->president = $notification->getArg("president");
    }
  }
}
anywhere else...

Code: Select all

$president = new President(/* etc */);
$observable->notifyAll(new Notification("change president", array("president" => $president));