Controller ( Yii Framework )

在mvc的controller,主要是處理邏輯部份,會依照不同的path、參數、method…等,決定要處理的資料,以及要回傳什麼資訊給user。在Yii的controller都繼承至CController,需要導向哪個action,會在controller裡面設定完成。

  1. Route
  2. Action
  3. Filter

Route

在Yii裡的程式進入點,都是透過project/index.php這一隻程式,在根據webApp的confifg,決定預設執行哪支controller,以及在project/protected/controllers對應的path。

例如在project/index.php裡,會指定使用哪個config:

1
2
3
$config=dirname(__FILE__).'/protected/config/main.php';

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

然後在main.php的config中,可以透過defaultController指定預設controller:

1
2
3
4
return Array(
'defaultController'=>'post',
//doSomething
);

意思就是當path為根目錄時,會導向至PostController,例如url為:

1
http://localhost/

與下面url相同,會藉由r這個參數對照contoller/action(當action未指定時,預設執行actionIndex這function,也可透過defaultAction自訂):

1
http://localhost/index.php?r=post/index

對應的project/protected/controllers/PostController

1
2
3
4
5
6
class PostController extends CController {

public function actionIndex(){
//doSomething...
}
}

如果想自訂controller對應class的路徑,可使用controllerMap


自訂url對應controller和action

使用自訂的url規則,去對應controller和action,可以修改webapp的config:

1
2
3
4
5
6
7
8
9
10
11
return array(
'urlManager'=>array(
'urlFormat'=>'path',
'rules'=>array(
'post/<id:\d+>/<title:.*?>'=>'post/view',
'posts/<tag:.*?>'=>'post/index',
'<controller:\w+>/<action:\w+>'=>'<controller>/<action>',
),
),
//doSomething...
);

nginx的config設定,將除了靜態檔案以外的request,全部導向至index.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
server {

listen 80 default;
server_name localhost;

access_log /var/log/nginx/localhost.access.log;

location ~* \.(js|png|jpg|gif|css|html)$ {

root /home/user/example/yii_example/yii/demos/blog;
}

location / {

index index.php index.html index.htm;
rewrite ^(.*) /index.php last;
}

location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
#fastcgi_param SCRIPT_FILENAME /tmp$fastcgi_script_name;
fastcgi_param SCRIPT_FILENAME /home/user/example/yii_example/yii/demos/blog$fastcgi_script_name;
include fastcgi_params;
}
}

Action

yii根據path導向至不同的controller,在透過controller指派action去處理。一般來說action可以直接寫在controller裡,也可以透過繼承CAction的方式,在controller定義此class。

在controller裡面定義action,有兩種方式:

  • 只需要讓function名稱的開頭為action,在加上action的名稱即可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class PostController extends CController {

    /**
    * path為
    /index.php?r=post
    /index.php?r=post/index
    **/
    public function actionIndex(){

    }

    /**
    * path為
    /index.php?r=post/create
    **/
    public function actionCreate(){

    }
    }
  • 透過actions這個function,回傳定義好的config,對應不同CAction

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class PostController extends CController {

    public function actions()
    {
    return array(
    // page action renders "static" pages stored under 'protected/views/site/pages'
    // They can be accessed via: index.php?r=site/page&view=FileName
    'page'=>array(
    'class'=>'CViewAction',
    ),
    // file path under 'protected/controllers/post/SayHelloAction'
    'sayhello'=>array(
    'class'=>'application.controllers.post.SayHelloAction',
    ),
    );
    }
    }

CAction

繼承至CAction的方式,如下:

1
2
3
4
5
6
7
class SayHelloAction extends CAction {

public function run()
{
//doSomething...
}
}

yii也提供一些現成的action可以使用,像是:

  • CCaptchaAction
    自動產生驗證碼,需要安裝php5-gd。

  • CViewAction
    可透過view這個參數,導向到不同頁面,而不需要針對每個頁面寫一個action。

Filter

yii的filter與action在controller的配置方式差不多,同樣可以在controller自訂filter的function,也可以class的方式繼承CFilter

在controller寫filter方式:

1
2
3
4
public function filterAccessControl($filterChain)
{
// call $filterChain->run() to continue filter and action execution
}

如果呼叫$filterChain->run(),將會繼續往下執行下個filter和action,反則會中斷掉。

與action在controller宣告不同的是,filter的套用,必須在controller裡定義filters這個function,回傳定義的config:

1
2
3
4
5
6
public function filters(){

return array(
"accessControl",
);
}

上面這種方式,將會套用此controller的所有action,若要套用個別action,如下:

