Home Web Development Yii 1.1 Application Development Cookbook

Yii 1.1 Application Development Cookbook

books-svg-icon Book
eBook $28.99 $19.99
Print $48.99
Subscription $15.99 $10 p/m for three months
$10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
BUY NOW $10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
eBook $28.99 $19.99
Print $48.99
Subscription $15.99 $10 p/m for three months
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
  1. Free Chapter
    Under the Hood
About 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.
Publication date:
August 2011
Publisher
Packt
Pages
392
ISBN
9781849515481

 

Chapter 1. Under the Hood

In this chapter, we will cover:

  • Using getters and setters

  • Using Yii events

  • Using import and autoloading

  • Using exceptions

  • Configuring components

  • Configuring widget defaults

  • Using Yii core collections

  • Working with request

 

Introduction


In this chapter, we will cover the most interesting Yii features that are hidden "under the hood". These are mostly described in the framework API, but since they are not mentioned in the official guide (http://www.yiiframework.com/doc/guide/) or mentioned very briefly, only experienced Yii developers usually use these. Yet, features described here are relatively simple and using them makes development with Yii much more fun and productive.

 

Using getters and setters


Yii has many features that came from other languages, such as Java or C#. One of them is defining properties with getters and setters for any of the class extended from CComponent (that is, virtually any Yii class).

From this recipe, you will learn how to define your own properties using getters and setters, how to make your properties read-only, and how to hide custom processing behind native PHP assignments.

How to do it...

  1. As PHP does not have properties at the language level, we can only use getters and setters in the following way:

    class MyClass
    {
        // hiding $property
        private $property;
        
        // getter
        public function getProperty()
        {
            return $this->property;
        }
        
        // setter
        public function setProperty($value)
        {
            $this->property = $value;
        }
    }
    
    $object = new MyClass();
    
    // setting value
    $object->setProperty('value');
    
    // getting value
    echo $object->getProperty();
  2. This syntax is very common in the Java world but it is a bit long to use in PHP. Still, we want to use the same functionality C# properties gives us: calling getters and setters like class members. With Yii, we can do it in the following way:

    // extending CComponent is necessary
    class MyClass extends CComponent
    {
        private $property;
    
        public function getProperty()
        {
            return $this->property;
        }
    
        public function setProperty($value)
        {
            $this->property = $value;
        }
    }
    
    $object = new MyClass();
    $object->property = 'value'; // same as $object->setProperty('value');
    echo $object->property; // same as $object->getProperty();
  3. Using this feature, you can make properties read-only or write-only while keeping the simple PHP syntax as follows:

    class MyClass extends CComponent
    {
        private $read = 'read only property';
        private $write = 'write only property';
    
        public function getRead()
        {
            return $this->read;
        }
    
        public function setWrite($value)
        {
            $this->write = $value;
        }
    }
    
    $object = new MyClass();
    
    // gives us an error since we are trying to write to read-only property
    $object->read = 'value'; 
    
    // echoes 'read only property'
    echo $object->read; 
    
    // gives us an error since we are trying to read to write-only property
    echo $object->write; 
    
    // writes 'value' to private $write
    $object->write = 'value';
  4. Yii uses this technique extensively because almost everything is a component. For example, when you are calling Yii::app()->user->id to get the currently logged in user ID, what's really called is Yii::app()->getUser()->getId().

How it works...

To use getters and setters like properties, CComponent uses the PHP magic methods: __get, __set, __isset, and __unset (http://php.net/manual/en/language.oop5.magic.php). The following example shows what Yii 1.1 CComponent::__get looks like:

public function __get($name)
{
   $getter='get'.$name;
   if(method_exists($this,$getter))
      return $this->$getter();
…

This magic PHP method intercepts all calls to missing real properties, so when we are calling $myClass->property, it receives property as $name parameter. If a method named getProperty exists, then PHP uses its return value as a property value.

There's more...

For further information, refer to the following URL:

http://www.php.net/manual/en/language.oop5.overloading.php#language.oop5.overloading.members

See also

  • The recipe named Using Yii events in this chapter

  • The recipe named Configuring components in this chapter

 

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('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.

  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('admin@example.com', '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

 

Using import and autoloading


When programming with PHP, one of the most annoying things is loading additional code with include and require. Fortunately, you can do it automatically using the SPL class loader (http://php.net/manual/en/function.spl-autoload.php).

Autoloading is one of the features which Yii relies on. Still, there are many questions about it on the forums. Let's get it clear and show how we can use it.

When we are using a class, for example, CDbCriteria, we are not including it explicitly so PHP initially cannot find it and is trying to rely on the autoloading feature; SPL autoloader to be precise. In most cases, Yii default autoloader (YiiBase::autoload) will be used.

For the sake of speed and simplicity, almost all core framework classes are loaded when needed without including or importing them explicitly. It's done through YiiBase::$_coreClasses map, so loading core classes is very fast. Zii classes, such as CMenu, extension classes or your own classes are not loaded automatically, so we need to import them first.

To import classes, we will use Yii::import:

  • Import does not include a class immediately by default

  • It does not include a class if it is not used

  • It will not load a class twice, so it is safe to import the same class multiple times

How to do it...

  1. Let's assume that we have a custom class named LyricsFinder that finds lyrics for a given song. We have put it under protected/apis/lyrics/ and in our protected/controllers/TestController.php, we are trying to use it in the following way:

    class TestController extends CController
    {
       public function actionIndex($song)
       {
          $lyric = 'Nothing was found.';
          $finder = new LyricsFinder();
    
          if(!empty($song))
             $lyric = $finder->getText($song);
    
          echo $lyric;
       }
    }
  2. When executing it, we will get the following PHP error:

    include(LyricsFinder.php) [<a href='function.include'>function.include</a>]: failed to open stream: No such file or directory.
  3. Yii helps us there a bit because at the error screen, we can see that autoloader fails because it doesn't know where to look for our class. Therefore, let's modify our code:

    class TestController extends CController
    {
       public function actionIndex($song)
       {
          $lyric = 'Nothing was found.';
          
          // importing a class
          Yii::import('application.apis.lyrics.LyricsFinder');
    
          $finder = new LyricsFinder();
    
          if(!empty($song))
             $lyric = $finder->getText($song);
    
          echo $lyric;
       }
    }

    Now our code works.

    Note

    The built-in Yii class loader requires that each class should be placed into a separate file named the same as the class itself.

How it works...

Let's look at application.apis.lyrics.LyricsFinder:

application is a standard alias that points to your application protected folder and is translated into a filesystem path. The following table shows some more standard aliases:

Alias

Path

application

path_to_webroot/protected

system

path_to_webroot/framework

zii

path_to_webroot/framework/zii

webroot

path_to_webroot

ext

path_to_webroot/protected/extensions

Note

You can define your own aliases using the Yii::setPathOfAlias method. Typically, it can be done as the first lines of protected/config/main.php, so all other config parts will be able to use these new aliases.

apis.lyrics are translated to apis/lyrics and are appended to a path retrieved from the application alias, and LyricsFinder is the class name we want to import.

If LyricsFinder requires some additional classes located in its directory, then we can use Yii::import('application.apis.lyrics.*') to import the whole directory. Note that * does not include subfolders, so if you need lyrics/includes, you should add another import statement Yii::import('application.apis.lyrics.includes.*').

For performance reasons, it is better to use explicit paths with a class name instead of * if you are importing a single class.

There's more...

If you want your classes to be imported automatically like the Yii core classes, then you can configure global imports in your main.php configuration file:

return array(
   // …

   // global imports
   'import'=>array(
      'application.models.*',
       'application.components.*',
       'application.apis.lyrics.*',
      'application.apis.lyrics.includes.*',
      'application.apis.albums.AlbumFinder',
   ),

Note

Note that using *, with a huge amount of global imports could slow your application down as there will be too many directories to check.

Tip

Downloading the example code

You can download the example code files for all Packt books you have purchased from your account at http://www.PacktPub.com. If you purchased this book elsewhere, you can visit http://www.PacktPub.com/support and register to have the files e-mailed directly to you.

 

Using exceptions


Exceptions are a core PHP feature, but they are seldom used fairly. Yii makes exceptions very useful.

There are two main areas where Yii exceptions come in handy, which are as follows:

  1. Exceptions allow simplifying the process of detecting and fixing application errors and special situations, such as database connection failure or API failure.

  2. Exceptions allow generating different HTTP responses in a very clean way.

Generally, an exception should be thrown when a component cannot handle a special situation, such as the one said earlier, and needs to leave it to higher-level components.

How to do it…

  1. Let's assume that we have an application/apis/lyrics/LyricsFinder.php class that makes an HTTP request to an API using CURL and returns song lyrics based on its name. This is how we can use exceptions inside of it:

    // create some custom exceptions to be able to catch them
    // specifically if needed
    
    // general lyrics finder exception
    class LyricsFinderException extends CException {}
    
    // used when there is a connection problem
    class LyricsFinderHTTPException extends LyricsFinderException{}
    
    
    class LyricsFinder
    {
       private $apiUrl = 'http://example.com/lyricsapi&songtitle=%s';
    
       function getText($songTitle)
       {
          $url = $this->getUrl($songTitle);
          $curl = curl_init();       
          curl_setopt($curl, CURLOPT_URL, $url); 
          curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
          $result = curl_exec($curl);
    
          // if there is an HTTP error, we'll throw an exception if($result===false)
          {
             $errorText = curl_error($curl);
             curl_close($url);
             throw new LyricsFinderHTTPException($errorText);
          }
    
          curl_close($curl);
          return $result;
       }
       
       private function getRequestUrl($songTitle)
       {
          return sprintf($this->apiUrl, urlencode($songTitle));
       }
    }
  2. As we don't know how a specific application needs to handle its API connection, we will leave it to the application itself by throwing a custom LyricsFinderHTTPException. This is how we can handle it in our protected/controllers/TestController.php:

    class TestController extends CController
    {
      public function actionIndex($song)
      {
        $lyric = 'Nothing was found.';
        
        // importing api class
        Yii::import('application.apis.lyrics.LyricsFinder');
    
        $finder = new LyricsFinder();
    
        if(!empty($song))
        {
           // We don't want to show user an error.
           // Instead we want to apologize and
           // invite him to try again later.
           try {
              $lyric = $finder->getText($song);
           }
           // we are looking for specific exception here
           catch (LyricsFinderHTTPException $e)
           {
              echo 'Sorry, we cannot process your request. Try again later.';
           }
        }         
    
        echo $lyric;
      }
    }
  3. Another usage of Yii exceptions is the generation of different HTTP responses by throwing CHttpException. For example, an action that displays a blog post represented by a Post model, loaded by its ID will look like this:

    class PostController extends CController
    {
      function actionView()
      {
        if(!isset($_GET['id']))
          // If there is no post ID supplied, request is definitely wrong.
          // According to HTTP specification its code is 400.
          throw new ChttpException(400);
          
          // Finding a post by its ID
          $post = Post::model()->findByPk($_GET['id']);
          
          if(!$post)
             // If there is no post with ID specified we'll generate
             // HTTP response with code 404 Not Found.
             throw new CHttpException(404);
    
             //  If everything is OK, render a post
             $this->render('post', array('model' => $post));
      }
    }

How it works…

Yii converts all non-fatal application errors to CException automatically.

Additionally, the default exception handler raises either the onError or an onException event. The default event handler writes a log message with error level set to error. Additionally, if your application's YII_DEBUG constant is set to true, unhandled exception or error will be displayed at a handy error screen. This screen includes a call stack trace, a code area where the exception was raised, and the file and line where you can look for the code to fix.

 

Configuring components


Yii is a very customizable framework. Moreover, as in every customizable code, there should be a convenient way to setup different application parts. So in Yii, this is provided through a configuration file named main.php located at protected/config/.

How to do it…

If you have worked with Yii before, then you have probably configured a database connection:

return array(
   …
   'components'=>array(
      'db'=>array(
         'class'=>'system.db.CDbConnection',
         'connectionString'=>'mysql:host=localhost;dbname=database_name',
         'username'=>'root',
         'password'=>'',
         'charset'=>'utf8',
      ),
      …
   ),
   …
);

This way of configuring component is used when you want to use a component across all application parts. With the preceding configuration, you can access a component by its name, such as Yii::app()->db.

How it works…

When you are using the Yii::app()->db component for the first time directly or through active record model, Yii creates a component and initializes its public properties with the corresponding values provided in db array under the components section of the main.php application configuration file. In the preceding code, 'connectionString' the value will be assigned to CDbConnection::connectionString, 'username' will be assigned to CDbConnection::username, and so on.

If you want to find out what 'charset' stands for or want to know what else you can configure in the db component, then you need to know its class. In case of db component, the class is CDbConnection. You can refer to its API page at http://www.yiiframework.com/doc/api/CDbConnection/ and look for its public properties you can set from config.

In the preceding code, the 'class' property is a bit special because it is used to specify component class name. It does not exist in the CDbConnection class. Therefore, it can be used to override a class as follows:

return array(
   …
   'components'=>array(
      'db'=>array(
         'class'=>'application.components.MyDbConnection',
         …
      ),
      …
   ),
   …
);

This way, you can override each application component and it is very useful whenever a standard component does not fit your application.

There's more...

Now, let's find out which standard Yii application components you can configure. There are two application types bundled with Yii which are as follows:

  1. Web application (CWebApplication)

  2. Console application (CConsoleApplication)

Both are extended from CApplication, so both console and web applications are sharing its components.

You can get the component names from API pages (http://www.yiiframework.com/doc/api/) and the source code of the registerCoreComponents application method, but let's list them here so the list can be used as a reference.

Both console and web application components are listed in the following table:

Component name

Default/suggested component class

Description

coreMessages

CPhpMessageSource

This component provides the source for translating Yii framework messages.

db

CDbConnection

This component provides a database connection.

messages

CPhpMessageSource

This component provides the source for translating application messages.

errorHandler

CErrorHandler

This component handles PHP errors and uncaught exceptions.

securityManager

CSecurityManager

This component provides security-related services, such as hashing, encryption, and so on.

statePersister

CStatePersister

This component provides global state persistence methods.

format

CFormatter

This component provides a set of commonly used data formatting methods.

cache

CFileCache

This component provides a caching feature.

Additional components available only for web application are listed in the following table:

Component name

Default component class

Description

session

CHttpSession

This component provides the session-related functionalities.

request

CHttpRequest

This component encapsulates the $_SERVER variable and resolves its inconsistency among different web servers.

It also manages the cookies sent from and to the user.

urlManager

CUrlManager

URL router; used both to generate and resolve application URLs.

assetManager

CAssetManager

This component manages the publishing of private asset files.

user

CWebUser

This component represents the user session information.

themeManager

CThemeManager

This component manages themes.

authManager

CPhpAuthManager

This component manages role-based access control (RBAC).

clientScript

CClientScript

This component manages client scripts (JavaScript and CSS).

widgetFactory

CWidgetFactory

This component creates widgets and supports widget skinning.

You can add your own application components (classes extended from CComponent) by simply adding new configuration items and pointing their class properties to your custom classes.

See also

  • The recipe named Configuring widget defaults in this chapter

 

Configuring widget defaults


In Yii, code pieces commonly used in views are placed into widgets. For example, a widget can render a tag cloud or provide a custom form input type. Core widgets are highly configurable and are used in views as follows:

<?$this->widget('CLinkPager', array(
'pages' => $pages,
'pageSize' => 15,
))?>

In the preceding code, we are using $this->widget that calls a CLinkPager widget with an array of parameters to display a pagination. pages and pageSize are both assigned to the corresponding public properties of CLinkPager before it is being rendered.

Note that we have changed the count of items per page to 15 in our example. If we want our pagination to display 15 items per page on all pages of our application, then we will need to provide a pageSize parameter with value 15 for all CLinkPager widget calls. Is there a better way? Definitely, yes.

How to do it…

A Yii web application provides a bunch of components. One of them is a widget factory that since Yii 1.1.3 can be used to set widget defaults.

  1. Let's use it to set pageSize application-wide. We will need to edit the application configuration file main.php as follows:

    return array(
      …
      'components'=>array(
        'widgetFactory'=>array(
          'widgets'=>array(
            'CLinkPager'=>array(
              'pageSize'=>15,
            ),
            …
          ),
        ),
        …
      ),
    );
  2. Now, the default value for CLinkPager's pageSize will be 15, so if we omit this parameter for all the application CLinkPagers then it will be 15, application-wide.

  3. Moreover, we still can override the pageSize value for a specific widget:

    <?$this->widget('CLinkPager', array(
    'pages' => $pages,
    'pageSize' => 5,
    ))?>

This works much like the CSS cascade. You are setting the default overall style in an external file, but are still able to override this through inline styles for individual widgets.

See also

  • The recipe named Configuring components in this chapter

 

Using Yii core collections


Yii has a set of collection classes used mainly for internal purposes which are not described in the Definitive Guide, but are still very useful for applications:

  • Lists: CList, CTypedList

  • Maps: CMap, CAttributeCollection

  • Queue: CQueue

  • Stack: CStack

How to do it…

All collections implement SPL IteratorAggregate, Traversable, and Countable. Lists and maps also implement SPL ArrayAccess. It allows using collections like a standard PHP construct. The following is a snippet from the CList API:

  • The following is the snippet from CList API:

    // append at the end
    $list[]=$item;
    
    // $index must be between 0 and $list->Count
    $list[$index]=$item;
    
    // remove the item at $index
    unset($list[$index]);
    
    
    // if the list has an item at $index
    if(isset($list[$index]))
    
    // traverse each item in the list
    foreach($list as $index=>$item)
    
    // returns the number of items in the list
    $n=count($list);
  • CList is an integer-indexed collection. Compared to the native PHP array, it adds stricter checks, can be used in OO fashion, and allows to make a collection read-only:

    $list = new CList();
    $list->add('python');
    $list->add('php');
    $list->add('java')
    
    if($list->contains('php'))
       $list->remove('java');
    
    $anotherList = new CList(array('python', 'ruby'));
    $list->mergeWith($anotherList);
    
    $list->setReadOnly(true);
    
    print_r($list->toArray());
  • There is another list collection named CTypedList that ensures that the list contains only items of a certain type:

    $typedList = new CTypedList('Post');
    $typedList->add(new Post());
    $typedList->add(new Comment());

    As we are trying to add a comment to a posts list, the preceding code will give you the following exception:

    CTypedList<Post> can only hold objects of Post class.
  • CMap allows using every value, integer or not, as a key. Just like in CList, it can also be used in the native PHP style, has almost the same set of OO-methods, and allows making a collection read only:

    $map = new CMap();
    $map->add('php', array('facebook', 'wikipedia', 'wordpress', 'drupal'));
    $map->add('ruby', array('basecamp', 'twitter'));
    print_r($map->getKeys());
  • There is also one handy static method named CMap::mergeArray that can be used to recursively merge two associative arrays while replacing scalar values:

    $apps1 = array(
        'apps' => array(
            'task tracking',
            'bug tracking',
        ),
        'is_new' => false
    );
    
    $apps2 = array(
        'apps' => array(
            'blog',
            'task tracking',
        ),
        'todo' => array(
            'buy milk',
        ),
        'is_new' => true
    );
    
    $apps = CMap::mergeArray($apps1, $apps2);
    CVarDumper::dump($apps, 10, true); 

    The result of the preceding code is as follows:

    array
    (
        'apps' => array
        (
            '0' => 'task tracking'
            '1' => 'bug tracking'
            '2' => 'blog'
            '3' => 'task tracking'
        )
        'is_new' => true
        'todo' => array
        (
            '0' => 'buy milk'
        )
    )
  • CAttributeCollection includes all CMap functionality and can work with data just like properties:

    $col = new CAttributeCollection();
    
    // $col->add('name','Alexander');
    $col->name='Alexander';
    
    // echo $col->itemAt('name');
    echo $col->name; 
  • CQueue and CStack implement a queue and a stack respectively. A stack works as LIFO: last in, first out, and the queue is FIFO: first in, first out. Same as list and map collections these can be used in native PHP style and have OO style methods:

    $queue = new CQueue();
    
    // add some tasks
    $queue->enqueue(new Task('buy milk'));
    $queue->enqueue(new Task('feed a cat'));
    $queue->enqueue(new Task('write yii cookbook'));
    
    // complete a task (remove from queue and return it)
    echo 'Done with '.$queue->dequeue();
    echo count($queue).' items left.';
    // return next item without removing it
    echo 'Next one is '.$queue->peek();
    
    foreach($queue as $task)
       print_r($task);
    
    $garage = new CStack();
    
    // getting some cars into the garage
    $garage->push(new Car('Ferrari'));
    $garage->push(new Car('Porsche'));
    $garage->push(new Car('Kamaz'));
    
    // Ferrari and Porsche can't get out
    // since there is…
    echo $garage->peek(); // Kamaz!
    
    // we need to get Kamaz out first
    $garage->pop();
    
    $porsche = $garage->pop();
    $porsche->drive();
 

Working with request


You can work with request data directly using PHP superglobals such as $_SERVER, $_GET, or $_POST but the better way is to use Yii powerful CHttpRequest class that resolves inconsistencies among different web servers, manages cookies, provides some additional security, and has a nice set of OO methods.

How to do it…

You can access the request component in your web application by using Yii::app()->getRequest(). So, let's review the most useful methods and their usage, methods that return different parts of the current URL. In the following table, returned parts are marked with a bold font.

getUrl

http://cookbook.local/test/index?var=val

getHostInfo

http://cookbook.local/test/index?var=val

getPathInfo

http://cookbook.local/test/index?var=val

getRequestUri

http://cookbook.local/test/index?var=val

getQueryString

http://cookbook.local/test/index?var=val

The methods that allow us to ensure request type are getIsPostRequest, getIsAjaxRequest , and getRequestType.

  • For example, we can use getIsAjaxRequest to serve different content based on request type:

    class TestController extends CController
    {
       public function actionIndex()
       {
          if(Yii::app()->request->isAjaxRequest)s
             $this->renderPartial('test');
          else
             $this->render('test');
       }
    }

    In the preceding code, we are rendering a view without layout if the request is made through AJAX.

  • While PHP provides superglobals for both POST and GET, Yii way allows us to omit some additional checks:

    class TestController extends CController
    {
       public function actionIndex()
       {
          $request = Yii::app()->request;
    
          $param = $request->getParam('id', 1);
          // equals to
          $param = isset($_REQUEST['id']) ? $_REQUEST['id'] : 1;
    
          $param = $request->getQuery('id');
          // equals to
          $param = isset($_GET['id']) ? $_GET['id'] : null;
    
          $param = $request->getPost('id', 1);
          // equals to
          $param = isset($_POST['id']) ? $_POST['id'] : 1;
       }
    }
  • getPreferredLanguage tries to determine the user's preferred language. It can't be completely accurate, but it is good to use it as a fallback in case the user has not specified a preferred language manually.

    class TestController extends CController
    {
       public function actionIndex()
       {
          $request = Yii::app()->request;
          $lang = $request->preferredLanguage;
    
          // trying to get language setting from DB
          $criteria = new CDbCriteria();
          $criteria->compare('user_id', $request->getQuery('userid'));
          $criteria->compare('key', 'language');
          $setting = Settings::model()->find($criteria);
          if($setting)
             $lang = $setting->value;
    
          Yii::app()->setLanguage($lang);
          
          echo Yii::t('app', 'Language is: ').$lang;
       }
    }
  • sendFile allows to initiate file download as follows:

    class TestController extends CController
    {
       public function actionIndex()
       {
       $request = Yii::app()->getRequest();
          $request->sendFile('test.txt', 'File content goes here.');
       }
    }

    This action will trigger a file download and send all necessary headers, including content type (mimetype) and content length. Mimetype, if not set manually as a third parameter, will be guessed based on the filename's extension.

  • The last thing we are going to show in this chapter is the getCookies method. It returns a CCookieCollection class instance that allows us to work with cookies. As CCookieCollection extends CMap, we can use some native PHP methods as follows:

    class TestController extends CController
    {
       public function actionIndex()
       {
          $request = Yii::app()->request;
          // getting a cookie
          $cookie = $request->cookies['test'];
          if($cookie)
             // printing cookie value
             echo $cookie->value;      
          else {
             // creating new cookie
             $cookie=new CHttpCookie('test','I am a cookie!');
             $request->cookies['test'] = $cookie;
          }
    }

There's more...

If you are working with a lot of cookie values and want to shorten the code provided, then you can use a helper as follows:

class Cookie
{
  public static function get($name)
  {
       $cookie=Yii::app()->request->cookies[$name];
       if(!$cookie)
           return null;

       return $cookie->value;
  }

  public static function set($name, $value, $expiration=0)
  {
       $cookie=new CHttpCookie($name,$value);
       $cookie->expire = $expiration;
       Yii::app()->request->cookies[$name]=$cookie;
  }
}

After you drop this code into protected/components/Cookie.php, you will be able to perform the following:

class TestController extends CController
{
   public function actionIndex()
   {
      $cookie = Cookie::get('test');
      if($cookie)
         echo $cookie;      
      else
         Cookie::set('test','I am a cookie!!');
   }
}
Latest Reviews (1 reviews total)
Yii 1.1 Application Development Cookbook
Unlock this book and the full library FREE for 7 days
Start now