Yii 1.1: Events explained

24 followers

Let's see what exactly are the events in Yii!

What is an event? It is the fact that we want to express that in certain points in our source code something happens!

Let's say that we are inside a function called runForestRun() Instead of simply executing the code of this function we would like in addition to express that forest ran. In other words to say that this event has occured. Why? Because maybe others are interested that this event has occured and they would like to react!

So we want this:

function runForestRun(){ //some point in our code, either function or method
    //... arbitrary code before raising the event
    $myComponent->onForestRan(new CEvent($this));
    //... maybe some code over here after raising the event
}

In the first place the

new CEvent($this)

is just the basic object event of Yii. The CEvent class can of course be extended in case we want to get extra functionality. (like the CModelEvent which is used inside CActiveRecord)

Yeah ok I know, the myComponent does not have a method "onForestRan" yet that's why we should declare it inside the component like this:

public function onForestRan($event){
    $this->raiseEvent('onForestRan', $event);
}

Typically here we simply call raise event but certainly someone could add extra code, usually before calling raiseEvent if he wishes something extra. The standard convention is that an event is defined with the on prefix and after the name of the event with the first letter upper-case.

Cool let's say we executed event and we yelled that forest has ran! But who is going to listen? The computer cannot guess by itself therefore we should tell it who is interested! In other words to define the handlers.

So earlier before raising the event we should have called, maybe inside the initialization method of the component, something like this:

$myComponent->onForestRan = array(new SomeOtherClass, 'eventHandler1');

or that:

$myComponent->onForestRan = function() {};

or in general any callback function is valid in php: http://php.net/manual/en/language.pseudo-types.php

We define one line for each one that has interest that forest ran! In other words we could say that we prepare a list of functions or methods (static or not) that will be executed one by one when the event is raised.

The event handlers must have a signature like this:

function eventHandler1($event) {
}

And now we can grab the sender object by $event->sender ;)

Please note that when a class (a component) yells that an event has happened (that forest ran) then it is more sane that the event handlers (those of interest to the event) are other entities! To define a method inside the same class is technically possible but without much of sense!

So let's do a resume of the events in three steps:

1) Define the event we want as a method inside the class with the prefix on and typically inside it we call the raiseEvent.

2) Define all the callback functions (the handlers) which they will be called when the event is going to be raised and we are done with the preperation of the event.

3) Insert the statement $myComponent->onForestRan(new CEvent($this)); in every point of the source code that is necessary in order for the event to be raised at runtime.

Please do not hesitate to ask me if not everything is understood :)

References: http://www.yiiframework.com/doc/guide/1.1/en/basics.component#component-event

http://www.yiiframework.com/wiki/44/behaviors-events

http://www.yiiframework.com/wiki/255/using-events-with-caction-classes

Total 9 comments

#15998 report it
marcovtwout at 2014/01/08 06:25am
Re: Tips for better/cleaner event management

Let's list the possible ways of attaching your event, sorted by 'amount of magic' from most to least. Pick your favorite :)

$object->onCreateAttachment = $callback;
 
$object->onCreateAttachment[] = $callback;
 
$object->onCreateAttachment->add($callback);
 
$object->attachEventHandler('onCreateAttachment', $callback)); // my personal favorite, no magic
#15790 report it
apotter at 2013/12/17 06:39am
I found a three part blog which I found helpful.

It nicely explains some of the concepts and has some limited practical coding examples. It can be found here

#15755 report it
undsoft at 2013/12/13 05:59am
Use this.

I find this extension very helpful: http://www.yiiframework.com/extension/static-events/

#14705 report it
mindplay at 2013/09/04 01:40pm
Tips for better/cleaner event management

Here's a few tips to make your event experience safer and more enjoyable.

First, consider extending CEvent - rather than simply raising non-specific events, avoid using the "catch-all" CEvent::$params collection, and instead declare classes with real constructors and properties that express the context and meaning of your events.

Using type-hints in the constructor, you can achieve IDE support, and guarantee that only a specific type of sender (and context values) are allowed:

class AttachmentEvent extends CEvent
{
    /**
     * @var Attachment
     */
    public $attachment;

    public function __construct(AttachmentController $sender, Attachment $attachment)
    {
        parent::__construct($sender);

        $this->attachment = $attachment;
    }
}

Next, when writing the actual event declarations, declare them as protected when possible, to prevent outside use of what is (most likely) an internal API intended strictly for use by the component sending the events. Use type-hints to prevent firing events without providing the required context. Avoid repeating the function name - use the magic __FUNCTION__ constant instead:

/**
 * @param Attachment $attachment created attachment
 */
public function onCreateAttachment(Attachment $attachment)
{
    $this->raiseEvent(__FUNCTION__, new AttachmentEvent($this, $attachment));
}

Finally, to avoid obscurity (and IDE warnings) in code that registers event listeners, avoid using the magic $on* set-accessor:

// do NOT do this:
$object->onCreateAttachment = function ($event) { ... }

This is bad, ugly and wrong, because $onCreateAttachment is actually a CList - so it looks like you're overwriting the entire list of event handlers! Which isn't the case (and this is a design flaw in CComponent) but if you read the property immediately after writing it, it will return a list. (yuck!)

Instead, declare the actual list-properties using doc-blocks - this provides IDE support and keeps people from having to read through your code, find the method-declarations, and understanding the magic behind it. Like this:

/**
 * @property-read CList|callable[] $onCreateAttachment
 */
class AttachmentController extends Controller {
    ...
}

When registering event handlers, you can now write out that code properly:

$object->onCreateAttachment[] = function (AttachmentEvent $event) { ... }

Note that addition of the [] brackets, meaning "append to the list", rather than relying on the dubious magical code that prevents you from overwriting the list - also, this code will pass IDE inspection.

Also note the type-hint in the event handler signature, which enables you to work with the actual $event object with IDE support inside the handler body.

#12859 report it
Blizz at 2013/04/16 06:53am
answer... perhaps

You have to look at it from the "listener" side of the story. It is a bit more work to create a separate triggering function for every event, true.

The assumption is however that there is usually more than one listener. If you have an "onXXXXXXX" function you can just use the "yii magic" to assign a new listener to the function itself instead of having call functions. It's a bit of a compromise to gain you some clarity.

From a practical point of view it is the only way the framework can pretty much guarantee that a specific event exists (the CComponent::hasEvent() function) to prevent you from binding to non-existing events.

#10773 report it
sseventysix at 2012/11/22 02:12pm
Real examples

Could you provide some real examples of where in which files to place the various pieces of code? Like Controller, Model, Component (Base Controller), Module???

Thanks

#7987 report it
pligor at 2012/05/01 12:57pm
Common query

As common practice I would like to ask the person who voted negatively to give some feedback on why the negative vote. Thank you :)

#7963 report it
pligor at 2012/04/29 06:45pm
Answer (?)

Well yes indeed it makes more sense when there are several handlers and not just one. Therefore when you raise the event, several functions are being executed with just one line of code.

I guess that you could have called yii's events "a bunch of functions" but it helps to give a more meaningful name and to think in a more object oriented way.

Obviously yii's events have a quite different purpose than javascript's events which are more straightforward. But remember that javascript is stateful!

Hope I helped :)

#7962 report it
phreak at 2012/04/29 04:13pm
question ?

Well, I understand pretty much everything about events. But I cannot give myself clear answer to this: why we create the method onForestRun($event) just to run the event. Can't we just call it directly from code ?

Leave a comment

Please to leave your comment.

Write new article