Models and Event Handlers.

Say i have a model for a contest table, and i have a property called "name" that defines the name of the contest.

Now, i want to be able to attach distinct events for this property, for example, before i echo it in frontend i want to CHtml::encode() it, or say i have a component that i plug in and that component has a method which transforms the name in uppercase or makes it bold or similar behavior(if you are familiar with wordpress, think at add_filters() functionality), basically i want to let an open door to change this property before i retrieve it.

Right now i have something like:




<?php

//model class

class Contest extends CActiveRecord

{

    

    public function onBeforeGetName($event)

    {

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

    }

    

    public function beforeGetName()

    {

        if($this->hasEventHandler('onBeforeGetName'))

        {

            $event=new CModelEvent($this);

            $this->onBeforeGetName($event);

            return $event->isValid;

        }

        else

            return true;

    }

    

    public function getName()

    {

        $this->beforeGetName();

        

        return $this->name;

    }


    [...]


    [...]

}



So i added an event handler to be executed right before i request the name property of my model

My controller looks like:




<?php

class SiteController extends CmsFrontendController

{

    private $model;

    

    public function actionTest()

    {

        $this->model=Contest::model()->findByPk(20);

        $this->model->attachEventHandler('onBeforeGetName', array($this, 'upperName'));

        $this->model->getEventHandlers('onBeforeGetName')->add(array($this, 'boldName'));

        echo $this->model->getName();

    }

    

    public function upperName()

    {

        $this->model->name=strtoupper($this->model->name);

    }

    

    public function boldName()

    {

        $this->model->name='<b>'.$this->model->name.'</b>';

    }

    [...]

   

    [...]

}



All this seems correct to you ?

I mean, it looks a bit messy from my point of view, but once again, as i stated, i don’t fully understand the events mechanism in yii.

LE:

But what if i need to attach this event handler to a component, what do i do then , because i don’t have access to the model instance anymore ($this->model from the controller in my case)

I’m confused why you would do this using events and not just have the appropriate CHtml method calls in the views.

Eh, the idea is that during runtime, on various points of my script, i want to be able to attach some "hooks" to alter the model properties in various ways.

what about a behaviour?

http://blog.mbischof.de/ein-anrede-behavior

Hi there mbi and thx for the reply.

Yes, i am looking for something like this, basically your example is perfect, the only thing is that in your model you define the behaviors like:




public function behaviors()

{

    return array(

        'salutation' => array(

            'class' => 'ext.behaviors.CSalutationBehavior'

        )

    );

}



But in my case, i would like to attach them dinamically from various components/extensions, that’s why i thought that events are more suited for this situation.

But i might went wrong with this idea from the start, so i will repeat that what i really want to accomplish is something in wordpress filters style.

Say i have a component that has a method that makes the font bold, and another one that makes it upper case, let’s call these methods “filters”.

After i load the model in my controller, i want to check what "filters" should i apply for that model property and if it is the case, pass the property of the model through those filters.

If you take a look at my first example, and test it, you’ll see that it does just that, but at a controller level, not in a component.

Hope i was clear enough, if not… well, i give up :)

Well, i have some new findings.

Seems like i was looking for behaviors but wasn’t aware of that.

Anyway, i also found a neat solution to change the model properties by attaching events.

Here is the example:




<?php

//CONTROLLER METHOD: 

    public function actionIndex()

    {

        Yii::import('application.modules.contest.components.*');

        Yii::import('application.modules.contest.models.*');


        $contest=Contest::model()->findByPk(20);

        

        $contest->attachEventHandler('onBeforeGetName', array(CXTest::model(&$contest), 'getBoldName'));

        $contest->attachEventHandler('onBeforeGetName', array(CXTest::model(&$contest), 'getUpperName'));

        

        echo $contest->getName();

    }




?>






<?php

//MODEL EVENT HANDLERS


    public function onBeforeGetName($event)

    {

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

    }

    

    public function beforeGetName()

    {

        if($this->hasEventHandler('onBeforeGetName'))

        {

            $event=new CModelEvent($this);

            $this->onBeforeGetName($event);

            return $event->isValid;

        }

        else

            return true;

    }

    

    public function getName()

    {

        $this->beforeGetName();

        

        return $this->name;

    }


?>






<?php 

//Component which suppose to alter the content.

class CXTest

{

    public $model;

    private static $class_call=false;

    private static $_models=array();


    public static function model($model)

    {

        self::$class_call=true;

        $class=get_class($model);

        if(empty(self::$_models[$class]))

        {

            $_model=new self;

            $_model->model=$model;

            self::$_models[$class]=$_model;

        }

        self::$class_call=false;

        return self::$_models[$class];

    }


    public function __construct()

    {

        if($this->model===null&&self::$class_call===false)

            throw new CException(Yii::t('app', 'Please instantiate the model first'));

    }

    

    public function setName($str)

    {

        $this->model->name=$str;

    }

    

    public function getName()

    {

        return $this->model->name;

    }


    public function getBoldName()

    {

        $this->setName('<b>'.$this->model->name.'</b>');

    }

    

    public function getUpperName()

    {

        $this->setName(strtoupper($this->model->name));

    }


}

?>



As i said, i am aware that this could be accomplished via behaviors as well, but this demonstrates it can be done via events too.

Any thoughts on this ?

Hah thanks to mbi’s blog post: http://blog.mbischof.de/yiiframework-events i discovered the “sender” property so right now, the CXTest becomes :




<?php

class CXTest extends CApplicationComponent

{


    public function getBoldName($event)

    {

        $event->sender->name = '<b>'.$event->sender->name.'</b>';

    }

    

    public function getUpperName($event)

    {

        $event->sender->name=strtoupper($event->sender->name);

    }


}

?>



And the attachment is:




        $contest=Quiz::model()->findByPk(20);

        $cxtest = new CXTest;


        $contest->attachEventHandler('onBeforeGetName', array($cxtest, 'getBoldName'));

        $contest->attachEventHandler('onBeforeGetName', array($cxtest, 'getUpperName'));


        echo $contest->getName();



This is just great;) but the question is, how we suppose to discover all of these goodies ?

The only reason I ask is that generally the model data shouldn’t be modified for presentation reasons. It breaks the MVC paradigm and it only takes another developer to do a $model->save() to lead to data corruption. Unless you put logic in to reverse the modifications beforeSave. For the examples you gave it adds a load of overhead to the application as well.

@Say_Ten - you are perfectly right about the save() method and it’s implications in my case. Though i will not call this method your point is still valid and makes sense.

Maybe handling the model contents to a component and attach events to that component instead of the model ?

then in the views use something like <?php echo Yii::app()->contest->getName(); ?> ?

Yes, it adds some overhead and complexity, but the thing is that i would really want to have this feature active.

Anyway, if you have other suggestions, i am open to ideas.

Thanks.