Book Image

Yii 1.1 Application Development Cookbook

Book Image

Yii 1.1 Application Development Cookbook

Overview of this book

When Alex told me he was about to write a Yii cookbook about a year ago, I was wondering how original it would be, considering the fact that there was already an online user-contributed cookbook (aka. Yii wiki). It turned out Alex produced a book that is not only full of wisdom about how to use Yii effectively, but also presented in such a systematic way that it can be taken as an essential companion book to the definitive guide to Yii. In fact, Alex has successfully intrigued the interest of every member in the Yii developer team when he asked for review and comments on his newly finished book chapters.As the founder and the lead developer of the Yii framework, I feel this book is a must-read for every Yii programmer. While this book does not describe directly the rules set by Yii, it shows how to program with Yii from a practical perspective. People who are driven by tight project schedules will find this book very handy as it gives ready-to-use solutions to many problems they may face in their projects; people who are already familiar with Yii will also find this book very informative as most problem solutions given in the book can be considered as officially recommended because they have undergone thorough review of every Yii developer team member. Alex, through this book and his active participation in the Yii project, proved himself to be a great programmer as well as a good writer. Qiang XueLead developer of the Yii framework Yii framework is a rapidly growing PHP5 MVC framework often referred to as Rails for PHP. It has become a solid base for many exciting web applications such as Stay.com and Russia Today's meetfriends.rt.com and can be a good base for your developments. Yii is an object-oriented, high-performance, component-based PHP web application framework. Yii is pronounced as Yee and is an acronym for "Yes It Is!". Familiar with Yii and want to exploit it to its full potential, but do not know how to go about it? Yii 1.1 Application Development Cookbook will show you how to use Yii efficiently. You will learn about implementing shortcuts using core features, creating your own reusable code base, using test-driven development, and many more topics that will escalate your knowledge in no time at all! Yii 1.1 Application Development Cookbook will help you learn more about Yii framework and application development practices in general with demonstrations of shortcuts and information about dangerous things you should not do. Grouped in 13 chapters, the recipes will assist you to write your applications exploiting Yii core functionality to its full potential. The chapters are generally independent of each other and you can start reading from the chapter you need most, whether it is "AJAX and jQuery", "Database, Active Record and Model Tricks" or "Extending Yii". The most interesting topics include Yii application deployment, a guide to writing your own extensions, advanced error handling, debugging and logging, application security, and performance tuning. Yii 1.1 Application Development Cookbook will help you utilize Yii functionalities completely and efficiently.
Table of Contents (21 chapters)
Yii 1.1 Application Development Cookbook
Credits
Foreword
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 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:

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

    Note

    CComponent::hasEvent checks if 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 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 and CApplication::onEndRequest. Let's use them. Put the following in index.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('[email protected]', '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.

  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 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);
        }
    }
  3. Now, it is time to implement a notifier. 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 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.

See also

  • The recipe named Using getters and setters in this chapter

  • The recipe named Configuring components in this chapter