Welcome once again to this tour of the Yii Framework’s CComponent
class. This three-part series demonstrates how Yii uses a component-based architecture and how the class implements properties, configuration, events, and behaviors. In Part 1 you saw how Yii uses PHP’s magic methods to implement component properties. In this article, which is Part 2 of the series, I’ll show you how you can do event-based programming in PHP. I’ll also show you how the component class makes it possible.
Events
An application event is something that occurs which might be of interest to other bits of code. A standard event in most GUI applications would be a “click” event, but the sky’s the limit and what events you define is really up to you. For example, when a user registers you’ll often want to send her an email welcoming her to your application. You could define “Registration complete” as an event. Rather than hard-coding email logic or even setting different user properties in your code, the module could simply raise an event and forget about any specific implementation details. The details can be provided by application-specific modules allowing you to keep individual requirements separate from your reusable code. Events allow you to attach a potentially unlimited amount of functionality without changing your core modules and components.
There are generally three steps to implementing an event:
- Define the event
- Attach custom functionality to the event
- Trigger the event when it happens
Defining Events
First you need to define the event allowing Yii to attach functions to it. Continuing with the user registration example, you would create a new event arbitrarily called onUserRegistered
that is raised after a user has been successfully registered. This would be defined within my your user module’s code.
<?php
public function onUserRegistered($event) {
$this->raiseEvent("onUserRegistered", $event);
}
Events in Yii are defined by simply adding a function with “on” prefixed to the method name. In order to attach a function to an object you need to be able to access the object from other areas of the application. The event is added to the user component so that it can be accessed throughout the application through Yii::app()->user
.
Triggering Events
The controller class of the user module would be responsible for triggering the event when it actually occurs.
<?php
public function actionUserRegister() {
// code to register a user and add to the database
// ...
// user has been successfully registered so lets raise an event to tell the world
$e = new CEvent($this, array("user" => $user));
Yii::app()->user->onUserRegistered($e);
}
Once a user has been successfully registered and added to your database, you’ll want to tell the world. This code raises the event. Events do not know the specific details of the function they are calling so all handling functions must have the same interface. In Yii, all handling functions expect one parameter which is a CEvent
object. The CEvent
object expects two parameters: a reference to the object that raised the event, and an optional array of parameters that can be used to store specific information relating to the particular event (these parameters can be accessed later by your handling functions). In this example I want all handling functions to be able to access the user object that was just registered. To do this I pass an array of array("user" => $user)
where $user
is an object representing our recently registered user. I then trigger the event and evoke Yii’s raiseEvent()
function by calling the function onUserRegistered()
and pass in the event object.
That is all the code that needs to be added to the user module. Now any additional code anywhere in your application can attach additional functionality to be executed when a new user registers.
Attaching Events
Continuing with the example, let’s see how attach an event’s callback.
<?php
public function init() {
Yii::app()->user->onUserRegistered = array($this, "sendMyEmail");
}
public function sendMyEmail($event) {
$user = $event->params["user"];
mail($user->email, "Welcome to this amazing event app", "Hello...");
}
I’ve attaching the sendMyEmail()
method to the onUserRegistered
event so now when a new user registers the sendMyEmail()
function will be called. Alternatively, in PHP 5.3 or greater you can specify an anonymous function. Yii supports assigning any value to an event that would also return true if passed into the PHP is_callable()
function.
Yii’s Magic
Now let’s look at how Yii implements events; managing events is all handled by cleverness in the CComponent
class. In order to implement events the component class has to implement the three main concepts:
- Defining events – Yii need to store or be able to lookup defined events.
- Triggering events – When an event occurs, Yii need to call all of our PHP functions attached to the event.
- Attaching events – Yii need a mechanism to store a list of valid PHP callbacks against an event.
The mechanism for defining events in Yii is to simply create a function with the “on” prefix as you saw earlier. The mechanism for triggering an event is to add $this->raiseEvent("onMethodName");
within the defined event and simply call the method when the event occurs in your code. This leaves us with two implementation details left:
- Attaching functions to the event.
- Calling all functions attached to the event when it is triggered.
To attach the event you use the code onMyEventName = callback
, which means the implementation for attaching functions to events must be handled in the components magic __set
method.
<?php
public function __set($name, $value){
if (strncasecmp($name, "on", 2) === 0 && method_exists($this, $name)) {
$name = strtolower($name);
if (!isset($this->_e[$name])) {
$this->_e[$name] = new CList();
}
return $this->_e[$name]->add($value);
}
}
The implementation first checks if the value of $name
starts with the text “on” and that a method also exists with the same name as the value. If it does, Yii assumes $value
is a representation of a callback which it needs to attach to the event defined by $name
. Yii has a private member variable $_e
which holds an array of callbacks keyed by event names, and it simply adds the callback to the list for the particular event key.
$_e => array( 'onUserRegistered' => array( 0 => array(object, 'sendMyEmail') ), 'onSomeOtherEvent'=>array( 0 => function(){} 1 => ... ) )
All that’s left now is to call the attached functions when the event is triggered. From looking at the array of stored events, this could be as easy as looking up the event in the $_e
variable, and iterating through each callback in the array and running it.
public function raiseEvent($name, $event) {
foreach ($this->_e[$name] as $handler) {
if (is_array($handler)) {
// an array: 0 - object, 1 - method name
list($object, $method) = $handler;
$object->$method($event);
}
else {
// PHP 5.3+ anonymous function
call_user_func($handler, $event);
}
}
}
I’ve refactored the raiseEvent()
method so it’s easier to read for our demo, but the Yii implementation has more error checking and handles additional more callback types, such as static methods.
Summary
Events are a great concept to implement for robust, flexible code. In this article you’ve learned how to create reusable components that can have their functionality extended through the use of events, and have seen seen how the Yii framework’s CComponent
class implements and manages events in PHP. Of course, the usage and implementation details may be specific to Yii but the concept itself is universal.
This wraps up our second article in this three part series. The final article walks through creating and implementing behaviors, another great way to extend a component’s functionality while keeping it easy to reuse.