1
2
3
4
5
6
7
public function filters(){

//將filter套用至edit和create action
return array(
"accessControl + edit, create",
);
}

如果把+替換成-,指的就是除了edit和create這兩個不套用filter,剩下全部套用。

CFilter

CFilter將會透過preFilter這個function,決定要繼續向下執行或者停止,而postFilter則是會在preFilter向下執行時,才會去呼叫。

定義一個filter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class PerformanceFilter extends CFilter
{

public $unit;

protected function preFilter($filterChain)
{
// logic being applied before the action is executed
//print $unit;
return true; // false if the action should not be executed
}

protected function postFilter($filterChain)
{
// logic being applied after the action is executed
}
}

在controller中使用PerformanceFilter:

1
2
3
4
5
6
7
8
9
10
11
12
13
class PostController extends CController
{
......
public function filters()
{
return array(
array(
'application.filters.PerformanceFilter - edit, create',
'unit'=>'second',
),
);
}
}

filter第二個傳入參數unit,將會設定PerformanceFilter的unit值。所以除了第一個參數,會設定filter,之後的參數都會對應filter的public屬性。

Getting Started with the Yii Framework

REQUIREMENTS

實際需要使用到的:

文章中需要使用到:

INSTALL

在終端機上執行以下command:

1
2
sudo apt-get update
sudo apt-get install php5-cgi php5-fpm php5-sqlite php5-mysql nginx git

QUICK START

抓取yii的source:

1
git clone https://github.com/yiisoft/yii.git

建立一個webapp:

1
2
mkdir webapp
./yii/framework/yiic webapp webapp/

建立完後的目錄結構:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
.
├── assets
├── css
│   ├── bg.gif
│   ├── form.css
│   ├── ie.css
│   ├── main.css
│   ├── print.css
│   └── screen.css
├── images
├── index.php
├── index-test.php
├── protected
│   ├── commands
│   ├── components
│   ├── config
│   ├── controllers
│   ├── data
│   ├── extensions
│   ├── messages
│   ├── migrations
│   ├── models
│   ├── runtime
│   ├── tests
│   ├── vendor
│   ├── views
│   ├── yiic
│   ├── yiic.bat
│   └── yiic.php
└── themes
└── classic

其中assets、runtime和data,必須要讓www-data(nginx、apache)有讀、寫和執行的功能,可使用設定群組的方式,或者執行chmod -R 777 assets protected/runtime protected/data

打開nginx config:

1
sudo vi /etc/nginx/sites-enabled/default

修改以下這兩個location,設定你的webapp位置和設定fastcgi連接到php-fpm:

1
2
3
4
5
6
7
8
9
10
11
12
location / { 
root /tmp/webapp;
index index.html index.htm index.php;
}

location ~ \.php$ {
#fastcgi_pass 127.0.0.1:9000;
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /tmp/webapp$fastcgi_script_name;
include /etc/nginx/fastcgi_params;
}

如果使用的是nginx和php-fpm,請檢查php-fpm的listen設定,對應上面的fastcgi_pass。檔案位置在/etc/php5/fpm/pool.d/www.conf

設定完之後,重開nginx:

1
2
3
4
sudo /etc/init.d/nginx restart

#若是php-fpm的config的有被修改到,需重啟fpm
sudo /etc/init.d/php5-fpm restart

在瀏覽上打開http://localhost/,就可以看到剛剛佈署完的結果了。

Zend framework page cache

初始projectt為例,可直接至application.ini配置cache的參數。

;關閉預設buffer
resources.frontController.params.disableOutputBuffering = true

resources.cachemanager.page.frontend.name = Page
;cache存活時間      
resources.cachemanager.page.frontend.options.lifetime = 5
;debug模式,會在header最上方顯示提示
resources.cachemanager.page.frontend.options.debug_header = true
;儲存的容器
resources.cachemanager.page.backend.name = File
;設定存放位置
resources.cachemanager.page.backend.options.cache_dir = APPLICATION_PATH "/cache"

;如果不一定要在自身project底下建立cache的目錄
;可直接導向到/tmp即可,以下的權限修改跟folder建立都可以省略
;/tmp底下檔案,當電腦關閉時,將會自動清除
;resources.cachemanager.page.backend.options.cache_dir = "/tmp"

建立cache folder和修改權限:

cd newproject/application

mkdir cache

sudo chown www-data:www-data cache

在controller init的加入要cache的action:

// 註冊 Cache Action Helper
$this->_helper->addHelper(new Zend_Controller_Action_Helper_Cache());

$this->_helper->cache(array(
  'index', // 指定要快取的 Action
));

以IndexController為例如下:

