New model events onGet, onSet, onIsset, onUnset, onCall

I want to discuss the addition of a couple of additional events to models. Namely:

onGet

onSet

onIsset

onUnset

onCall

They all would be fired when accessing an undefined property / calling an undefined method (a.k.a. when calling a PHP magic method). I only see a problem with an apropriate naming schema, because there’s no before or after in this case. Existing events use e.g. afterConstruct(). This method fires the event and can be overridden in child classes. We would need something different and on is already taken, because it defines the event.

So for now let me use these names the events:

onMagicGet

onMagicSet

onMacicIsset

onMagicUnset

onMagicCall

Please bare with me - i don’t really like this either, but let’s have a look at a possible implementation using this schema in CModel:




// Define magic methods 

// (called from all child implementations, e.g. from AR, as last ressort)

public function __get($name)

{

    return $this->magicGet($name);

}

public function __set($name,$value)

{

    $this->magicSet($name,$value);

}

public function __call($name,$args)

{

    return $this->magicCall($name,$args);

}

// TBC ...


// Define events

public function onMagicGet($event)

{

    $this->raiseEvent('onMagicGet',$event);

}

public function onMagicSet($event)

{

    $this->raiseEvent('onMagicSet',$event);

}

public function onMagicCall($event)

{

    $this->raiseEvent('onMagicCall',$event);

}

// TBC ...




// To ease overriding in child models:

public function magicGet($name)

{

    if ($this->hasEventHandlers('onMagicGet')

    {

        $event=new CMagicModelEvent;

        $event->name=$name;

        $this->onMagicGet($event);

        return $event->value;

    }

}

public function magicCall($name,$args)

{

    if ($this->hasEventHandlers('onMagicCall')

    {

        $event=new CMagicModelEvent;

        $event->name=$name;

        $event->args=$args;

        $this->onMagicCall($event);

        return $event->value;

    }

}


// Required to propagate getter/setter/call information to event handlers

class CMagicModelEvent extends CEvent

{

    // Name of get/set var or method

    public $name;


    // method arguemnts

 	public $args;


    // Value to set / return from called method    

    public $value;

}



How can this be useful?

We could now implement behaviors that add new attributes to activerecords.

In my case i thought about implementing date/time/decimal conversion for i18n. My idea was to create a behavior that allows to prefix existing attributes with formatted (like formattedBirthday). These attributes could then be used for display purposes and maybe also to convert form input back into a generic format. They would behave much like "real" AR attributes.

But this is a different story … ;)

Potential pitfals:

[b]

[/b]

I’m not sure how much sense it makes, to allow multiple listeners to attach. So maybe the above approach is already overdosed…

Please let me know your thoughts. The proposal is surely not perfect yet, but i think something similar would open up a wide range of possibilities.

Another possible use case could be class table inheritance as discussed in this thread: http://www.yiiframework.com/forum/index.php?/topic/12978-class-table-inheritance/page__gopid__86614#entry86614

Example:

3 classes with one table for each class (simple example):

[list=1]

[*]Person - fields: id, name, gender

[*]Employee - fields: person_id, department

[*]Manager - employee_id, foreign_language

[/list]

The Manager class would inherit from Employee which itself is inherited from Person. A manager object would use all the fields its parent classes provide (e.g. name)

Instead of overriding Active Record’s __get or __set methods to enable CTI one could use a behavior which is reacting to events fired by the above mentioned situations. This would enable clean and maintainable code like this


//Manager object


$this->name

which would fire an onMagicGet event because the manager object doesn’t have any field, column or relation named “name”. The behavior could now use this event to search for this attribute in the corresponding parent object which would cause the same effect -> at the end of this cycle the attribute would be found in the Person object and can therefore be returned.

I see your point and think that’s another nice use case.

On second thought i come back to my first naming scheme and propose these very common names instead:




// Define event:

public function onGet() {}

public function onSet() {}

public function onIsset() {}

public function on Unset() {}

public function onCall() {}


// Methods that you could override in concrete models:

public function get() {}

public function set() {}

public function isset() {}

public function unset() {}

public function call() {}



I will open a ticket now to get some more feedback ;)

EDIT: http://code.google.com/p/yii/issues/detail?id=2233