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 only mentioned very briefly, only experienced Yii developers usually use these. However, the features described here are relatively simple and using them makes development with Yii much more fun and productive.
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 classes 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.
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();
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 that C# properties gives us: calling getters and setters like class members (
$model->property
instead of$model->getProperty()
). 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();
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. Note that there's no setRead setter // method. $object->read = 'value'; // echoes 'read only property' echo $object->read; // gives us an error since we are trying to read // to write-only property. Note that there's no getWrite getter // method. echo $object->write; // writes 'value' to private $write $object->write = 'value';
Yii uses this technique extensively because almost everything is a component. For example, when you call Yii::app()->user->id
to get the currently logged in user ID, what's really called is Yii::app()->getUser()->getId()
.
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 call $myClass->property
, it receives property
as the $name
parameter. If a method named getProperty
exists, then PHP uses its return value as a property
value.
For further information, refer to the following URL:
http://www.php.net/manual/en/language.oop5.overloading.php#language.oop5.overloading.members
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.
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.
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
methodAll 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!"; });
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
) 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 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
andCApplication::onEndRequest
. Let's use them. Insert the following code snippet in theindex.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();
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('admin@example.com', '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.
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 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 the
Notifier
class. Createprotected/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); } }
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.
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.
For further information, refer to the following URLs:
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 that 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 use a class, for example, CDbCriteria
, we are not including it explicitly so PHP initially cannot find it and tries to rely on the autoloading feature; the SPL autoloader, to be precise. In most cases, the 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 the 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 defaultIt 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
Let's assume that we have a custom class named
LyricsFinder
that finds lyrics for a given song. We have put it underprotected/apis/lyrics/
and in ourprotected/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; } }
When executing it, we will get the following PHP error:
include(LyricsFinder.php): failed to open stream: No such file or directory.
Yii helps us there a bit because at the error screen, we can see that the 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. When developing using case insensitive filesystems such as ones used by Windows, make sure you're using the same case in both the filename and code since it can be a problem when you deploy your code to a case sensitive Linux server.
Let's look at application.apis.lyrics.LyricsFinder
.
application
is a standard alias that points to your application's protected
folder and is translated into a filesystem path. The following table shows some more standard aliases:
Alias |
Path |
---|---|
|
|
|
|
|
|
|
|
|
|
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.
If you want your classes to be imported automatically like with Yii's 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 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
To get the example code files for this book visit http://yiicookbook.org/code.
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:
Exceptions allow the simplifying of the process of detecting and fixing application errors and special situations, such as database connection failure or API failure
Exceptions allow the generating of 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.
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 lyrics for a song 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->getRequestUrl($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)); } }
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 exception
LyricsFinderHTTPException
. This is how we can handle it in ourprotected/controllers/TestController.php
class: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; } }
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 aPost
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)); } }
Yii converts all non-fatal application errors to CException
automatically.
Additionally, the default exception handler raises either the onError
event or the onException
event. The default event handler writes a log message with the error level set to error
. Additionally, if your application's YII_DEBUG
constant is set to true
, unhandled exceptions or errors 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.
For further information, refer to the following URLs:
Yii is a very customizable framework. Moreover, as in every customizable code, there should be a convenient way to set up different application parts. So in Yii, this is provided through a configuration file named main.php
located at protected/config/
.
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 a 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
.
When you are using the Yii::app()->db
component for the first time directly or through the Active Record model, Yii creates a component and initializes its public properties with the corresponding values provided in the db
array under the components
section of the main.php
application configuration file. In the preceding code, the 'connectionString'
value will be assigned to CDbConnection::connectionString
, the 'username'
value 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 the 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 that you can set from config.
In the preceding code, the 'class'
property is a bit special because it is used to specify the component's 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's component and it is very useful whenever a standard component does not fit your application.
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:
Web application (
CWebApplication
)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 that it can be used as a reference.
Both console and web application components are listed in the following table:
Additional components available only for web application are listed in the following table:
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.
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 the CLinkPager
widget before it is 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 a value of 15
for all CLinkPager
widget calls. Is there a better way? Definitely, yes.
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.
Let's use it to set
pageSize
application-wide. We will need to edit themain.php
application configuration file as follows:return array( … 'components'=>array( 'widgetFactory'=>array( 'widgets'=>array( 'CLinkPager'=>array( 'pageSize'=>15, ), … ), ), … ), );
Now, the default value for
pageSize
ofCLinkPager
will be15
, so if we omit this parameter for all theCLinkPager
classes of the application then it will be15
, application-wide.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 set the default overall style in an external file, but can still override this through inline styles for individual widgets.
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
All collections implement SPL IteratorAggregate
, Traversable
, and Countable
. Lists and maps also implement SPL ArrayAccess
. It allows the use of collections like a standard PHP construct. The following is a snippet from the CList
API:
The following is the code snippet from the
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 the
Post
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 inCList
, 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 of theCMap
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
andCStack
implements a queue and a stack respectively. A queue works as FIFO: first in, first out, and the stack is LIFO: last in, first out. In the same way 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();
You can work with request data directly using PHP superglobals such as $_SERVER
, $_GET
, or $_POST
but the better way is to use Yii's powerful CHttpRequest
class that resolves inconsistencies among different web servers, manages cookies, provides some additional security, and has a nice set of OO methods.
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, the returned parts are marked with a bold font.
Methods |
Results |
---|---|
|
|
|
http://cookbook.local |
|
|
|
|
|
|
The methods that allow us to ensure the request types are getIsPostRequest
, getIsAjaxRequest
, and getRequestType
.
For example, we can use
getIsAjaxRequest
to serve different content based on the request type:class TestController extends CController { public function actionIndex() { if(Yii::app()->request->isAjaxRequest) $this->renderPartial('test'); else $this->render('test'); } }
In the preceding code, we are rendering a view without a layout if the request is made through AJAX.
While PHP provides superglobals for both
POST
andGET
, Yii 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 // Settings table that holds id of the user, // setting name and setting value. $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 us to initiate a 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. The MIME type, 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 aCCookieCollection
class instance that allows us to work with cookies. AsCCookieCollection
extendsCMap
, 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; } }
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!!');
}
}