[extension] Yii events observer for models

Hi,

Yii events seem good, but have one drawback: you can attach handlers only to created objects. I.e. if you have Post model, then you can attach handler to ‘onAfterSave’ event only after creating instance of Post, but you can’t attach global handler to all instances of Post model. We developed an extension that could help you to avoid this restriction.

Installation

[list=1]

[*]Download package from attachment

[*]Unzip package to your app folder

[*]Change protected/config/main.php:




return array(

    ...

   

    // append preloaded components with DemoObserver

    // (it's just example of observer)

    'preload'=>array(..., 'DemoObserver'),


    ...


    // add to application components ActiveRecord and DemoObserver

    // (it's just example of observer)

    'components' => array(

        ...

        'DemoObserver' => array(

            'class' => 'DemoObserver',

        ),

        'ActiveRecord',

    ),

    ...

);



[*]Important: Make sure that all your models extend from ActiveRecord class (not CActiveRecord)

[*]Modify protected/components/DemoObserver.php in concordance with your wishes

[/list]

Example of usage:




class DemoObserver extends CComponent

{

    /**

     * Attach event handlers here

     */

    public function init()

    {

        Post::model()->attachEventHandler('onAfterSave', array($this, 'savePost'));

    }

   

    /**

     * Just example of event handler

     *

     * @param object $event

     */

    public function savePost($event)

    {

        $post = $event->sender;

        // do some actions with post

        // e.g.:

        if ($post->isNewRecord)

        {

            echo 'User ' . Yii::app()->user->id . ' just created new post "' . $post->name . '"';

        }

    }

}



This observer will print ‘User USER_ID just created new post “POST_NAME”’ anytime new post created.

One more example: Activity Feed

You can create observer which would be ‘spawn’ for user and log his actions (then you can show activity feed for user friends and so on).




class ActivityObserver extends CComponent 

{

	/**

	 * Attach event handlers here

	 */

	public function init() 

	{

		Post::model()->attachEventHandler('onAfterSave', array($this, 'savePost'));

		Comment::model()->attachEventHandler('onAfterSave', array($this, 'saveComment'));

		User::model()->attachEventHandler('onAfterSave', array($this, 'saveProfile'));

	}

	

	public function savePost($event)

	{

		$post = $event->sender;

		if ($post->isNewRecord)

		{

			$activity = new Activity();

			$activity->user_id = Yii::app()->user->id;

			$activity->message = "created new post '" . CHtml::encode($post->name) . "'";

			$activity->created_at = time();

			$activity->save();

		}

	}

	

	public function saveComment($event)

	{

		$comment = $event->sender;

		if ($comment->isNewRecord)

		{

			$activity = new Activity();

			$activity->user_id = Yii::app()->user->id;

			$activity->message = "commented post '" . CHtml::encode($comment->post->name) . "'";

			$activity->created_at = time();

			$activity->save();

		}

	}

	

	public function saveProfile($event)

	{

		$profile = $event->sender;

		if (!$profile->isNewRecord)

		{

			$activity = new Activity();

			$activity->user_id = $profile->id;

			$activity->message = "updated profile";

			$activity->created_at = time();

			$activity->save();

		}

	}

}



Now you can easily show friends activity:




class Activity extends ActiveRecord {


	public function scopes()

	{

		return array(

			'desc'=>array(

				'order' => 'created_at DESC',

			),

		);

	}


	public function findAllForUser($friendsIds) 

	{

		$criteria = new CDbCriteria();

		$criteria->condition = 'user_id IN (' . join(',', $friendsIds) . ')';

		return $this->desc()->findAll($criteria);

	}

}


$friendsActivity = Activity::model()->findAllForUser($friendsIds);



Hope this extension will be helpful for you and profitable for your projects.

Hey

I did a similar thing, I just extended AR with a static property.




    static $storedEventHandlers = array();


    protected function afterConstruct() {

        parent::afterConstruct();

        $this->attachEventHandlersFromStatic();

    }


    protected function afterFind() {

        parent::afterFind();

        $this->attachEventHandlersFromStatic();


    }


    public function attachEventHandlersFromStatic()

    {

        if (isset(self::$storedEventHandlers[get_class($this)])) {

            foreach (self::$storedEventHandlers[get_class($this)] as $staticEventHandler)

            {

                $this->attachEventHandler($staticEventHandler['event'], $staticEventHandler['handler']);

            }

        }

    }


    public static function storeEventHandler($class, $event, $handler)

    {

        self::$storedEventHandlers[$class][] = array('event' => $event, 'handler' => $handler);

    }

Usage:


AttributeValue::storeEventHandler('AttributeValue', 'onAttributeValueChange', array('ProductMobiltelefon', 'handleAttributeValueChange'));

The first parameter is needed, because I’m using 5.2 and late static binding was introduced in 5.3 only.

But you could use non-static method to attach event handler like AttributeValue::model()->storeEventHandler(…). In this case you can get class name of object via get_class(). But of course use static method with class name argument has own profits too.

@Weavora Team:

Nice approach. There’s only one drawback i see: The observer will be initialized on every request, even if no model is accessed at all. Also all your models now have to extend your custom AR class.

I wonder if you couldn’t try a behavoiral approach instead. The behavior could either utilize OnAfterConstruct to attach the global observer. Or maybe it’s even simpler if you implement the complete observer as behavior. (See CActiveRecordBehavior).

Just brainstorming here, though… :)

I’m not sure I follow what advantage this provides over a standard event or behavior?

I’m likely just missing something, so if someone could expound on this topic for me, I’d appreciate it :slight_smile:

This is a great extension. We are thinking about using it or a derivative of it for Zurmo. I would like to add hooks for making custom workflows and this is perfect. I do not want to have to attach events to each instantiated model, so this works great for our needs. http://www.zurmo.org if you want to check out what we are doing.

It prevents you from trawling through your code and adding your model related event wherever you use a particular model. This makes it much cleaner, you do it once and forget about it.

I’d be very interested in this approach. Has anyone done this? Many thanks!

The event-interceptor can be easily used to create a behavior that observes a model and forwards its events. I just bundled it with an application component that serves as event registry. See static-events. This way you don’t need to extends from a 3rdPary class (although you have to configure your own classes to use a 3rdParty behavior) and you don’t need to preload any components. The event registry will be lazy loaded by yii the first time an event is forwarded to it.