控制器是 MVC 中的 ‘C’ 。在应用了路由且正确的控制器被找到之后,控制器的动作(action)被调用。控制器将处理解释请求数据,确保正确的模型被调用,确保正确的输出或视图被渲染。控制器可被视为模型和视图的中间人。你要保持控制器很精炼,而模型类很丰满。这会帮助你更容易地重用你的代码,并使你的代码更易于测试。
通常,控制器用于管理单个视图逻辑。例如,你为一个在线面包店建立站点,你可能会有一个 RecipesController 和一个 IngredientsController,管理你的食谱和原料。在 CakePHP 中,控制器用它们处理的主要模型命名。也常常会有一个控制器和多个模型共同工作的情况。
应用程序控制器扩展自 AppController 类,它扩展自内核的 Controller 类。AppController 类可以在/app/Controller/AppController.php 中定义,它可以包含应用程序中多个控制器共享的方法。
控制器提供的一些方法被叫做 动作(actions)。动作是控制器用来处理请求的方法。默认情况下,控制器中所有的公有方法都可以从 url 访问。控制器负责解析请求并创建响应。通常响应是以渲染视图的格式完成,但有时也会用其它途径创建响应。
诚如介绍中所言,AppController 类是所有应用程序控制器的你类。 AppController 自身是扩展自 CakePHP 核心库中的 Controller 类。 /app/Controller/AppController.php 文件中的 AppController 类类似于:
1 class AppController extends Controller { 2 }
AppController 中建立的属性和方法在应用程序的所有控制器中都可用。 它是放置所有控制器的公用代码的最佳位置。 组件(你将会有后面学到)则用于存放许多控制器(但不一定是全部)都使用的代码。
相对于正常的面向对象继承规则,CakePHP 对于控制器的特殊属性,做了一些扩展工作。组件和助手清单被控制器特殊对待。 在这些情况下,AppContrller 值数组和子控制器类的数组合并。子类的那些值总是覆盖 AppController 类的同名值。
注解
CakePHP 合并 AppController 和你的应用程序控制器的如下变量:
如果你在 AppController 中定义了 $helpers ,记得添加默认的 Html 和 Form 助手。
还要记得在子类的回调方法中调用 AppController 的回调方法。
1 public function beforeFilter() { 2 parent::beforeFilter(); 3 }
请求参数
在为 CakePHP 应用程序生成了请求之后,CakePHP 的 Router 和 Dispatcher 类使用 路由配置 寻找和建立正确的控制器。请求数据压入一个请求对象。CakePHP 把所有重要的请求信息都放入 $this->request 属性。关于 CakePHp 请求对象的更多信息请参看 CakeRequest 。
控制器负责将请求参数转换成浏览器/用户要求的响应。CakePHP 使用约定自动完成这些处理过程并移动了在其它情况下你需要编写的一些程式化的代码。
根据约定,CakePHP 渲染一个动作名映射的视图。回到我们的在线面包店的例子,我们的 RecipesController 可能会包含 view()、share() 和 search() 动作。这个控制器在 /app/Controller/RecipesController.php 文件中,并且包含如下内容:
1 # /app/Controller/RecipesController.php 2 3 class RecipesController extends AppController { 4 public function view($id) { 5 //action logic goes here.. 6 } 7 8 public function share($customerId, $recipeId) { 9 //action logic goes here.. 10 } 11 12 public function search($query) { 13 //action logic goes here.. 14 } 15 }
这些动作对应的视图文件分别是 app/View/Recipes/view.ctp、 app/View/Recipes/share.ctp 和 andapp/View/Recipes/search.ctp。视图文件名被规定为使用小写和下划线间隔的版本的动作名。
控制器动作通常使用 set() 来创建用于渲染视图瓣 View 上下文。 由于使用 CakePHP 的约定,你不需要手动创建和渲染视图。取而代之的是,一旦一个应用程序的动作完成,CakePHP 将处理视图渲染和传输。
某些情况下你可能需要跳过默认的的行为。下面的技巧可以跳过默认的视图渲染。
控制器方法使用 requestAction() 处理你想返回的非字符串数据。如果你的控制器方法使用 常规请求 + requestAction,你需要在运行前检查请求的类型:
1 class RecipesController extends AppController { 2 public function popular() { 3 $popular = $this->Recipe->popular(); 4 if (!empty($this->request->params['requested'])) { 5 return $popular; 6 } 7 $this->set('popular', $popular); 8 } 9 }
上面的控制器动作是如何在一个方法中使用 requestAction() 和常规请求。给非 requestActin 请求返回一个数组将引起错误,这是需要的避免的。关于使用 requestAction() 的更多提示请参阅 Controller::requestAction() 环节。
为使你在应用程序中更有效地使用控制器,我们提供了 CakePHP 控制器常用的一些核心属性和方法。
CakePHP 控制器提供了能用来在请求生命周期中插入逻辑的回调:
Controller::beforeFilter()这个函数在控制器中的每个动作前执行。 它是检验活动会话或用户授权验证的好地方。
注解
beforeFilter() 方法在找不到动作时或者脚手架动作执行前,也会执行。
在控制器动作逻辑之后、视图渲染前调用。这个回调不常用,但是如果你在动作结束前手动调用 render() 时可能会需要它。
Controller::afterFilter()在控制器动作逻辑和渲染完成之后调用。这是运行时执行的最后一个控制器方法。
除了控制器周期回调之外,组件 也提供一组回调集。
完整的控制器方法列表及其描述请访问 CakePHP API,浏览 http://api20.cakephp.org/class/controller。
控制器与视图交互有几种方法。 首先,可以使用 set() 向视图传送数据。还可以决定使用哪个视图类,以及在控制器中渲染哪个视图文件。
Controller::set(string $var, mixed $value)set() 方法是从控制器向视图传送数据的主要途径。一旦你使用 set() ,就可以在视图中访问这些变量:
1 // 先从控制器中传送数据 2 3 $this->set('color', '粉'); 4 5 // 然后在视图中使用这些数据 6 ?>
你已经为蛋糕选择了 echo $color; ?> 颜色的糖衣。
set() 方法还能将它的第一个参数设成一个关联数组。这是在视图中访问信息集合时常用的方法。
在 1.3 版更改: 数组的键在被赋值到视图前将不会被改变(例如,’underscored_key’ 不再会变成 ‘underscoredKey’):
1 $data = array( 2 'color' => 'pink', 3 'type' => 'sugar', 4 'base_price' => 23.95 5 ); 6 7 // 使 $color, $type, and $base_price 8 // 在视图中有效: 9 10 $this->set($data);
属性 $pageTitle 不存在,使用 set() 设置标题:
1 $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 被渲染:
1 class RecipesController extends AppController { 2 // ... 3 public function search() { 4 // Render the view in /View/Recipes/search.ctp 5 $this->render(); 6 } 7 // ... 8 }
虽然 CakePHP 将在每个动作逻辑之后自动调用它(直到你将 $this->autoRender 设置为 false),但是你可以通过在控制器中使用 $action 指定另一个视图文件。
如果 $action 以 ‘/’ 开头,视图文件或元素文件就以 /app/View 作为起始位置定位。这将允许元素渲染转向,在 ajax 调用中非常有用。:
1 // 在 /View/Elements/ajaxreturn.ctp 渲染元素 2 $this->render('/Elements/ajaxreturn');
你还可以使用第三个参数 $file 指定另一个视图或者元素文件。 $layout 参数允许你指定视图在哪个布局中渲染。
经常使用的流程控制方法是 redirect()。 这个方法以 CakePHP-relative URL 的形式作为它的第一个参数。当用户成功发出了一条命令,你可能会希望将其转向到一个回执屏幕。:
1 public function place_order() { 2 // Logic for finalizing order goes here 3 if ($success) { 4 $this->redirect(array('controller' => 'orders', 'action' => 'thanks')); 5 } else { 6 $this->redirect(array('controller' => 'orders', 'action' => 'confirm')); 7 } 8 }
你还能使用相对或者绝对 URL 作为 $url 参数:
1 $this->redirect('/orders/thanks')); 2 $this->redirect('http://www.example.com');
也可以传递数据给转向后的动作:
1 $this->redirect(array('action' => 'edit', $id));
redirect() 的第二个参数允许你伴随重定向定义一个 PHP 的状态码。可以使用 301 (永久转移)或者 303 (see other),这依赖于转向的性质。
在转向后,这个方法将执行 exit(),直到你将第三个参数指定为 false。
如果你需要转向到提交页,可以使用:
1 $this->redirect($this->referer());
此方法还支持基于名称的参数。如果你想转向到类似于http://www.example.com/orders/confirm/product:pizza/quantity:5 的链接地址,你可以使用:
1 $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 等。
控制器中的包含的这个方法加载模型。加载过程正常是由 CakePHP 完成,但是当从不同的视角访问控制器时,这个方法很易于被调用。如果你在命令行脚本或者其它用途中需要 CakePHP, constructClasses() 就可以派上用场了。
Controller::referer(mixed $default = null, boolean $local = false)返回当前请求的来源 URL。参数 $default 能够在无法从请求包的头部中读取 HTTP_REFERER 时,提供一个默认的 URL。代替如下方式:
1 class UserController extends AppController { 2 public function delete($id) { 3 // delete code goes here, and then... 4 if ($this->referer() != '/') { 5 $this->redirect($this->referer()); 6 } else { 7 $this->redirect(array('action' => 'index')); 8 } 9 } 10 }
可以用:
1 class UserController extends AppController { 2 public function delete($id) { 3 // delete code goes here, and then... 4 $this->redirect($this->referer(array('action' => 'index'))); 5 } 6 }
如果 $default 没有设置,此函数默认为站点的根目录 - ‘/’。
如果将参数 $local 设置为 true, 则限定提交页必须是当前服务器。
Controller::disableCache()用于通知用户的 浏览器 不要缓存当前请求的结果。这与视图缓存不同,见后续章节。
传送这一效果的头是:
1 Expires: Mon, 26 Jul 1997 05:00:00 GMT 2 Last-Modified: [current datetime] GMT 3 Cache-Control: no-store, no-cache, must-revalidate 4 Cache-Control: post-check=0, pre-check=0 5 Pragma: no-cache
Controller::postConditions(array $data, mixed $op, string $bool, boolean $exclusive)
使用这一方法转换一个 POST 模型数据,作为一个模型的查找数据的集合。 这个提供一个建立搜索逻辑的快捷方式。例如,一个管理员可能想要搜索订单以便了解哪些订单需要被装运。 可以使用 CakePHP 的 FormHelper 和HtmlHelper 建立基于订单模型的快捷表单。 之后,一个控制器动作能够使用来自这个表单的数据构造查找条件:
1 public function index() { 2 $cOnditions= $this->postConditions($this->request->data); 3 $orders = $this->Order->find('all', compact('conditions')); 4 $this->set('orders', $orders); 5 }
如果 $this->request->data['Order']['destination'] 等于 “Old Towne Bakery”, postConditions 将这些条件转换为与 Model->find() 方法兼容的数组。在这种情况下,就是 array('Order.destination' => 'Old TowneBakery')。
如果你在多个条件中使用不同的 SQL 运算符,用第二个参数支持它们:
1 /* 2 $this->request->data 的内容 3 array( 4 'Order' => array( 5 'num_items' => '4', 6 'referrer' => 'Ye Olde' 7 ) 8 ) 9 */ 10 11 // 我们来获取最后四组订单中包含 'Ye Olde' 的订单 12 $cOnditions= $this->postConditions( 13 $this->request->data, 14 array( 15 'num_items' => '>=', 16 'referrer' => 'LIKE' 17 ) 18 ); 19 $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 最好与(缓存的)元素一起使用 – 作为在渲染前为元素获取数据的一种方法。 让我们用在布局中放置一个 ‘’最终注释’’ 作为例子。 首先我们需要建立一个控制器函数用于返回数据
1 // Controller/CommentsController.php 2 class CommentsController extends AppController { 3 public function latest() { 4 if (empty($this->request->params['requested'])) { 5 throw new ForbiddenException(); 6 } 7 return $this->Comment->find('all', array('order' => 'Comment.created DESC', 'limit' => 10)); 8 } 9 }
你总是需要包含校验以确保你的 requestAction 方法确实来源于 requestAction。 如果不这样做就会允许从 URL 直接访问 requestAction 方法, 这是不可取的。
如果我们现在建立一个简单的元素调用这个函数:
1 // View/Elements/latest_comments.ctp 2 3 $comments = $this->requestAction('/comments/latest'); 4 foreach ($comments as $comment) { 5 echo $comment['Comment']['title']; 6 }
我们可以在任何位置放置元素以获取所需的输出:
1 echo $this->element('latest_comments');
这样写,当元素被渲染,一个请求将被创建,发送给控制器去获取数据,这些数据被处理,然后返回。 然后按照上面的警告,最好是用元素缓存不需要计算的数据。将元素的调用修改成如下的样子:
1 echo $this->element('latest_comments', array('cache' => '+1 hour'));
当缓存元素视图文件存在且有效时,requestAction 将不被调用。
另外,requestAction 现在能使用基于 cake 风格的数组来组成 url:
1 echo $this->requestAction( 2 array('controller' => 'articles', 'action' => 'featured'), 3 array('return') 4 );
这种方式允许 requestAction 通过调用 Router::url 的方式来提高性能。这种基于数组的 url 与同样基于数组的 HtmlHelper::link() 有一点不同: - 如果你使用命名或者传递参数,你必须把他们放进第二个数组,并且用正确的键把它们打包。这是因为 requestAction 合并命名参数的数组(requestAction 的第二个参数)并带有 Controller::params 成员,且不明确放置命名参数数组到键 ‘named’ 中;$option 数组中的附加成员也将在请求的动作的 Controller::params 数组中:
1 echo $this->requestAction('/articles/featured/limit:3'); 2 echo $this->requestAction('/articles/view/5');
在 requestAction 中的数组将是:
1 echo $this->requestAction( 2 array('controller' => 'articles', 'action' => 'featured'), 3 array('named' => array('limit' => 3)) 4 ); 5 6 echo $this->requestAction( 7 array('controller' => 'articles', 'action' => 'view'), 8 array('pass' => array(5)) 9 );
注解
对于这种类似于字符串 url 的数组,requestAction 处理的方式与其它地方的处理方式有所不同。
当数组形式的 url 与 requestAction() 一起使用时,你必须指定请求动作中所需的 全部 参数。包括像 $this->request->data 这样的参数。除了要传递全部参数, 传递和命名参数必须按上面显示的那样,放在第二个数组中。
Controller::loadModel(string $modelClass, mixed $id)当你需要在一个没有默认模型的控制器中使用模型时,或者在使用一个关联模型时,loadModel 功能很容易就可以实现你的目的。 model:
1 $this->loadModel('Article'); 2 $recentArticles = $this->Article->find('all', array('limit' => 5, 'order' => 'Article.created DESC')); 3 4 $this->loadModel('User', 2); 5 $user = $this->User->read();
控制器属性
访问 CakePHP API 可以获得控制器属性的完整列表及它们的描述信息。浏览 http://api20.cakephp.org/class/controller。
property Controller::$name$name 属性用来给控制器命名。通常控制器的名字就是其所操作的主要模型名字的复数形式。此属性并非必须,除非在 inflect 中保存了它:
1 // $name controller attribute usage example 2 class RecipesController extends AppController { 3 public $name = 'Recipes'; 4 }
$components, $helpers 和 $uses
其次常用的控制器属性是通知 CakePHP 你要与当前控制器一起使用的是哪些助手、组件和模型。通过作为控制器类变量的 $components 和 $uses 提供的这些属性来向控制器加入 MVC 类(例如,$this->ModelName),通过$helpers 向视图添加可引用的对象变量 ($this->{$helpername}).
注解
控制器有一些默认的类变量,因此你可能并不需要配置你的控制器。
控制器默认访问他们的主要模型变量。我们的 RecipesController 拥有 $this->Recipe 形式的 Recipe 模型类变量,而 ProductsController 则拥有 $this->Product 形式的 Product 模型变量。不过,可以通过 $uses 变量让控制器访问附加的模型,与当前控制器名称相符的模型也必须包含。示例展示见下文。
如果你不希望在控制器中使用模型,设置 public $uses = array()。 这将允许你使用一个不带有默认模型文件的控制器。但是定义在 AppController 中的模型仍然会加载。你还可以通过使用 false 来指明不加载任何模型。甚至在 AppController 中定义这些参数。
在 2.1 版更改: Uses 现在有了新的默认值,它以不同的方式处理 false。
property Controller::$helpersHtml、Form 和 Session 助手默认可用,像是 SessionComponent 一样。但是如果你选择在 AppController 中定义自己的 $helpers 数组,就要在其中包括 Html 和 Form,以确保它们在你的控制器中仍然默认可用。要了解关于这些类的更多信息,请浏览本手册后面的相关章节。
让我们看看如何通过 CakePHP 控制器你计划使用附加的 MVC 类:
1 class RecipesController extends AppController { 2 public $uses = array('Recipe', 'User'); 3 public $helpers = array('Js'); 4 public $compOnents= array('RequestHandler'); 5 }
这儿的每个变量都与其继承的值相合并,因此不必(如上例)重定义 Form 助手或者你在 App 控制器中定义的任何东西。