You are viewing revision #2 of this wiki article.
This is the latest version of this article.
You may want to see the changes made in this revision.
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
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 ?
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 :)
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 :)
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
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.
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 aCList
- 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.Use this.
I find this extension very helpful:
http://www.yiiframework.com/extension/static-events/
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
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
Answer as to why we need to create "onEventName" functions
In case people are still using Yii 1 and are still confused even after reading Blizz's comment and wonder why all examples always do this, it is because everytime you attach a handler to an event (CComponent::attachEventHandler()), yii does the following:
Before yii gives back the list of existing handlers though, it first checks if such an event name exists for that component. Yii does this through CComponent::hasEvent('onEventName'), which does a simple method_exists and concludes that all methods that start with "on" is an event. Once it passes this check, then it returns the handlers list for that event (lazy initializing the list if this was the first time the component was used). If you skip declaring your onEventName method and go straight to something like $this->raiseEvent('onEventName', new CEvent($this)); whenever you component needs to trigger an event, then all your earlier calls to add a handler for that event would fail because the CComponent::hasEvent('onEventName') call would return false. Your component's event list will also be empty (CComponent private attribute $_e) because the framework never had the chance to initialize it with a list of handlers.
I managed to reach a workable version of the explanations:
`
phpclass A extends CComponent
{
public function __construct()
{
$this->onForestRan = [ B::class, 'ForestRan' ];
}
public function runForestRun()
{
echo __METHOD__ ."\n"; $this->onForestRan( new CEvent( $this ) );
}
public function onForestRan( $event )
{
echo __METHOD__ ."\n"; $this->raiseEvent( 'onForestRan', $event );
}
}
class B extends CComponent
{
public static function ForestRan( $event )
{
echo __METHOD__ ."\n"; print_r( $event->sender );
}
}
$a = new A();
$a->runForestRun();
Output : A::runForestRun B::ForestRan **A** Object ( [_e:CComponent:private] => Array ... ... ...
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.