class IndexController extends Zend_Controller_Action      
{

    public function init()      
    {     
        /* Initialize action controller here */   

        // 註冊 Cache Action Helper                 
        $this->_helper->addHelper(new Zend_Controller_Action_Helper_Cache());

        $this->_helper->cache(array(
            'index', // 指定要快取的 Action
        ));

    }

    public function indexAction()
    {              
        // action body
    }
}

當使用debug mode的時候,就會在頁面上出現"DEBUG HEADER : This is a cached page !"的字樣,表示目前是使用cache。

Installation zend framework

官方下載(以1.12.3 version為例),然後解壓縮:

wget https://packages.zendframework.com/releases/ZendFramework-1.12.3/ZendFramework-1.12.3.zip

unzip ZendFramework-1.12.3.zip

建置project和copy library:

./ZendFramework-1.12.3/bin/zf.sh project newproject

cp -r ./ZendFramework-1.12.3/library ./newproject/library

會產生以下檔案及目錄:

.
|-- application
|   |-- Bootstrap.php
|   |-- configs
|   |-- controllers
|   |-- models
|   `-- views
|-- docs
|   `-- README.txt
|-- library
|   `-- Zend
|-- public
|   `-- index.php
`-- tests
    |-- application
    |-- bootstrap.php
    |-- library
    `-- phpunit.xml

最後在配置server(apache、nginx…)即可,以下是用nginx設定:

server {

    listen 80;
    server_name localhost;

    #path:/etc/nginx/sites-enabled/default

    root /var/www/newproject;
    index  index.html index.htm index.php;

    location / {
        if (!-f $request_filename) {
            rewrite "^(.*)$" /public/index.php?q=$1 last;
            break;
        }
    }

    location ~ \.php$ {
        fastcgi_pass   127.0.0.1:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  /var/www/newproject$fastcgi_script_name;
        include fastcgi_params;
    }   
}

從上方配置可看到,使用$request_filename,判斷該檔案路徑存不存在,將所有不存在的url導向index.php,在由index.php去分派給controller,如果存在則直接導向該檔案。

最後打開http://localhost就可以看到配置好的project首頁了。

Zend framework core cache

zend framework提供了Zend_Cache::factory,這個factory可以產生出好幾種cache方式,官方範例如下:

// We choose a backend (for example 'File' or 'Sqlite'...)
$backendName = '[...]';

// We choose a frontend (for example 'Core', 'Output', 'Page'...)
$frontendName = '[...]';

// We set an array of options for the chosen frontend
$frontendOptions = array([...]);

// We set an array of options for the chosen backend
$backendOptions = array([...]);

// We create an instance of Zend_Cache
// (of course, the two last arguments are optional)
$cache = Zend_Cache::factory($frontendName,
                             $backendName,
                             $frontendOptions,
                             $backendOptions);

frontendName:

指的是快取對象,例如資料、頁面、檔案。

backendName:

指的是要儲存的容器,例如存入檔案裡、sqlite、或者是連接至Memcache server等…。

frontendOptions和backendOptions:

指的是可選參數值。

可參考官方frontend cachebackend cache

在前台頁面中,有一些資料可能是很久才去更新一次,這時就可以使用cache:
  • 如果是採用page cache,那可能要只cache一部分頁面,透過ajax抓取,或者直接在view上面加判斷式。
  • 透過file cache可直接將抓取到的api資料,直接存入,只要每次去檢查cache時效是否過了,如果時效過則在重新抓取。
以下是一個簡單example:
class FileCache {

    private $cache;

    public function FileCache( $lifetime = 300 ){
        // 設定 Frontend 選項
        $frontendOptions = array(
           'lifetime' => $lifetime, // 快取時間
           'automatic_serialization' => true, // 自動 serialization
        );  
        // 設定 Backend 選項
        $backendOptions = array(
            'cache_dir' => APPLICATION_PATH . '/cache/', // 快取存放路徑
        );  
        // 建立一個快取物件
        $this->cache = Zend_Cache::factory('Core',
                                           'File',
                                           $frontendOptions,
                                           $backendOptions);

    }   

    public function load( $name ){
        return $this->cache->load( $name );
    }   

    public function save( $name, $data ){
        return $this->cache->save( $data, $name );
    }

    public function isAvailable( $name ){
        return $this->cache->test( $name );  
    }   
}

使用方式如下:

$cache = new FileCache( 60 );

$cacheName = "data_cache";

if( $cache->isAvailabel( $cacheName ) ){

    $result = $cache->load( $cacheName );
}else{

    $result = array(1,2,3,4,5);
    $cache->save( $cacheName, $result )
}

print_r( $result );