Attach behavior to a model

Hi!

I am looking for a way to attach a behavior to a model before calling find. The model contains an implementation of the afterFind event.

I tried the following code, but I think this just shows I did not understand it :)





$test = Verify::model();

$test->attachBehavior('BConvertDisplay', array('class' => 'ext.ConvertDisplay', 'convertible_attributes' => array()));


$test->test();

		

$verifyList = $test->findAll($criteria);




The behavior has a method test() and I can run it from the model, but not on the ActiveRecords in the result. There are no errors, but also nothing happens after I called find.

If I add the behavior to internally to the model it works just fine - afterFind is called on every record.

Any hints?

Cheers,

Hein

I’m trying to understand behaviors myself. IIRC you have to make the implementation in your model protected and also call the parent implementation which then will fire the event.

/Tommy

Not sure if i understand but you can extend CModelBehavior and extend it’s events() method to attach any event handlers (also see the source code of CModelBehavior):


class MyBehavior extends CModelBehavior{


    public function events() {

        return array(

            'onAfterFind' => 'test'

        );

    }


    public function test() {

        // Executed afterFind()

    }

}



Override the behaviors() method in your model to connect your behavior:


class Verify extends CActiveRecord {


    public function behaviors() {

        return array(

            'MyBehavior' => array('class' => 'MyBehavior'),

        );

    }

...



To the best of my understanding, regarding e.g the event OnAfterFind, CActiveRecord defines a protected method afterFind() which, if a handler is registered, raises the event. This method can be overriden in a derived model. That method, if present, has to be declared as protected and is required to call the parent implementation parent::afterFind() or the event will not occur. That’s where the behaviors event will come into play.

It is a bit confusing that the directly called method has the obvious name for an event handler. Also, as I understand it, the method OnAfterFind is needed for defining the event, but also has the added functionality of raising the very event in question. Thus, we have the choice to call the method as an alternative to raising the event throughout the code.

/Tommy

Hi!

I tried the variant with overriding the behaviors() method and it works fine. The question is, how can I correctly attach a behavior at runtime and make it handle the afterFind method.

The idea behind is the following: I have numerical timestamps in my database, so they are just ints. When working with the model doing calculations and stuff, I want them to stay numeric.

Only when I display the values in a form, I would like them to be converted into string dates and also accept string dates as input. Thus, I will put afterFind and beforeSave into the behavior.

But when I add it to the model ‘statically’ by overriding behaviors(), it will convert the timestamps into strings all the time. For displaying, I would like to change the behavior of the model, but the default should be as it is now.

I found out that I can attach the behavior to the model at runtime and it takes it. But when I do a findAll and get a bunch of AR objects, they dont have it. This only works if I change the models design.

I might need to do another step to kind of ‘publish’ the changed model behavior to the generated AR objects, but I am not getting it. And this is what I am looking for :)

How can I make sure that the AR objects take over the behavior that I attached to the model at runtime?

CU

Hein

PS: This also relates to my other topic at http://www.yiiframework.com/forum/index.php?/topic/4346-extend-model-with-new-attributes-and-populate-by-join-query/

I am thinking of a behavior, that will make extra attributes, that have been read during a join query, available with the model as if they had been in the model definition. As an alternative for relations, which are nice for forms, but not that helpful for grids, where you might want sorting, filtering or paging. Being able to extend your model at runtime would be a nice opportunity.

Take a look at CBehavior’s attach() implementation: There all events from event() are attached with:


$owner->attachEventHandler($event,array($this,$handler));

That’s the way how you can also attach event handlers of your behavior.

Hi Mike,

I did not mention it, but this is what I also tried :) And it did not help.

If you look at my first post, I attach the bahavior to the model at runtime. That works. I run a findAll on that changed model, but the resulting AR objects do not inherit the behavior I added to the model.

From the code in my first post this means, I can run the test() method on the model with success, but it is missing on all the AR objects that result from the findAll. So I think this is why the events are not handled within my behavior - the AR’s just dont have it.

So when I add a behavior to a model at runtime - what do I do wrong, because my AR’s dont inherit it. Is my approach wrong? My syntax? I still think I oversee some important step - it may be so obvious that I cant see it :D

CU

Hein

Do we agree that the lifetime of ‘runtime’ is one request? Or is it me…?

/Tommy

Hmm, but you could still try to dynamically attach the behavior in your model’s afterFind() method then. Or i still don’t get the problem :)

Exactly. One request to the application. In a specific action of a specific controller I am thinking of how to attach a behavior to a model and get a bunch of AR objects from that model, that show this behavior.

Cheers,

Hein

I don’t get the whole picture yet. I guess the behavior is attached to one particular object. You might want to try adding the behavior to config, declare the events in the behavior and then call disableBehavior() when not required.

/Tommy

Hi Tommy,

I was thinking that, too. But there is a trap. As I try to use afterFind - when do I disable the behavior?

Same with attaching the behavior to the AR objects after findAll - just too late for the afterFind event :)

Same with scenarios - you just cant use them in this case, as they can only be set after the call to find. This way, I could implement the behavior into the model - well, would not need to be a behavior then at all - and activate it with the scenario.

The only solution I can think of by now is

  • adding a condition to the beginning of the event handlers and

  • use a global parameter to decide if the behavior is on or off

But that is not nice, its a workaround :)

CU

Hein