Have you ever heard about Yii events system? It allows you attach handlers to certain events in the application. Events could be raised by any application component (e.g. model, widget or controller) and could be very useful in some cases. You can read more about events here:
http://www.yiiframework.com/doc/guide/1.1/en/basics.component#component-event http://www.yiiframework.com/wiki/44/behaviors-events/
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. Our team developed an extension that could help you to avoid this restriction.
Installation:
- Download extension: http://weavora.com/yii/events_observer.zip
- Unzip package to your app folder
- Change protected/config/main.php:
...
// 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
Example of usage:
{
/**
* 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.
Advanced example of usage: 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).
{
/**
* 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:
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 component will be helpful for you and profitable for your projects
Comments (7)
Is it possible to attach a global observer for all models? i.e. if I want all my models to have created_at and updated_at fields in the database.
No, you can’t attach handler for all models at one place, instead you should to attach handler to each model one by one. In you case with created_at/updated_at fields, more profitable solution is use behaviors: e.g. blameable-behavior
Nice article. Thx
Thank you very much, very interesting, but download link http://weavora.com/yii/events_observer.zip is broken. fix it.?
Hey guys am unable to run this example. plz help me out.
Am trying to run this example with Yii demo ‘blog’ application, but no msg echoed yet after post created or saved..????
I have copied both files(DemoObserver.php, ActiveRecord.php) into yii\demos\blog\protected\components directory.
Added lines in main.php in config directory, as Like:
‘preload’=>array(‘log’, ‘DemoObserver’),
‘components’=>array( ‘DemoObserver’ => array( ‘class’ => ‘DemoObserver’, ), ‘ActiveRecord’, ),
Post::model()->attachEventHandler(‘onAfterSave’, array($this, ‘savePost’));
‘Blog’ has Post model also. What should i do? may be am going wrong? so help me.
The idea is good but the design is not . You should not overload attachEventHandler because each time a new Model is created each events are added to the static property _events that grow very fast so you can end with a very very large array that consume a lot of memory for nothing .
2 options rename for exemple to
the best way
public function addStaticEventHandler($name,$handler) { self::$_events[] = array( ‘component’ => get_class($this), ‘name’ => $name, ‘handler’ => $handler ); }
and then
public function attachStaticEvents($events) {
foreach ($events as $event) { if ($event['component'] == get_class($this)) parent::attachEventHandler($event['name'], $event['handler']); } }
or add a key to the _event array to avoid duplicate entry when you use multiple instance of the same model
self::$_events[get_class($this)][$name] = array( ‘component’ => get_class($this), ‘name’ => $name, ‘handler’ => $handler ); parent::attachEventHandler($name, $handler);
Very useful indeed; as you note, the Observer pattern is all about not having to change the code of the Model you’re observing.
But unless I’ve missed something obvious, there’s no clear statement of license/usage (just a copyright statement). Any chance you can clarify the license for this (and your other code on github too) please?