《Yii 1.1应用程序开发实例》

第二章:路由,控制器和视图

本章中,我们将涵括以下内容:

  • Configuring URL rules
  • Generating URLs by path
  • Using regular expressions in URL rules
  • Creating URL rules for static pages
  • Providing your own URL rules at runtime
  • Using base controller
  • Using external actions
  • Displaying static pages with CViewAction
  • Using flash messages
  • Using controller context in a view
  • Reusing views with partials
  • Using clips
  • Using decorators
  • Defining multiple layouts
  • Paginating and sorting data

介绍

This chapter will help you to learn some handy things about Yii URL router, controllers, and views. You will be able to make your controllers and views more flexible.

Configuring URL rules

Yii URL router is quite powerful and does two main tasks: it resolves URLs into internal routes and creates URLs from these routes. Router rules description is scattered over the official Yii guide and API docs. Let's try to understand how to configure application rules by example.

准备工作

  1. Create a fresh Yii application using yiic webapp as described in the official guide (http://www.yiiframework.com/doc/guide/) and find your protected/ config/main.php. It should contain the following:
// application components
'components'=>array(
    ...
    // uncomment the following to enable URLs in path-format
    /*
    'urlManager'=>array(
        'urlFormat'=>'path',
        'rules'=>array(
            '<controller:\w+>/<id:\d+>'=>'<controller>/view',
            '<controller:\w+>/<action:\w+>/<id:\d+>'=>'<controller>/<action>',
            '<controller:\w+>/<action:\w+>'=>'<controller>/<action>',
        ),
    ),
  1. Delete everything from rules as we are going to start from scratch.
  1. In your protected/controllers, create WebsiteController.php with the following code inside:
class WebsiteController extends CController
{
    public function actionIndex()
    {
        echo "index";
    }
    
    public function actionPage($alias)
    {
        echo "Page is $alias.";
    }
}

This is the application controller we are going to customize URLs for.

  1. Configure your application server to use clean URLs. If you are using Apache with mod_rewrite and AllowOverride turned on, then you should add the following lines to the .htaccess file under your webroot folder:
Options +FollowSymLinks
IndexIgnore */*
RewriteEngine on
 
# if a directory or a file exists, use it directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
 
# otherwise forward it to index.php
RewriteRule . index.php

怎么做...

Our website should display the index page at /home and all other pages at /page/<alias_here>. Additionally, /about should lead to a page with alias about.

  1. Add the following to your rules in protected/config/main.php:
'home' => 'website/index',
'<alias:about>' => 'website/page',
'page/<alias>' => 'website/page',
  1. After saving your changes, you should be able to browse the following URLs:
  • /home
  • /about
  • /page/about
  • /page/test

The following screenshot shows part of a page that opens when /about URL is used:

它是如何工作的...

Let's review what was done and why it works. We'll start with the right part of the first rule:

'home' => 'website/index',

What is website/index exactly?

In the Yii application, each controller and its actions have corresponding internal routes. A format for an internal route is moduleID/controllerID/actionID. For example, the actionPage method of WebsiteController corresponds to the website/page route. So, in order to get the controller ID, you should take its name without the Controller postfix and make its first letter lowercased. To get an action ID, you should take action method name without the action prefix and, again, make its first letter lowercased.

Now, what is home?

To understand it in a better way, we need to know, at least perfunctorily, what's happening when we access our application using different URLs.

When we are using /home, URL router checks our rules one by one starting from the top trying to match URL entered with the rule. If the match is found, then the router is getting controller and its action from an internal route assigned to the rule and is executing it. So, /home is the URL pattern that defines which URLs will be processed by the rule it belongs to.

The fewer rules you have, the fewer checks are needed if URL does not match. Less URLs means more performance.

还有更多...

You can create parameterized rules using a special syntax. Let's review the third rule:

'page/<alias>' => 'website/page',

Here, we are defining an alias parameter that should be specified in URL after /page/. It can be virtually anything and it will be passed as $alias parameter to WebsiteController::actionPage($alias).

You can define a pattern for such a parameter. We did it for the second rule:

'<alias:about>' => 'website/page',

Alias here should match about or else, the rule will not be applied.

进一步阅读

For further information, refer to the following URLs:

  • http://www.yiiframework.com/doc/guide/en/basics.controller
  • http://www.yiiframework.com/doc/guide/en/topics.url
  • http://www.yiiframework.com/doc/api/1.1/CUrlManager

另请参阅

  • The recipe named Generating URLs by path in this chapter
  • The recipe named Using regular expressions in URL rules in this chapter
  • The recipe named Creating URL rules for static pages in this chapter
  • The recipe named Providing your own URL rules at runtime in this chapter

Generating URLs by path

Yii allows you not only to route your URLs to different controller actions but also to generate a URL by specifying a proper internal route and its parameters. This is really useful because you can focus on internal routes while developing your application and care about real URLs only before going live.

Never specify URLs directly and use the Yii URL toolset. It will allow you to change URLs without rewriting a lot of application code.

准备工作

  1. Create a fresh Yii application using yiic webapp as described in the official guide and find your protected/config/main.php. Replace rules array as follows:
// application components
'components'=>array(
    ...
    // uncomment the following to enable URLs in path-format
    /*
    'urlManager'=>array(
        'urlFormat'=>'path',
        'rules'=>array(
            '<alias:about>' => 'website/page',
            'page/about/<alias:authors>' => 'website/page',
            'page/<alias>' => 'website/page',
        ),
  1. In your protected/controllers, create WebsiteController with the following code inside:
class WebsiteController extends CController
{
    public function actionIndex()
    {
        echo "index";
    }
    
    public function actionPage($alias)
    {
        echo "Page is $alias.";
    }
}

This is our application controller that we are going to generate custom URLs for.

  1. Configure your application server to use clean URLs. If you are using Apache with mod_rewrite and AllowOverride turned on, then you should add the following lines to the .htaccess file under your webroot folder:
Options +FollowSymLinks
IndexIgnore */*
RewriteEngine on
 
# if a directory or a file exists, use it directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
 
# otherwise forward it to index.php
RewriteRule . index.php

怎么做...

We need to generate URLs pointing to index and page actions of WebsiteController. Depending on where we need it, there are different ways for doing it, but the basics are the same. Let's list some methods that generate URLs.

CHtml::link() and some other CHtml methods such as form, refresh, and ajaxLink all accept URLs and are typically used in views. These are using CHtml::normalizeUrl internally to resolve internal routes. Therefore, you should pass data in one of the following formats:

  • URL string: In this case, URL passed will be used as is.
  • array(internal route, param => value, param => value, ...). In this case, URL will be generated.

What is internal route? Each controller and its actions have corresponding routes. A format for a route is moduleID/controllerID/actionID. For example, actionPage method of WebsiteController corresponds to website/page route. To get a controller ID, you should take its name without Controller postfix and make its first letter lowercased. To get an action ID, you should take action method name without action prefix and, again, make its first letter lowercased.

Parameters are $_GET variables that will be passed to an action with internal route specified. For example, if we want to create a URL to WebsiteController::actionIndex that passes $_GET['name'] parameter to it, it can be done like this:

echo CHtml::link('Click me!', array('website/index', 'name' => 'Qiang'));

URLs are also helpful when using controller. Inside the controller, you can use createUrl and createAbsoluteUrl to get both relative and absolute URLs:

class WebsiteController extends CController
{
    public function actionTest()
    {
        echo $this->createUrl('website/page', 'alias' => 'about');
        echo $this->createAbsoluteUrl('website/page', 'alias' => 'test');
    }
    
    // the rest of the methods
}

As we have URL rules defined in the router configuration, we will get the following URLs:

  • /about
  • http://example.com/about

Relative URLs can be used inside your application while absolute ones should be used for pointing to locations outside of your website (like other websites) or for linking to resources meant to be accessed from outside (RSS feeds, e-mails, and so on).

When you cannot get controller instance, for example, when you implement a console application, you can use application's methods:

echo Yii::app()->createUrl('website/page', 'alias' => 'about');
echo Yii::app()->createAbsoluteUrl('website/page', 'alias' => 'test');

The difference is that when using controller-specific methods, you can omit both controller and module names. In this case, the current module name and the current controller name are used:

class MyController extends CController
{
    public function actionIndex()
    {
        // As we're inside of controller, createUrl will assume that URL
        // is for current controller
        echo $this->createUrl('index');
    }
}

它是如何工作的...

All URL building tools we have reviewed are internally using the CWebApplication:: createUrl method that is calling CUrlManager::createUrl. It tries to apply routing rules one by one starting from the top. If no rules are matched, then the default URL form is generated.

还有更多...

For further information, refer to the following URLs:

  • http://www.yiiframework.com/doc/guide/en/basics.controller
  • http://www.yiiframework.com/doc/guide/en/topics.url
  • http://www.yiiframework.com/doc/api/CUrlManager
  • http://www.yiiframework.com/doc/api/CHtml/#normalizeUrl-detail
  • http://www.yiiframework.com/doc/api/CHtml/#link-detail
  • http://www.yiiframework.com/doc/api/CController/#createUrl-detail
  • http://www.yiiframework.com/doc/api/CWebApplication/#createUrl-detail

另请参阅

  • The recipe named Configuring URL rules in this chapter
  • The recipe named Using regular expressions in URL rules in this chapter
  • The recipe named Creating URL rules for static pages in this chapter
  • The recipe named Providing your own URL rules at runtime in this chapter

Using regular expressions in URL rules

One of the "hidden" features of Yii URL router is that you can use regular expressions that are pretty powerful when it comes to strings handling.

准备工作

  1. Create a fresh Yii application using yiic webapp as described in the official guide and find your protected/config/main.php. It should contain the following:
// application components
'components'=>array(
    ...
    // uncomment the following to enable URLs in path-format
    /*
    'urlManager'=>array(
        'urlFormat'=>'path',
        'rules'=>array(
            '<controller:\w+>/<id:\d+>'=>'<controller>/view',
            '<controller:\w+>/<action:\w+>/<id:\d+>'=>'<controller>/<action>',
            '<controller:\w+>/<action:\w+>'=>'<controller>/<action>',
        ),
    ),
  1. Delete everything from rules as we are going to start from scratch.
  2. In your protected/controllers, create PostController.php with the following code inside:
class PostController extends CController
{
    public function actionView($alias)
    {
        echo "Showing post with alias $alias.";
    }
    
    public function actionIndex($order = 'DESC')
    {
        echo "Showing posts ordered $order.";
    }
    
    public function actionHello($name)
    {
        echo "Hello, $name!";
    }
}

This is our application controller we are going to access using our custom URLs.

  1. Configure your application server to use clean URLs. If you are using Apache with mod_rewrite and AllowOverride turned on, then you should add the following lines to the .htaccess file under your webroot folder:
Options +FollowSymLinks
IndexIgnore */*
RewriteEngine on
 
# if a directory or a file exists, use it directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
 
# otherwise forward it to index.php
RewriteRule . index.php

怎么做...

We want our PostController actions to accept parameters according to some rules and give "404 not found" HTTP response for all parameters that do not match. In addition, post index should have an alias URL archive.

Let's use regular expressions to achieve it:

'post/<alias:[-a-z]+>' => 'post/view',
'(posts|archive)' => 'post/index',
'(posts|archive)/<order:(DESC|ASC)>' => 'post/index',
'sayhello/<name>' => 'post/hello',

Now, you can try the following URLs:

// success
http://example.com/post/test-post
// fail
http://example.com/post/another_post
// success
http://example.com/posts
// success
http://example.com/archive
// fail
http://example.com/archive/test
// success
http://example.com/posts/ASC
// success

The following screenshot shows that the URL http://example.com/post/test-post has run successfully:

The following screenshot shows that the URL http://example.com/archive/test did not run successfully and encountered an error:

它是如何工作的...

You can use regular expressions in both parameter definition and the rest of the rule. Let's read our rules one by one.

'post/<alias:[-a-z]+>' => 'post/view',

Alias parameter should contain one or more English letter or a dash. No other symbols are allowed.

'(posts|archive)' => 'post/index',

Both posts and archive are leading to post/index.

'(posts|archive)/<order:(DESC|ASC)>' => 'post/index',

Both posts and archive are leading to post/index. Order parameter can only accept two values: DESC and ASC.

'sayhello/<name>' => 'post/hello',

You should specify the name part but there are no restrictions on what characters are allowed.

Note that regardless of the rule used, the developer should never assume that input data is safe.

还有更多...

To learn more about regular expressions, you can use the following sources:

  • http://www.php.net/manual/en/reference.pcre.pattern.syntax.php
  • Mastering Regular Expressions, by Jeffrey Friedl (http://regex.info/)

另请参阅

  • The recipe named Configuring URL rules in this chapter
  • The recipe named Creating URL rules for static pages in this chapter

Creating URL rules for static pages

A website typically contains some static pages. Usually, they are /about, /contact, /tos, and so on, and it is common to handle these pages in a single controller action. Let's find a way to create URL rules for these types of pages.

准备工作

  1. Create a fresh Yii application using yiic webapp as described in the official guide and find your protected/config/main.php. It should contain the following:
// application components
'components'=>array(
    ...
    // uncomment the following to enable URLs in path-format
    /*
    'urlManager'=>array(
        'urlFormat'=>'path',
        'rules'=>array(
            '<controller:\w+>/<id:\d+>'=>'<controller>/view',
            '<controller:\w+>/<action:\w+>/<id:\d+>'=>'<controller>/<action>',
        '<controller:\w+>/<action:\w+>'=>'<controller>/<action>',
    ),
),
  1. Delete everything from rules as we are going to start from scratch.
  2. In your protected/controllers, create WebsiteController with the following code:

class WebsiteController extends CController { public function actionPage($alias) { echo "Page is $alias."; } }

  1. Configure your application server to use clean URLs. If you are using Apache with mod_rewrite and AllowOverride turned on you should add the following lines to the .htaccess file under your webroot folder:
Options +FollowSymLinks
IndexIgnore */*
RewriteEngine on
 
# if a directory or a file exists, use it directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
 
# otherwise forward it to index.php
RewriteRule . index.php

怎么做...

The most straightforward way is defining a rule for each page:

'<alias:about>' => 'website/page',
'<alias:contact>' => 'website/page',
'<alias:tos>' => 'website/page',

Using regular expressions, we can compact it to a single rule:

'<alias:(about|contact|tos)>' => 'website/page',

Now, what if we want the URL to be /tos and an alias parameter to be terms_of_service?

No problem, we can use default parameters to achieve it:

'tos' => array('website/page', 'defaultParams' => array('alias' =>
    'terms_of_service')),

OK. What if we have many pages and want to be able to dynamically create pages without adding more rules or changing existing ones?

We can achieve this with the following rule:

'<alias>' => 'website/page'

As this rule matches everything, we need to place it last, so it won't affect all other rules. In addition, default rules with one slug, such as controller name will stop working. To overcome this issue, we need to add default rules which we deleted in the Getting ready section of this recipe.

它是如何工作的...

Let's read rules we just wrote.

'<alias:about>' => 'website/page',

If the URL is /about, then pass it as the alias parameter to website/page.

'<alias:(about|contact|tos)>' => 'website/page',

If the URL is /about or /contact or /tos, then pass it as the alias parameter to website/page.

'tos' => array('website/page', 'defaultParams' => array('alias' =>
    'terms_of_service')),

When the URL is /tos, pass terms_of_service as the alias parameter value.

This rule is a bit special because it uses default parameter option. Default parameter allows you to set a value that will be used if parameter with name specified is omitted. When you need to specify an option for the rule, you should use an array notation:

'pattern' => array('internal/route', 'option' => 'value', 'option' =>
    'value', ...),

For a list of options you can set, refer to the following API page:
http://www.yiiframework.com/doc/api/1.1/CUrlRule

另请参阅

  • The recipe named Configuring URL rules in this chapter
  • The recipe named Using regular expressions in URL rules in this chapter

Providing your own URL rules at runtime

When you are developing an application with pluggable module architecture, you most likely need to somehow inject your module-specific rules into an existing application.

准备工作

  1. Set up a new application using yiic webapp.
  2. Add .htaccess, shown in official URL Management guide to your webroot.
  3. Add 'showScriptName' => false to your URL manager configuration.
  4. Generate the page module using Gii.
  5. Don't forget to add your new module to the modules list in your application configuration.

The Yii code generator is shown in the following screenshot:

怎么做...

  1. Create ModuleUrlManager.php in your protected/components directory with the following code inside:
<?php
class ModuleUrlManager
{
    static function collectRules()
    {
        if(!empty(Yii::app()->modules))
        {
            foreach(Yii::app()->modules as $moduleName => $config)
            {
                $module = Yii::app()->getModule($moduleName);
                if(!empty($module->urlRules))
                {
                    Yii::app()->getUrlManager()->addRules($module->urlRules);
                }
            }
        }
        return true;
    }
}
  1. In your application configuration, add the following line:
'onBeginRequest' => array('ModuleUrlManager', 'collectRules'),
  1. Now, in your page module, you can add custom rules. To do so, open PageModule.php and add:
public $urlRules = array(
    'test' => 'page/default/index',
);
  1. To test if it works, open your browser and go to http://example.com/test. This page should look like the one shown in the following screenshot:

This is the view content for action "index". The action belongs to the controller "DefaultController" in the "page" module.

  1. You still can override URL rules from your main application configuration file. So, what you specify in module's urlRules is used only when the main application rules are not matching.

它是如何工作的...

Let's review the ModuleUrlManager::collectRules method.

If there are modules defined in our application, then we are checking if urlRules public property exists. If it does, then there are some rules defined in the module and they are added using CUrlManager::addRules.

CUrlManager::addRules description says "In order to make the new rules effective, this method must be called before CWebApplication::processRequest".

Now, let's check how our application works. In our index.php, we have the following line:

Yii::createWebApplication($config)->run();

After being initialized with configuration, we are calling CWebApplication::run():

public function run()
{
    if($this->hasEventHandler('onBeginRequest'))
        $this->onBeginRequest(new CEvent($this));
    $this->processRequest();
    if($this->hasEventHandler('onEndRequest'))
        $this->onEndRequest(new CEvent($this));
}

As we can see, there is an onBeginRequest event raised just before calling processRequest. That is why we are attaching our class method to it.

还有更多...

As instantiating all application modules on every request is not good for performance, it is good to cache module rules. Caching strategy can vary depending on your application. Let's implement a simple one:

<?php
class ModuleUrlManager
{
    static function collectRules()
    {
        if(!empty(Yii::app()->modules))
        {
            $cache = Yii::app()->getCache();
            
            foreach(Yii::app()->modules as $moduleName => $config)
            {
                $urlRules = false;
                
                if($cache)
                    $urlRules = $cache->get('module.urls.'.$moduleName);
                    
                if($urlRules===false){
                    $urlRules = array();
                    $module = Yii::app()->getModule($moduleName);
  
                    if(isset($module->urlRules))
                        $urlRules = $module->urlRules;
   
                    if($cache)
                        $cache->set('module.urls.'.$moduleName, $urlRules);
                }
                if(!empty($urlRules))
                    Yii::app()->getUrlManager()->addRules($urlRules);
            }
        }
        
        return true;
    }
}

This implementation caches URL rules per module. So, adding new modules is not a problem but changing existing ones requires you to flush cache manually using Yii::app()->cache->flush().

另请参阅

  • The recipe named Configuring URL rules in this chapter

Using base controller

In many frameworks, the concept of a base controller that is being extended by other ones is described right in the guide. In Yii, it is not in the guide as you can achieve flexibility in many other ways. Still, using base controller is possible and can be useful.

准备工作

A new application using yiic webapp is to be set up.

Let's say we want to add some controllers that will be accessible only when the user is logged in. We can surely set this constraint for each controller separately, but we will do it in a better way.

怎么做...

  1. First, we will need a base controller that our user-only controllers will use. Let's create SecureController.php in protected/components with the following code:
<?php
class SecureController extends Controller
{
    public function filters()
    {
        return array(
            'accessControl',
        );
    }
    
    public function accessRules()
    {
        return array(
            array('allow',
                'users'=>array('@'),
            ),
            array('deny',
                'users'=>array('*'),
            ), 
        );
    }
}
  1. Now, go to the Gii controller generator and enter SecureController into the Base Class field. You will get something like this:
class TestController extends SecureController
{
    public function actionIndex()
    {
        $this->render('index');
    }
    ... 
}
  1. Now, your TestController index will be only accessible if the user is logged in, even though we have not declared it explicitly in the TestController class.

它是如何工作的...

The trick is nothing more than a basic class inheritance. If filters or accessRules is not found in TestController, then it will be called from SecureController.

Using external action

In Yii, you can define controller actions as separate classes and then connect them to your controllers. This way, you can reuse some common functionality.

For example, you can move backend for autocomplete fields to an action and save some time by not having to write it over and over again.

Another simple example that we will review is deleting a model.

准备工作

  1. Set up a new application using yiic webapp.
  2. Create a DB schema with the following script:
CREATE TABLE `post` (
    `id` int(10) unsigned NOT NULL auto_increment,
    `created_on` int(11) unsigned NOT NULL,
    `title` varchar(255) NOT NULL,
    `content` text NOT NULL,
    PRIMARY KEY  (`id`)
);
 
CREATE TABLE `user` (
    `id` int(10) unsigned NOT NULL auto_increment,
    `username` varchar(200) NOT NULL,
    `password` char(40) NOT NULL,
    PRIMARY KEY  (`id`)
);
  1. Generate Post and User models using Gii.

怎么做...

  1. Let's write a usual delete action for posts first, as follows:
class PostController extends CController
{
    function actionIndex()
    {
        $posts = Post::model()->findAll();
        $this->render('index', array(
            'posts' => $posts,
        ));
             
    }
    
    function actionDelete($id)
    {
        $post = Post::model()->findByPk($id);
        if(!$post)
            throw new CHttpException(404);
            
        if($post->delete())
            $this->redirect('post/index');
        throw new CHttpException(500);
    }
}

We have defined two actions. One lists all posts and another deletes a post specified if it exists and redirects back to index action.

  1. Now, let's do the same in a separate action class. Create DeleteAction.php in your protected/components directory as follows:
class DeleteAction extends CAction
{
    function run()
    {
        if(empty($_GET['id']))
            throw new CHttpException(404);
            
        $post = Post::model()->findByPk($_GET['id']);
        
        if(!$post)
            throw new CHttpException(404);
            
        if($post->delete())
            $this->redirect('post/index');
 
        throw new CHttpException(500);
    }
}
  1. Let's use it inside our controller. Delete actionDelete, we will not need it anymore. Then, add the actions method:
class PostController extends CController
{
 
    function actions()
    {
        return array(
            'delete' => 'DeleteAction',
        );
    }
    
    ...
}
  1. OK. Now, we are using external delete action for post controller, but what about the user controller? To use our DeleteAction with UserController we need to customize it first. We do this as follows:
class DeleteAction extends CAction
{
    public $pk = 'id';
    public $redirectTo = 'index';
    public $modelClass;
    
    function run()
    {
        if(empty($_GET[$this->pk]))
            throw new CHttpException(404);
            
        $model = CActiveRecord::model($this->modelClass)
            ->findByPk($_GET[$this->pk]);
            
        if(!$model)
            throw new CHttpException(404);
            
        if($model->delete())
            $this->redirect($this->redirectTo);
        throw new CHttpException(500);
    }
}
  1. Now, we can use this action for both post controller and user controller. For post controller, we do this as follows:
class PostController extends CController
{
    function actions()
    {
        return array(
            'delete' => array(
                'class' => 'DeleteAction',
                'modelClass' => 'Post',
            );
        );
    }
    ...
}
  1. For user controller, we do this as follows:
class UserController extends CController
{
    function actions()
    {
        return array(
            'delete' => array(
                'class' => 'DeleteAction',
                'modelClass' => 'User',
            );
        );
    }
    ...
}
  1. This way, you can save yourself a lot of time by implementing and reusing external actions for tasks of a similar type.

它是如何工作的...

Every controller can be built from external actions like a puzzle from pieces. The difference is that you can make external actions very flexible and reuse them in many places. In the final version of DeleteAction, we defined some public properties. As DeleteAction is a component, we can set its properties through config. In our case, we pass config into the actions controller method used to add actions to a module.

还有更多...

For further information, refer to the following URLs:

  • http://www.yiiframework.com/doc/api/CAction/
  • http://www.yiiframework.com/doc/api/CController#actions-detail

Displaying static pages with CViewAction

If you have a few static pages and aren't going to change them very frequently, then it's not worth querying database and implementing a page management for them.

准备工作

Set up a new application using yiic webapp.

怎么做...

  1. We just need to connect CViewAction to our controller.
class SiteController extends CController
{
    function actions()
    {
        return array(
            'page'=>array(
                'class'=>'CViewAction',
            ),
        );
    }
}class SiteController extends CController
{
    function actions()
    {
        return array(
            'page'=>array(
                'class'=>'CViewAction',
            ),
        );
    }
}
  1. Now, put your pages into protected/views/site/pages. Name them about.php and contact.php.
  2. Now, you can try your pages by typing:

http://example.com/index.php?r=site/page&view=contact

Alternatively, you can type the following:

http://example.com/site/page/view/about

If you have configured clean URLs with path format.

它是如何工作的...

We are connecting external action named CViewAction that simply tries to find a view named the same as the $_GET parameter supplied. If it is there, it displays it. If not, then it will give you 404 Not found page.

还有更多...

There are some useful CViewAction parameters we can use. These are listed in the following table:

Parameter nameDescription
basePathIt is a base path alias that is prepended to a view name. Default is pages. That means a page named faq.company will be translated to protected/views/pages/faq/company.php.
defaultViewIt is a name of a page to render when there is no $_GET parameter supplied. Default is index.
layoutLayout used to render a page. By default, controller layout is used. If it is set to null, then no layout is applied.
renderAsTextIf set to true, then the page will be rendered as is. Else, PHP inside will be executed.
viewParamThe name of the $_GET parameter used to pass page name to CViewAction. Default is view.
进一步阅读

For further information, refer to the following URL:

http://www.yiiframework.com/doc/api/CViewAction

另请参阅

  • The recipe named Using external actions in this chapter

Using flash messages

When you are editing a model with a form, when you are deleting a model, or doing any other operation, it is good to tell users if it went fine or if there was an error. Typically, after some kind of action, such as editing a form, a redirect will happen and we need to display a message on the page we want to go to. However, how to pass it from the current page to the redirect target and clean afterwards? Flash messages will help us.

准备工作

Set up a new application using yiic webapp.

怎么做...

  1. Let's create a protected/controllers/WebsiteController.php controller as follows:
class WebsiteController extends CController
{
    function actionOk()
    {
        Yii::app()->user->setFlash('success', 'Everything went fine!');
        $this->redirect('index');
    }
    
    function actionBad()
    {
        Yii::app()->user->setFlash('error', 'Everything went wrong!');
        $this->redirect('index');
    }
    
    function actionIndex()
    {
        $this->render('index');
    }
}
  1. Additionally, create the protected/views/website/index.php view as follows:
<?php if(Yii::app()->user->hasFlash('success')):?>
<div class="flash-notice">
    <?php echo Yii::app()->user->getFlash('success')?>
</div>
<?php endif?>
<?php if(Yii::app()->user->hasFlash('error')):?>
<div class="flash-error">
    <?php echo Yii::app()->user->getFlash('error')?>
</div>
<?php endif?>
<li>Now, if we go to http://example.com/website/ok, we'll be redirected to http://example.com/website/index and a success message will be displayed. Moreover, if we go to http://example.com/website/bad, we will be redirected to the same page, but with an error message. Refreshing the index page will hide
the message.</li>

它是如何工作的...

We are setting a flash message with Yii::app()->user->setFlash('success', 'Everything went fine!'), for example, calling CWebUser::setFlash. Internally, it is saving a message into a user state, so in the lowest level, our message is being kept in $_SESSION until Yii::app()->user->getFlash('success') is called and the $_SESSION key is deleted.

还有更多...

The following URL contains an API reference of CWebUser and will help you to understand flash messages better:

http://www.yiiframework.com/doc/api/CWebUser

Using controller context in a view

Yii views are pretty powerful and have many features. One of them is that you can use controller context in a view. So, let's try it.

准备工作

Set up a new application using yiic webapp.

怎么做...

  1. Create a controller as follows:
class WebsiteController extends CController
{
    function actionIndex()
    {
        $this->pageTitle = 'Controller context test';
        $this->render('index');
    }
    
    function hello()
    {
        if(!empty($_GET['name']))
            echo 'Hello, '.$_GET['name'].'!';
    }
}
  1. Now, we will create a view showing what we can do:
<h1><?php echo $this->pageTitle?></h1>
<p>Hello call. <?php $this->hello()?></p>
<?php $this->widget('zii.widgets.CMenu',array(
'items'=>array(
    array('label'=>'Home', 'url'=>array('index')),
    array('label'=>'Yiiframework home',
        'url'=>'http://yiiframework.ru/',
    ),
))?>

它是如何工作的...

We are using $this in a view to refer to a currently running controller. When doing it, we can call a controller method and access its properties. The most useful property is pageTitle which refers to the current page title and there are many built-in methods that are extremely useful in views such as renderPartials and widget.

还有更多...

The following URL contains API documentation for CController where you can get a good list of methods you can use in your view:

http://www.yiiframework.com/doc/api/CController

Reusing views with partials

Yii supports partials, so if you have a block without much logic that you want to reuse or want to implement e-mail templates, partials are the right way to look.

准备工作

  1. Set up a new application using yiic webapp.
  2. Create a WebsiteController as follows:
class WebsiteController extends CController
{
    function actionIndex()
    {
        $this->render('index');
    }
}

怎么做...

We will start with a reusable block. For example, we need to embed a YouTube video at several website pages. Let's implement a reusable template for it.

  1. Create a view file named protected/views/common/youtube.php and paste an embed code from YouTube. You will get something like:
<object width="480" height="385"><param name="movie"
value="http://www.youtube.com/v/S6u7ylr0zIg?fs=1 "></
param><param name="allowFullScreen" value="true"></
param><param name="allowscriptaccess" value="always"></
param><embed src="http://www.youtube.com/v/S6u7ylr0zIg?fs=1"
type="application/x-shockwave-flash" allowscriptaccess="always"
allowfullscreen="true" width="480" height="385"></embed></object>
  1. Now, we need to make it reusable. We want to be able to set video ID, width, and height. Let's make width and height optional, as follows:
<object width="<?php echo!empty($width) ? $width : 480?>"
    height="<?php echo!empty($height) ? $height: 385?>">
    <param name="movie" value="http://www.youtube.com/v/<?php echo $id?>?fs=1 ">
        </param>
    <param name="allowFullScreen" value="true"></param>
    <param name="allowscriptaccess" value="always"></param>
    <embed src="http://www.youtube.com/v/<?php echo $id?>?fs=1"
        type="application/x-shockwave-flash" allowscriptaccess="always"
        allowfullscreen="true" width="<?php echo !empty($width) ? $width
       : 480?>" height="<?php echo !empty($height) ? $height: 385?>">
    </embed>
</object>
  1. Now, you can use it in your protected/views/website/index.php like this:
<?php $this->renderPartial('////common/youtube', array(
    'id' => '8Rp-CaIKvQs', // you can get this id by simply looking
    'width' => 320,
    'height' => 256,
))?>

Looks better, right? Note that we have used // to reference a view. This means that Yii will look for a view starting from protected/views not taking controller name into account.

  1. Now, let's send some e-mails. As we are unable to write unique letters to thousands of users, we will use a template but will make it customized. Let's add a new method to protected/controllers/WebsiteController.php as follows:
class WebsiteController extends CController
{
    function actionSendmails()
    {
        $users = User::model->findAll();
        foreach($users as $user)
        {
            $this->sendEmail('welcome', $user->email, 'Welcome to the
                website!', array('user' => $user));
        }
        echo 'Emails were sent.';
    }
    
    function sendEmail($template, $to, $subject, $data)
    {
        mail($to, $subject, $this->renderPartial
            ('//email/'.$template, $data, true));
    }
}
  1. Here is our template protected/views/email/welcome.php:
Hello <?php echo $user->name?>,
 
Welcome to the website!
 
You can go check our new videos section. There are funny raccoons.
 
Yours,
Website team.

它是如何工作的...

CController::renderPartial does the same template processing as CController::render except the former does not use layout. As we can access current controller in a view using $this, we can use its renderPartial to use view within another view. renderPartial is also useful when dealing with AJAX as you don't need layout rendered in this case.

还有更多...

For further information, refer to the following URL:

http://www.yiiframework.com/doc/api/CController/#renderPartial-detail

另请参阅

  • The recipe named Using controller context in a view in this chapter

Using clips

One of the Yii features you can use in your views is clips. The basic idea is that you can record some output and then reuse it later in a view. A good example will be defining additional content regions for your layout and filling them elsewhere.

准备工作

Set up a new application using yiic webapp.

怎么做...

  1. For our example, we need to define two regions in our layout: beforeContent and footer. Open protected/views/layouts/main.php and insert the following just before the content output (<?php echo $content; ?>):
<?php if(!empty($this->clips['beforeContent'])) echo
    $this->clips['beforeContent']?>

Then, insert the following into <div id="footer">:

<?php if(!empty($this->clips['footer'])) echo
    $this->clips['footer']?>
  1. That is it. Now, we need to fill these regions somehow. We will use a controller action for the beforeContent region. Open protected/controllers/ SiteController.php and add the following to actionIndex:
$this->beginClip('beforeContent');
echo 'Your IP is '.Yii::app()->request->userHostAddress;
$this->endClip();
  1. As for footer, we will set its content from a view. Open protected/views/site/ index.php and add the following:
<?php $this->beginClip('footer')?>
This application was built with Yii.
<?php $this->endClip()?>
  1. Now, when you open your website's index page, you should get your IP just before the page content and "built with" note in the footer.

它是如何工作的...

We mark regions with the code that just checks for existence of a clip specified and, if clip exists, the code outputs it. Then, we record content for clips we defined using special controller methods named beginClip and endClip.

See alse

  • The recipe named Using controller context in a view in this chapter

Using decorators

In Yii, we can enclose content into a decorator. The common usage of decorators is layout. Yes, when you are rendering a view using the render method of your controller, Yii automatically decorates it with the main layout. Let's create a simple decorator that will properly format quotes.

准备工作

Set up a new application using yiic webapp.

怎么做...

  1. First, we will create a decorator file protected/views/decorators/quote.php:
<div class="quote">
    &ldquo;<?php echo $content?>&rdquo;, <?php echo $author?>
</div>
  1. Now in protected/views/site/index.php, we will use our decorator:
<?php $this->beginContent('//decorators/quote', array('author' => 'Edward A. Murphy'))?>
If anything bad can happen, it probably will
<?php $this->endContent()?>
  1. Now, your homepage should include the following markup:
<div class="quote">
    &ldquo;If anything bad can happen, it probably will&rdquo;, Edward A. Murphy
</div>

它是如何工作的...

Decorators are pretty simple. Everything between beginContent and endContent is rendered into a $content variable and passed into a decorator template. Then, the decorator template is rendered and inserted in the place where endContent was called. We can pass additional variables into decorator using a second parameter of beginContent, such as the one we did for the author.

Note that we have used //decorators/quote as view path. This means that the view will be searched starting from either theme views root or application views root.

还有更多...

The following URL provides more details about decorators:

http://www.yiiframework.com/doc/api/CContentDecorator/

另请参阅

  • The recipe named Defining multiple layouts in this chapter
  • The recipe named Using controller context in a view in this chapter

Defining multiple layouts

Most applications use a single layout for all their views. However, there are situations when multiple layouts are needed. For example, an application can use different layouts at different pages: Two additional columns for blog, one additional column for articles, and no additional columns for portfolio.

准备工作

Set up a new application using yiic webapp.

怎么做...

  1. Create two layouts in protected/views/layouts: blog and articles. Blog will contain the following code:
<?php $this->beginContent('//layouts/main')?>
<div>
    <?php echo $content?>
</div>
<div class="sidebar tags">
    <ul>
        <li><a href="#php">PHP</a></li>
        <li><a href="#yii">Yii</a></li>
    </ul>
</div>
 
<div class="sidebar links">
    <ul>
        <li><a href="http://yiiframework.com/">Yiiframework</a></li>
        <li><a href="http://php.net/">PHP</a></li>
    </ul>
</div>
<?php $this->endContent()?>
  1. Articles will contain the following code:
<?php $this->beginContent('//layouts/main')?>
<div>
<?php echo $content?>
</div>
<div class="sidebar toc">
    <ul>
        <li><a href="#intro">1. Introduction</a></li>
        <li><a href="#quick-start">2. Quick start</a></li>
    </ul>
</div>
<?php $this->endContent()?>
  1. Create three controllers named BlogController, ArticleController, and PortfolioController with index actions in both:
class BlogController extends Controller
{
    function actionIndex()
    {
        $this->layout = 'blog';
        $this->render('//site/index');
    }
}
 
class ArticleController extends Controller
{
    function actionIndex()
    {
        $this->layout = 'articles';
        $this->render('//site/index');
   }
}
 
class PortfolioController extends Controller
{
    function actionIndex()
    {
        $this->render('//site/index');
    }
}
  1. Now try http://example.com/blog, http://example.com/article, and http://example.com/portfolio.

它是如何工作的...

We defined two additional layouts for blog and articles. As we don't want to copy-paste common parts from the main layout, we apply additional layout decorators using $this->beginContent and $this->endContent, as shown on the following diagram:

So, we are using a view rendered inside articles layout as main's $content.

另请参阅

  • The recipe named Using controller context in a view in this chapter
  • The recipe named Using decorators in this chapter

Paginating and sorting data

In latest Yii releases, accent was moved from using Active Record directly to grids, lists, and data providers. Still, sometimes it is better to use active record directly. Let's see how to list paginated AR records with ability to sort them.

准备工作

  1. Setup a new application using yiic webapp.
  2. Create a database structure table post with id and title fields, add 10—20 records.
  3. Generate Post model using Gii.

怎么做...

  1. First, you need to create protected/controllers/PostController.php:
class PostController extends Controller
{
    function actionIndex()
    {
        $criteria = new CDbCriteria();
        $count=Post::model()->count($criteria);
        $pages=new CPagination($count);
        
        // elements per page
        $pages->pageSize=5;
        $pages->applyLimit($criteria);
        
        // sorting
        $sort = new CSort('Post');
        $sort->attributes = array('id', 'title',);
        $sort->applyOrder($criteria);
    
        $models = Post::model()->findAll($criteria);
        
        $this->render('index', array(
            'models' => $models,
            'pages' => $pages,
            'sort' => $sort,
        ));
    }
}
  1. Now, let's implement protected/views/post/index.php as follows:
<p><?php echo $sort->link('id')?></p>
<p><?php echo $sort->link('title')?></p>
<ol>
<?php foreach($models as $model):?>
    <li>
        <h2><?php echo $model->id?> - <?php echo $model->title?></h2>
    </li>
<?php endforeach?>
</ol>
 
<?php $this->widget('CLinkPager', array(
    'pages' => $pages,
))?>
  1. Try to load http://example.com/post. You should get a working pagination and links that allow sorting list by ID or by title.

它是如何工作的...

First, we get total models count and initialize new pagination component instance with it. Then, we use the applyLimit method to apply limit and offset to criteria we have used for count request. After that, we create sorter instance for the model, specifying model attributes we want to sort by and applying order conditions to criteria by calling applyOrder. Then, we pass modified criteria to findAll. At this step, we have models list, pages, data used for link pager, and sorter that we use to generate sorting links.

In a view, we are using data we gathered. First, we are generating links with CSort::link method. Then, we are listing models. Finally, using CLinkPager widgets we are rendering pagination control.

还有更多...

Visit the following links to get more information about pagination and sorting:

  • http://www.yiiframework.com/doc/api/CPagination/
  • http://www.yiiframework.com/doc/api/CSort/
评论 X

      友荐云推荐
      Copyright 2011-2014. YiiBook.com