控制器
控制器是 MVC 中的 ‘C’ 。在應用了路由且正確的控制器被找到之後,控制器的動作(action)被調用。控制器將處理解釋請求數據,確保正確的模型被調用,確保正確的輸出或視圖被渲染。控制器可被視爲模型和視圖的中間人。你要保持控制器很精煉,而模型類很豐滿。這會幫助你更容易地重用你的代碼,並使你的代碼更易於測試。
通常,控制器用於管理單個視圖邏輯。例如,你爲一個在線麪包店建立站點,你可能會有一個 RecipesController 和一個 IngredientsController,管理你的食譜和原料。在 CakePHP 中,控制器用它們處理的主要模型命名。也常常會有一個控制器和多個模型共同工作的情況。
應用程序控制器擴展自 AppController 類,它擴展自內核的 Controller 類。AppController 類可以在/app/Controller/AppController.php 中定義,它可以包含應用程序中多個控制器共享的方法。
控制器提供的一些方法被叫做 動作(actions)。動作是控制器用來處理請求的方法。默認情況下,控制器中所有的公有方法都可以從 url 訪問。控制器負責解析請求並創建響應。通常響應是以渲染視圖的格式完成,但有時也會用其它途徑創建響應。
App 控制器
誠如介紹中所言,AppController 類是所有應用程序控制器的你類。 AppController 自身是擴展自 CakePHP 核心庫中的 Controller 類。 /app/Controller/AppController.php 文件中的 AppController 類類似於:
class AppController extends Controller {
}
AppController 中建立的屬性和方法在應用程序的所有控制器中都可用。 它是放置所有控制器的公用代碼的最佳位置。 組件(你將會有後面學到)則用於存放許多控制器(但不一定是全部)都使用的代碼。
相對於正常的面向對象繼承規則,CakePHP 對於控制器的特殊屬性,做了一些擴展工作。組件和助手清單被控制器特殊對待。 在這些情況下,AppContrller 值數組和子控制器類的數組合並。子類的那些值總是覆蓋 AppController 類的同名值。
註解
CakePHP 合併 AppController 和你的應用程序控制器的如下變量:
- $components
- $helpers
- $uses
如果你在 AppController 中定義了 $helpers ,記得添加默認的 Html 和 Form 助手。
還要記得在子類的回調方法中調用 AppController 的回調方法。
public function beforeFilter() {
parent::beforeFilter();
}
請求參數
在爲 CakePHP 應用程序生成了請求之後,CakePHP 的 Router 和 Dispatcher 類使用 路由配置 尋找和建立正確的控制器。請求數據壓入一個請求對象。CakePHP 把所有重要的請求信息都放入 $this->request 屬性。關於 CakePHp 請求對象的更多信息請參看 CakeRequest 。
控制器動作
控制器負責將請求參數轉換成瀏覽器/用戶要求的響應。CakePHP 使用約定自動完成這些處理過程並移動了在其它情況下你需要編寫的一些程式化的代碼。
根據約定,CakePHP 渲染一個動作名映射的視圖。回到我們的在線麪包店的例子,我們的 RecipesController 可能會包含 view()、share() 和 search() 動作。這個控制器在 /app/Controller/RecipesController.php 文件中,並且包含如下內容:
# /app/Controller/RecipesController.php
class RecipesController extends AppController {
public function view($id) {
//action logic goes here..
}
public function share($customerId, $recipeId) {
//action logic goes here..
}
public function search($query) {
//action logic goes here..
}
}
這些動作對應的視圖文件分別是 app/View/Recipes/view.ctp、 app/View/Recipes/share.ctp 和 andapp/View/Recipes/search.ctp。視圖文件名被規定爲使用小寫和下劃線間隔的版本的動作名。
控制器動作通常使用 set() 來創建用於渲染視圖瓣 View 上下文。 由於使用 CakePHP 的約定,你不需要手動創建和渲染視圖。取而代之的是,一旦一個應用程序的動作完成,CakePHP 將處理視圖渲染和傳輸。
某些情況下你可能需要跳過默認的的行爲。下面的技巧可以跳過默認的視圖渲染。
- 如果你從控制器動作中返回一個字符串或者能被轉換爲字符串的對象,它將被用作響應內容。
- 你可以返回一個 CakeResponse 對象,它帶有完整的響應。
控制器方法使用 requestAction() 處理你想返回的非字符串數據。如果你的控制器方法使用 常規請求 + requestAction,你需要在運行前檢查請求的類型:
class RecipesController extends AppController {
public function popular() {
$popular = $this->Recipe->popular();
if (!empty($this->request->params['requested'])) {
return $popular;
}
$this->set('popular', $popular);
}
}
上面的控制器動作是如何在一個方法中使用 requestAction() 和常規請求。給非 requestActin 請求返回一個數組將引起錯誤,這是需要的避免的。關於使用 requestAction() 的更多提示請參閱 Controller::requestAction() 環節。
爲使你在應用程序中更有效地使用控制器,我們提供了 CakePHP 控制器常用的一些核心屬性和方法。
請求生命週期回調
- class Controller
CakePHP 控制器提供了能用來在請求生命週期中插入邏輯的回調:
- Controller::beforeFilter()
-
這個函數在控制器中的每個動作前執行。 它是檢驗活動會話或用戶授權驗證的好地方。
註解
beforeFilter() 方法在找不到動作時或者腳手架動作執行前,也會執行。
- Controller::beforeRender()
-
在控制器動作邏輯之後、視圖渲染前調用。這個回調不常用,但是如果你在動作結束前手動調用 render() 時可能會需要它。
- Controller::afterFilter()
-
在控制器動作邏輯和渲染完成之後調用。這是運行時執行的最後一個控制器方法。
除了控制器週期回調之外,組件 也提供一組回調集。
控制器方法
完整的控制器方法列表及其描述請訪問 CakePHP API,瀏覽 http://api20.cakephp.org/class/controller。
與視圖進行交互
控制器與視圖交互有幾種方法。 首先,可以使用 set() 向視圖傳送數據。還可以決定使用哪個視圖類,以及在控制器中渲染哪個視圖文件。
- Controller::set(string $var, mixed $value)
-
set() 方法是從控制器向視圖傳送數據的主要途徑。一旦你使用 set() ,就可以在視圖中訪問這些變量:
// 先從控制器中傳送數據 $this->set('color', '粉'); // 然後在視圖中使用這些數據 ?> 你已經爲蛋糕選擇了 <?php echo $color; ?> 顏色的糖衣。
set() 方法還能將它的第一個參數設成一個關聯數組。這是在視圖中訪問信息集合時常用的方法。
在 1.3 版更改: 數組的鍵在被賦值到視圖前將不會被改變(例如,’underscored_key’ 不再會變成 ‘underscoredKey’):
$data = array( 'color' => 'pink', 'type' => 'sugar', 'base_price' => 23.95 ); // 使 $color, $type, and $base_price // 在視圖中有效: $this->set($data);
屬性 $pageTitle 不存在,使用 set() 設置標題:
$this->set('title_for_layout', 'This is the page title');
- Controller::render(string $action, string $layout, string $file)
-
render() 在每個被請求的控制器動作結束前被自動調用。此方法執行所有的視圖邏輯(使用通過 set() 方法提供的數據),將視圖放進它的佈局中,並且將其提供給最終用戶。
被渲染的默認視圖文件根據約定來決定。 如果 RecipesController 的 search() 動作被請求,此視圖文件 /app/View/Recipes/search.ctp 被渲染:
class RecipesController extends AppController { // ... public function search() { // Render the view in /View/Recipes/search.ctp $this->render(); } // ... }
雖然 CakePHP 將在每個動作邏輯之後自動調用它(直到你將 $this->autoRender 設置爲 false),但是你可以通過在控制器中使用 $action 指定另一個視圖文件。
如果 $action 以 ‘/’ 開頭,視圖文件或元素文件就以 /app/View 作爲起始位置定位。這將允許元素渲染轉向,在 ajax 調用中非常有用。:
// 在 /View/Elements/ajaxreturn.ctp 渲染元素 $this->render('/Elements/ajaxreturn');
你還可以使用第三個參數 $file 指定另一個視圖或者元素文件。 $layout 參數允許你指定視圖在哪個佈局中渲染。
渲染特殊視圖
在控制器中,你能夠渲染與約定不一樣的視圖。通過調用 render() 來實現這一目的。一旦你調用了 render(),CakePHP就不再試圖重新渲染默認的視圖了。
class PostsController extends AppController {
public function my_action() {
$this->render('custom_file');
}
}
這會用渲染 app/View/Posts/custom_file.ctp 來代替 app/View/Posts/my_action.ctp 。
流程控制
- Controller::redirect(mixed $url, integer $status, boolean $exit)
-
經常使用的流程控制方法是 redirect()。 這個方法以 CakePHP-relative URL 的形式作爲它的第一個參數。當用戶成功發出了一條命令,你可能會希望將其轉向到一個回執屏幕。:
public function place_order() { // Logic for finalizing order goes here if ($success) { $this->redirect(array('controller' => 'orders', 'action' => 'thanks')); } else { $this->redirect(array('controller' => 'orders', 'action' => 'confirm')); } }
你還能使用相對或者絕對 URL 作爲 $url 參數:
$this->redirect('/orders/thanks')); $this->redirect('http://www.example.com');
也可以傳遞數據給轉向後的動作:
$this->redirect(array('action' => 'edit', $id));
redirect() 的第二個參數允許你伴隨重定向定義一個 PHP 的狀態碼。可以使用 301 (永久轉移)或者 303 (see other),這依賴於轉向的性質。
在轉向後,這個方法將執行 exit(),直到你將第三個參數指定爲 false。
如果你需要轉向到提交頁,可以使用:
$this->redirect($this->referer());
此方法還支持基於名稱的參數。如果你想轉向到類似於http://www.example.com/orders/confirm/product:pizza/quantity:5 的鏈接地址,你可以使用:
$this->redirect(array('controller' => 'orders', 'action' => 'confirm', 'product' => 'pizza', 'quantity' => 5));
- Controller::flash(string $message, string $url, integer $pause, string $layout)
-
與 redirect() 相似,flash() 方法在一個操作完成後將用戶導向到一個新頁面。flash() 方法的不同點是它在將用戶送到另一個 URL 前顯示一條信息。
第一個參數是要顯示的信息,第二個參數是 CakePHP-relative URL。CakePHP 將在轉向前顯示 $message 信息$pause 秒。
如果你想使用自定義的模板來顯示中轉信息,可以在 $layour 參數指定佈局名稱。
爲了在頁面中使用中轉信息,要確保檢查了 SessionComponent 的 setFlash() 方法。
回調
除了 請求生命週期回調 之外,CakePHP 還支持相對於腳手架的回調。 CakePHP also supports callbacks related to scaffolding.
- Controller::beforeScaffold($method)
-
$method 是調用的方法名,如 index、edit 等。
- Controller::afterScaffoldSave($method)
-
$method 是要調用的方法名如 edit 或者 update。
- Controller::afterScaffoldSaveError($method)
-
$method 是要調用的方法名如 edit 或者 update。
- Controller::scaffoldError($method)
-
$method 是調用的方法名,如 index、edit 等。
其它有用的方法
- Controller::constructClasses()
-
控制器中的包含的這個方法加載模型。加載過程正常是由 CakePHP 完成,但是當從不同的視角訪問控制器時,這個方法很易於被調用。如果你在命令行腳本或者其它用途中需要 CakePHP, constructClasses() 就可以派上用場了。
- Controller::referer(mixed $default = null, boolean $local = false)
-
返回當前請求的來源 URL。參數 $default 能夠在無法從請求包的頭部中讀取 HTTP_REFERER 時,提供一個默認的 URL。代替如下方式:
class UserController extends AppController { public function delete($id) { // delete code goes here, and then... if ($this->referer() != '/') { $this->redirect($this->referer()); } else { $this->redirect(array('action' => 'index')); } } }
可以用:
class UserController extends AppController { public function delete($id) { // delete code goes here, and then... $this->redirect($this->referer(array('action' => 'index'))); } }
如果 $default 沒有設置,此函數默認爲站點的根目錄 - ‘/’。
如果將參數 $local 設置爲 true, 則限定提交頁必須是當前服務器。
- Controller::disableCache()
-
用於通知用戶的 瀏覽器 不要緩存當前請求的結果。這與視圖緩存不同,見後續章節。
傳送這一效果的頭是:
Expires: Mon, 26 Jul 1997 05:00:00 GMT Last-Modified: [current datetime] GMT Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0 Pragma: no-cache
- Controller::postConditions(array $data, mixed $op, string $bool, boolean $exclusive)
-
使用這一方法轉換一個 POST 模型數據,作爲一個模型的查找數據的集合。 這個提供一個建立搜索邏輯的快捷方式。例如,一個管理員可能想要搜索訂單以便了解哪些訂單需要被裝運。 可以使用 CakePHP 的 FormHelper 和HtmlHelper 建立基於訂單模型的快捷表單。 之後,一個控制器動作能夠使用來自這個表單的數據構造查找條件:
public function index() { $conditions = $this->postConditions($this->request->data); $orders = $this->Order->find('all', compact('conditions')); $this->set('orders', $orders); }
如果 $this->request->data['Order']['destination'] 等於 “Old Towne Bakery”, postConditions 將這些條件轉換爲與 Model->find() 方法兼容的數組。在這種情況下,就是 array('Order.destination' => 'Old TowneBakery')。
如果你在多個條件中使用不同的 SQL 運算符,用第二個參數支持它們:
/* $this->request->data 的內容 array( 'Order' => array( 'num_items' => '4', 'referrer' => 'Ye Olde' ) ) */ // 我們來獲取最後四組訂單中包含 'Ye Olde' 的訂單 $conditions = $this->postConditions( $this->request->data, array( 'num_items' => '>=', 'referrer' => 'LIKE' ) ); $orders = $this->Order->find('all', compact('conditions'));
第三個參數允許你通知 CakePHP 在多個查找條件中所用的邏輯操作符是什麼。字符串‘AND’、 ‘OR’ 和 ‘XOR’ 都是有效值。
如果最後一個參數被設置爲 true,並且 $op 參數是一個數組,不包含在 $op 中的域將不包含在返回條件中。
- Controller::paginate()
-
這個方法用於來自模型的分頁結果。你可以指定頁的尺寸,模型的查找條件,以及更多的內容。關於分頁的更多詳細信息請參見 分頁 一節。
- Controller::requestAction(string $url, array $options)
-
這個函數可以從任何位置調用控制器動作並返回來自動作的返回數據。$url 傳遞的是一個 CakePHP-relative URL (/controllername/actionname/params)。傳遞給接收控制器動作的擴展數據是放在 $options 數組中的。
註解
通過向 options 傳遞 ‘return’,你能夠使用 requestAction() 獲取渲染完的完整視圖:requestAction($url, array('return'));。要注意的要點是從一個控制器方法中生成一個使用了 ‘return’ 的 requestAction 可能會引起腳本或 css 標籤不正常工作。
警告
如果使用不帶緩存的 requestAction 可能會導致性能不佳。不太適合在控制器或者模型中這麼用。
requestAction 最好與(緩存的)元素一起使用 – 作爲在渲染前爲元素獲取數據的一種方法。 讓我們用在佈局中放置一個 ‘’最終註釋’’ 作爲例子。 首先我們需要建立一個控制器函數用於返回數據
// Controller/CommentsController.php class CommentsController extends AppController { public function latest() { if (empty($this->request->params['requested'])) { throw new ForbiddenException(); } return $this->Comment->find('all', array('order' => 'Comment.created DESC', 'limit' => 10)); } }
你總是需要包含校驗以確保你的 requestAction 方法確實來源於 requestAction。 如果不這樣做就會允許從 URL 直接訪問 requestAction 方法, 這是不可取的。
如果我們現在建立一個簡單的元素調用這個函數:
// View/Elements/latest_comments.ctp $comments = $this->requestAction('/comments/latest'); foreach ($comments as $comment) { echo $comment['Comment']['title']; }
我們可以在任何位置放置元素以獲取所需的輸出:
echo $this->element('latest_comments');
這樣寫,當元素被渲染,一個請求將被創建,發送給控制器去獲取數據,這些數據被處理,然後返回。 然後按照上面的警告,最好是用元素緩存不需要計算的數據。將元素的調用修改成如下的樣子:
echo $this->element('latest_comments', array('cache' => '+1 hour'));
當緩存元素視圖文件存在且有效時,requestAction 將不被調用。
另外,requestAction 現在能使用基於 cake 風格的數組來組成 url:
echo $this->requestAction( array('controller' => 'articles', 'action' => 'featured'), array('return') );
這種方式允許 requestAction 通過調用 Router::url 的方式來提高性能。這種基於數組的 url 與同樣基於數組的 HtmlHelper::link() 有一點不同: - 如果你使用命名或者傳遞參數,你必須把他們放進第二個數組,並且用正確的鍵把它們打包。這是因爲 requestAction 合併命名參數的數組(requestAction 的第二個參數)並帶有 Controller::params 成員,且不明確放置命名參數數組到鍵 ‘named’ 中;$option 數組中的附加成員也將在請求的動作的 Controller::params 數組中:
echo $this->requestAction('/articles/featured/limit:3'); echo $this->requestAction('/articles/view/5');
在 requestAction 中的數組將是:
echo $this->requestAction( array('controller' => 'articles', 'action' => 'featured'), array('named' => array('limit' => 3)) ); echo $this->requestAction( array('controller' => 'articles', 'action' => 'view'), array('pass' => array(5)) );
註解
對於這種類似於字符串 url 的數組,requestAction 處理的方式與其它地方的處理方式有所不同。
當數組形式的 url 與 requestAction() 一起使用時,你必須指定請求動作中所需的 全部 參數。包括像 $this->request->data 這樣的參數。除了要傳遞全部參數, 傳遞和命名參數必須按上面顯示的那樣,放在第二個數組中。
- Controller::loadModel(string $modelClass, mixed $id)
-
當你需要在一個沒有默認模型的控制器中使用模型時,或者在使用一個關聯模型時,loadModel 功能很容易就可以實現你的目的。 model:
$this->loadModel('Article'); $recentArticles = $this->Article->find('all', array('limit' => 5, 'order' => 'Article.created DESC')); $this->loadModel('User', 2); $user = $this->User->read();
控制器屬性
訪問 CakePHP API 可以獲得控制器屬性的完整列表及它們的描述信息。瀏覽 http://api20.cakephp.org/class/controller。
- property Controller::$name
-
$name 屬性用來給控制器命名。通常控制器的名字就是其所操作的主要模型名字的複數形式。此屬性並非必須,除非在 inflect 中保存了它:
// $name controller attribute usage example class RecipesController extends AppController { public $name = 'Recipes'; }
$components, $helpers 和 $uses
其次常用的控制器屬性是通知 CakePHP 你要與當前控制器一起使用的是哪些助手、組件和模型。通過作爲控制器類變量的 $components 和 $uses 提供的這些屬性來向控制器加入 MVC 類(例如,$this->ModelName),通過$helpers 向視圖添加可引用的對象變量 ($this->{$helpername}).
註解
控制器有一些默認的類變量,因此你可能並不需要配置你的控制器。
- property Controller::$uses
-
控制器默認訪問他們的主要模型變量。我們的 RecipesController 擁有 $this->Recipe 形式的 Recipe 模型類變量,而 ProductsController 則擁有 $this->Product 形式的 Product 模型變量。不過,可以通過 $uses 變量讓控制器訪問附加的模型,與當前控制器名稱相符的模型也必須包含。示例展示見下文。
如果你不希望在控制器中使用模型,設置 public $uses = array()。 這將允許你使用一個不帶有默認模型文件的控制器。但是定義在 AppController 中的模型仍然會加載。你還可以通過使用 false 來指明不加載任何模型。甚至在 AppController 中定義這些參數。
在 2.1 版更改: Uses 現在有了新的默認值,它以不同的方式處理 false。
- property Controller::$helpers
-
Html、Form 和 Session 助手默認可用,像是 SessionComponent 一樣。但是如果你選擇在 AppController 中定義自己的 $helpers 數組,就要在其中包括 Html 和 Form,以確保它們在你的控制器中仍然默認可用。要了解關於這些類的更多信息,請瀏覽本手冊後面的相關章節。
讓我們看看如何通過 CakePHP 控制器你計劃使用附加的 MVC 類:
class RecipesController extends AppController { public $uses = array('Recipe', 'User'); public $helpers = array('Js'); public $components = array('RequestHandler'); }
這兒的每個變量都與其繼承的值相合並,因此不必(如上例)重定義 Form 助手或者你在 App 控制器中定義的任何東西。
- property Controller::$components
-
組件數組允許你設置控制器要用的 組件。與 $helpers 和 $uses 一樣,控制器中的組件也會合並 AppController中的那些組件。與 $helpers 一樣,你能向組件傳送設置。更多信息請參見 配置組件 。
其它屬性
雖然你可以在 API 中打開所有的控制器屬性的詳細信息,另外一些控制器屬性在本手冊中的各自的章節仍然有存在價值。
關於控制器的更多內容
- 請求和響應對象
- CakeRequest
- 訪問請求對象
- 訪問 query 參數
- 訪問 POST 數據
- 訪問 PUT 或者 POST 數據
- 訪問 XML 或者 JSON 數據
- 訪問路徑信息
- 檢查請求
- CakeRequest 和 RequestHandlerComponent
- 與其它層面的請求互動
- CakeRequest API
- CakeResponse
- 變化中的響應類
- 處理內容類型
- 傳輸文件
- 設置 header
- 與瀏覽器緩存交互
- HTTP 緩存微調
- 緩存控制 頭信息
- 過期頭信息
- Etag 頭信息
- 最後編輯頭信息
- Vary 頭信息
- CakeResponse 與測試
- CakeResponse API
- 腳手架
- 使用腳手架建立一個簡單的管理界面
- 自定義腳手架視圖
- 頁面控制器
- 組件
- 配置組件
- 使用組件
- 運行中加載組件
- 組件回調
- 創建組件
- 在控制器中包含組件
- 在組件中使用其它組件
- 組件 API
- 回調