Yii 1.1: static-events

Attach events on class base.
11 followers

Description

Allows you to register event handlers on class base.

To make this possible, the extension consists of two pieces:

  1. The EventRegistry - an application component that serves as single point to register your event handlers.
  2. The EventBridgeBehavior - a behavior that observes its owner and forwards intercepted events to the EventRegistry which will in turn invoke registered event handlers.

Requirements

Usage

The event registry allows you to statically attach event handlers to events. This means you attach handlers on a per class base instead of on a per instance base like it is done in Yii normally. You specify 'invoke my event handler when CActiveRecord raises onAfterConstruct' instead of 'invoke my event handler when this instance of an CActiveRecord raises onAfterConstruct'.

When the event registry gets notified about an event that was raised by a certain object, it will lookup all registered handlers and invoke them. When doing the lookup, it will also care for inheritance. This means if somebody attached an event handler to CActiveRecord::onAfterConstruct, but the event is raised by MyModel (which extends CActiveRecord), the attached event handler will also be invoked. If somebody attached an event handler to MyModel::onAfterConstruct, he will be notified only if this class (or classes that inherit from MyModel) raise onAfterConstruct. He will not be notified if the event is raised by MyModel2 (which extends CActiveRecord).

Unfortunatelly, the event registry can't observe arbitrary classes. Instead, it works together with the EventBridgeBehavior. This behavior must be attached to all classes that you want to observe on a per class base. Some Yii classes allow you to attach behaviors in config (like CApplicationComponent and CModule, including their child classes CApplication, CConsoleApplication, CWebApplication, CWebModule and GiiModule). So for those it is easy to ensure the EventBridgeBehavior will be attached once they are created.

Other Yii classes allow you to override a method named behaviors() (like CConsoleCommand, CController and CModel, including their child classes). These classes are normally extended and not used directly. Often, you will have something like:

public Controller extends CController

or:

public ActiveRecord extends CActiveRecord

somewhere in your project and extend your actual controllers and active records from these customized base classes. So this might be a convenient place to configure the EventBridgeBehavior.

A third group of Yii classes doesn't allow you to configure behaviors at all (like COutputProcessor or CViewAction) although they define some events. So unfortunatelly the EventBridge isn't any help here. Since for those classes you have to attach behaviors on a per instance base, there isn't much sense in attaching event handlers on a per class base and chasing down instances to attach the corresponding behavior. Instead, you could as well attach the event handlers on a per instance base.

The last group of Yii classes that I can think of doesn't allow you to configure behaviors, but also doesn't provide any events. So we don't have to care about them here.

A last word about the EventBridgeBehavior: It requires the EventInterceptor extension to catch events of its owner and to forward them to the EventRegistry. Please make sure the extension is available and auto loadable.

// application.config.main
'import' => array(
  // EventInterceptor is required by EventBridgeBehavior
  'ext.components.event-interceptor.*',
),
'behaviors' => array(
  // attach EventBridgeBehavior to application, so we can attach to
  // application events on a per class base.
  'eventBridge' => array(
    'class'  => 'ext.components.static-events.EventBridgeBehavior',
  ),
),
'components' => array(
  'events' => array(
    'class'  => 'ext.components.static-events.EventRegistry',
    'attach' => array(
      // Attach to application events.
      // Again a stupid example. Since there will ever be only one
      // application instance, we could as well use normal per instance
      // event binding like it is done normally in Yii. But it shows how it
      // is meant to be done...
      'CApplication' => array(
        'onBeginRequest' => array(
          function( $event ) {
            Yii::log( 'CApplication::onBeginRequest', CLogger::LEVEL_TRACE );
          },
        ),
        'onEndRequest' => array(
          function( $event ) {
            Yii::log( 'CApplication::onEndRequest - first handler', CLogger::LEVEL_TRACE );
          },
          function( $event ) {
            Yii::log( 'CApplication::onEndRequest - second handler', CLogger::LEVEL_TRACE );
          },
        ),
      ),
    ),
  ),
  'log'=>array(
    'class'=>'CLogRouter',
    'routes'=>array(
      // enable web log route to see what we just logged
      array(
        'class'=>'CWebLogRoute',
      ),
    ),
  ),
),
 
// or to attach/ detach at runtime:
$events = Yii::app()->events;
$events->attach( 'CActiveRecord', 'onAfterConstruct', $callback );
$events->detach( 'CActiveRecord', 'onAfterConstruct', $callback );

Resources

Total 5 comments

#15220 report it
schmunk at 2013/10/18 12:55pm
Truly magnificent!

This is maybe the most marvelous extension I've used so far. And I think I've used "some" :)

A magic piece of code!

#9721 report it
fantgeass at 2012/09/06 02:00am
Re: Re: Modules support

@Ben, good explanation, thanks.

#9720 report it
Ben at 2012/09/05 09:45pm
Re: Modules support

Hi fantgeass,

I'm not sure what you mean. Can you please explain in more detail? To me the config reads like "when class User raises event onNewUser, execute static method newUser in class NewsFeed". This is already possible, it requires that the User class has the EventBridgeBehavior attached and the configuration should be placed, well, in the config. This also ensures that the event handlers get attached as soon as the EventRegistry is accessed the first time.

If the sample setup is for module-internal communication (you are sure both User and NewsFeed exist, because they are part of the module), and you don't want users of the module to have to configure these connections manually, the modules init() method might be right place to setup these connections.

#9705 report it
fantgeass at 2012/09/05 03:14am
Modules support

I advice to add "modules support", like this:

class UserModule extends CWebModule
{
    public $events = array(
        'User' => array(
            'onNewUser' => array(
                array('NewsFeed', 'newUser')
            )
        )
    );
}
#9700 report it
fantgeass at 2012/09/04 07:28am
Check issue

Check this https://github.com/bwoester/yii-static-events-component/issues/1

Leave a comment

Please to leave your comment.

Create extension