Book Image

Yii Application Development Cookbook - Second Edition - Second Edition

Book Image

Yii Application Development Cookbook - Second Edition - Second Edition

Overview of this book

The Yii framework is a rapidly growing PHP5 MVC framework often referred to as Rails for PHP. It has already become a solid base for many exciting web applications such as Stay.com and can be a good base for your developments, too. This book will help you to learn Yii quickly and in more depth for use in for your developments."Yii Application Development Cookbook" will show you how to use Yii efficiently. You will learn about taking shortcuts using core features, creating your own reusable code base, using test driven development, and many more topics that will give you a lot of experience in a moderate amount of time.The second edition fixes all errata found in the first edition and also features new recipes on the client side, HTTP caching, and using Composer with Yii.The chapters of the book are generally independent and since this book's goal is to enhance a practical approach to Yii development, you can start reading from the chapter you need most, be it Ajax and jQuery, Database, Active Record, and Model Tricks, or Extending Yii."Yii Application Development Cookbook" will help you to learn more about the Yii framework and application development practices in general, showing shortcuts and dangerous things you shouldn't do.With all the recipes grouped in 13 chapters, you will write your applications more efficiently using shortcuts and using Yii core functionality in a good way. The most interesting topics are; Yii application deployment, a guide to writing your own extensions, advanced error handling, debugging and logging, application security, performance tuning, and much more."Yii Application Development Cookbook" will help you to learn more about the Yii framework and application development practices in general. You will write your applications more efficiently using shortcuts and using Yii core functionality in a good way.
Table of Contents (20 chapters)
Yii Application Development Cookbook Second Edition
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface
Index

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 us to achieve 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.

Note

A method used to declare an event becomes the default event handler.

Typically, events are used like this:

  • Declare an event by adding a corresponding method

  • Attach one or multiple event handlers

  • The component raises an event by using the CComponent::raiseEvent method

  • All subscribed handlers are called automatically

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 the handlers' list (CList) using CComponent::getEventHandlers and work with it. For example, you can attach an event handler the same way as with attachEventHandler using the following code:

    $component->getEventHandlers('onClick')->add($handler);
  • To add an event handler to the beginning of the handlers' list, use the following code:

    $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.

Note

CComponent::hasEvent checks if the event specified is defined in the component.

CComponent::hasEventHandler checks if there are handlers attached to the event specified.

As we now know how to define and use handlers, let's review some real life examples as follows:

  • It is a common practice to compress your application output using gzip to save client bandwidth and speed up page loading time. If you have full access to your server, then you can configure it to do so, but in some environments such as shared hosting, you don't.

  • Fortunately, PHP can gzip the application output using output buffering and the ob_gzhandler function. In order to do so, we should start buffering the output when the application starts and release the gzipped output, when it completes.

  • Yii's application component has two events that will come in handy in this case: CApplication::onBeginRequest and CApplication::onEndRequest. Let's use them. Insert the following code snippet in the index.php file 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's core classes. You can get them all by searching for the 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's 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('[email protected]', 'Missing translation', $text);
    }
}

Let's look at another example. Let's assume 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 for 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.

  1. 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.

  2. Now let's move on to protected/models/Post.php. All standard AR methods are omitted to emphasize 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);
        }
    }
  3. Now it is time to implement the Notifier class. Create protected/components/Notifier.php as follows:

    class Notifier {
        function comment($event){
           $text = "There was new comment from {$event->comment->author} on post {$event->post->title}";
           mail('[email protected]', 'New comment', $text);
        }
    }
  4. 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);
       }
    }
  5. 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 method of the base class. 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 the base class for all Yii models including form models, the CModel::afterValidate method is defined. This method is 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'),
        );
    }

    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 the parent method inside of the afterValidate function because parent implementation calls onAfterValidate that actually raises events:

protected function afterValidate()
{
   $this->onAfterValidate(new CEvent($this));
}

Note

An event's 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. The first property contains an object that calls the current event, while the second can be used to prevent calling all other, not yet executed handlers, by setting it to false.

The approach described here can be used to customize your Active Record models and implement your own model behaviors.

See also

  • The Using getters and setters recipe

  • The Configuring components recipe