Using Yii events
Most Yii classes are extended from CComponent
which allows us to achieve great application flexibility by using events. An event is a message indicating that the application did something. We can register several event handlers that will react to certain event types. A handler can get parameters from an event it works with and react accordingly. Using events allows achieving great application flexibility.
In this recipe, you will learn how to declare and use both predefined and custom events in your application.
How to do it...
To declare an event in your CComponent
child class, you should add a method with a name starting with on. For example, if you add the onRegister
method, you will get a corresponding event declared.
Tip
A method used to declare an event becomes the default event handler.
Typically, events are used like this:
Let's look at how we can attach an event handler to an event. To achieve it, we can use the CComponent::attachEventHandler
method. It accepts the following two parameters:
$name
: Event name$handler
: Event handler; a standard PHP callback should be used
In PHP, we have several ways to define a callback as follows:
- Use a global function and just pass its name as a string, such as
'my_function'
. - Use a static class method. You should pass an array:
array('ClassName', 'staticMethodName')
. - Use an object method:
array($object, 'objectMethod')
. - Create and pass anonymous function using
create_function
as follows:$component->attachEventHandler('onClick', create_function('$event', 'echo "Click!";'));
- Since PHP 5.3, you can use anonymous functions without
create_function
:$component->attachEventHandler('onClick', function($event){ echo "Click!"; });
Note
When you use
CComponent::attachEventHandler
, event handler is added to the end of the handlers list. - To keep your code shorter, you can use component properties to manage event handlers as follows:
$component->onClick=$handler; // or: $component->onClick->add($handler);
- To manage event handlers more precisely, you can get handlers list (
CList
) usingCComponent::getEventHandlers
and work with it. For example, you can attach an event handler the same way as withattachEventHandler
using the following code:$component->getEventHandlers('onClick')->add($handler);
- To add an event handler to the beginning of handlers list, use:
$component->getEventHandlers('onClick')->insertAt(0, $handler);
- To delete a particular handler you can use
CComponent::detachEventHandler
as follows:$component->detachEventHandler('onClick', $handler);
- Alternatively, get a list of handlers as shown earlier and delete handlers from it.
As we now know how to define and use handlers, let's review some real life examples as follows:
- It is common practice to compress your application output using gzip to save client bandwidth and speed up page loading time. If you have an access to fine-tune your server, then you can instruct it to do so, but in some environments such as shared hosting, you can't.
- Fortunately, PHP can gzip the application output using output buffering and
ob_gzhandler
. In order to do so, we should start buffering the output when the application starts and releases the gzipped output, when it finishes. - Yii's application component has two events that will come in handy in this case:
CApplication::onBeginRequest
andCApplication::onEndRequest
. Let's use them. Put the following inindex.php
after configuring an application but before running it:… require_once($yii); $app = Yii::createWebApplication($config); // attaching a handler to application start Yii::app()->onBeginRequest = function($event) { // starting output buffering with gzip handler return ob_start("ob_gzhandler"); }; // attaching a handler to application end Yii::app()->onEndRequest = function($event) { // releasing output buffer return ob_end_flush(); }; $app->run();
Note
There are many handy events defined inside Yii core classes. You can get them all by searching for "function on" text in the framework
folder using your favorite IDE.
Now, let's look at another example. In Yii, you can translate strings to different languages using Yii::t
. As we all love perfect projects all language translations should be up to date. If they are not, we would like to receive an e-mail about it.
Events come in handy again here. In particular, the CMessageSource::onMissingTranslation
event that is called when the translation for a string passed to Yii::t
is missing.
This time we will use the application configuration file protected/config/main.php
to attach an event handler as follows:
… 'components' => array( … // messages component class is CPhpMessageSource by default 'messages' => array( // using static class method as event handler 'onMissingTranslation' => array('MyEventHandler', 'handleMissingTranslation'), ), … ) …
Now, we should implement our handler. Create protected/components/MyEventHandler.php
as follows:
class MyEventHandler { static function handleMissingTranslation($event) { // event class for this event is CMissingTranslationEvent // so we can get some info about the message $text = implode("\n", array( 'Language: '.$event->language, 'Category:'.$event->category, 'Message:'.$event->message )); // sending email mail('admin@example.com', 'Missing translation', $text); } }
Let's look at the last example. We have a blog application and we need to send an e-mail when there is a new comment (Comment
) to the blog post (Post
).
Comment is a standard AR model generated with Gii. Post is the same Gii-generated model except some customized methods. We will need a custom event NewCommentEvent
to store both Post
and Comment
models and a handler class Notifier
that will do the work.
- Let's start with
protected/components/NewCommentEvent.php
:class NewCommentEvent extends CModelEvent { public $comment; public $post; }
It is pretty simple. We have just added two properties.
- Now, let's move on to
protected/models/Post.php
. All standard AR methods are omitted to emphasize on what was added:class Post extends CActiveRecord { // custom method for adding a comment // to current post function addComment(Comment $comment){ $comment->post_id = $this->id; // creating event class instance $event = new NewCommentEvent($this); $event->post = $this; $event->comment = $comment; // triggering event $this->onNewComment($event); return $event->isValid; } // defining onNewComment event public function onNewComment($event) { // Event is actually triggered here. This way we can use // onNewComment method instead of raiseEvent. $this->raiseEvent('onNewComment', $event); } }
- Now, it is time to implement a notifier. Create
protected/components/Notifier.php
as follows: - Now, it is time to get these together in
protected/controllers/PostController.php
:class PostController extends CController { function actionAddComment() { $post = Post::model()->findByPk(10); $notifier = new Notifier(); // attaching event handler $post->onNewComment = array($notifier, 'comment'); // in the real application data should come from $_POST $comment = new Comment(); $comment->author = 'Sam Dark'; $comment->text = 'Yii events are amazing!'; // adding comment $post->addComment($comment); } }
- After the comment has been added, admin will receive an e-mail about it.
There's more...
It is not always necessary to attach an event handler. Let's look at how we can handle an event that is already declared inside an existing component by overriding a base class method. For example, we have a form model UserForm
used to collect some information about our application user and we need to get the complete name from the first and the last name entered by the user.
Fortunately, in CModel
, which is a base class for all Yii models including form models, CModel::afterValidate
method is defined. This method is being called after a successful form validation. Let's use it in our protected/models/UserForm.php
model:
class UserForm extends CFormModel { public $firstName; public $lastName; public $fullName; public function rules() { return array( // First name and last name are required array('firstName, lastName', 'required'), ); } // $event argument here is CEvent instance that // was created passed when an event method was called. // This time it was happened inside of // CModel::afterValidate(). function afterValidate() { // If this method was called then // the model is already filled // with data and data is valid // so we can use it safely: $this->fullName = $this->firstName.' '.$this->lastName; // It's important to call parent class method // so all other event handlers are called return parent::afterValidate(); } }
We need to call parent method inside of afterValidate
because parent implementation calls onAfterValidate
that actually raises events:
protected function afterValidate() { $this->onAfterValidate(new CEvent($this)); }
Note
An event method name should always be defined as function eventHandler($event){…}
, where $event
is a CEvent
instance. The CEvent
class contains just two properties named sender
and handled
. First property contains an object that called the current event while the second can be used to prevent calling all others not yet executed handlers by setting it to false
.
The approach described above can be used to customize your Active Record models and implement your own model behaviors.
Further reading
For further information, refer to the following URLs:
See also
- The recipe named Using getters and setters in this chapter
- The recipe named Configuring components in this chapter