控制器是PHP函数,通过它,你可以根据HTTP请求创建任务信息,并且构建和返回HTTP响应(作为Symfony2的Response对象)。响应可以是HTML页面、XML文档、序列化的JSON数组、图片、重定向、404错误甚至是你可以想到的一切。控制器中包含了你应用程序需要创建响应的抽象逻辑。
你的控制器可能是从请求中读取信息、导引数据库资源、发送电子邮件或者在用户Session中设置信息。但控制器的最终目的是返回传递给客户端的Response对象,不用担心还会变出什么戏法或有其它什么要求。下面是一些常见的示例:
1、控制器A准备了一个显示网站首面内容的Response对象
2、控制器B从请求中读取slug参数,从数据库中载入一条博文,并创建一个显示该博文的Response对象。如果slug不能被数据库中检索到,那么控制器将创建并返回一个带404状态码的Response对象。
3、控制器C处理关于联系人的表单提交。它从请求中读取表单信息,将联系人信息存入数据库并发送包含联系人信息的电子邮件给网站管理员。最后,它创建一个Response对象将用户的浏览器重定向到联系人表单的“感谢”页面。
“请求、控制器、响应” 生命周期
通过Symfony2项目处理的每个请求都会通过基本相同的生命周期。框架保管重复任务并最终执行有着你自定义应用程序代码的控制器。
1、每个请求都被单个前端控制器(如app.php或index.php)文件处理,前端控制器负责引导框架;
2、路由查看并匹配请求信息,并将其指向一个特定的路由,该路由决定调用哪个控制器;
3、执行控制器,控制器中的代码将创建并返回一个Response对象;
4、HTTP头和Response对象的内容将发回客户端。
创建控制器与创建页面一样方便,同时映射一个URI到该控制器
虽然名称相似,但前端控制器与我们在本章节所说的控制器是不同的,前端控制器是你web目录中的一个PHP小文件,所有的请求都直接经过它。一个典型的应用程序将有一个用于生产的前端控制器(如app.php)和一个用于开发的前端控制器(如app_dev.php)。你可以永远不需要对前端控制器进行编辑、查看和担心。
一个简单的控制器
控制器是一个可调用的PHP,负责返回资源的表现(大多数时间是一个HTML表现)。虽然一个控制器可以是任何的可被调用的PHP(函数、对象的方法或Closure),在Symfony2,控制器通常是在控制器对象中的一个方法,控制器也常被称为action。
// src/Acme/HelloBundle/Controller/HelloController.php namespace Acme\HelloBundle\Controller; use Symfony\Component\HttpFoundation\Response; class HelloController { public function indexAction($name) { return new Response('<html><body>Hello '.$name.'!</body></html>'); } }
注意:控制器是indexAction方法,它隶属于一个控制器类(HelloController)。不要对名称感到困惑:控制器类只是简单将几个控制器集中在一起的。通常情况下,控制器类将放置多个控制器(如updateAction、deleteAction等),一个控制器有时也会被当作一个action被提及。
这个控制器非常简单,但还是让我们看看它:
1、第3行:Symfony2充分利用了PHP5.3的名称空间的功能去为整个控制器类命名空间。use关键字导入Response类,是我们控制器必须返回的;
2、第6行:类名是一个控制器名加上词Controller。这是为控制器提供一致的惯例,并且可以在路由配置中仅被指向前面部分(如Hello);
3、第8行:在控制器类中的每个action都有着后缀Action,并在路由配置中通过action名(index)被指定。在下一节中,我们将使用路由映射一个URI到该action,并展示如何将路由占位符({name})变成action的参数($name);
4、第10行:控制器创建并返回一个Response对象。
将URI映射到控制器
我们的新控制器返回一个简单的HTML页。为了能够在指定URI中渲染该控制器,我们需要为它创建一个路由。
我们将在路由章节中讨论路由组件的细节,但让我们为我们的控制器创建一个简单路由:
# src/Acme/HelloBundle/Resources/config/routing.yml hello: pattern: /hello/{name} defaults: { _controller: AcmeHelloBundle:Hello:index }
/hello/ryan 现在执行HelloController::indexAction()控制器,并且将ryan赋给$name变量。创建一个“页”意味着简单地创建一个控制器方法并与路由关联。这里并没有什么隐蔽的层或藏在屏幕后的魔法。
注意,用于指向控制器的语法:AcmeHelloBundle:Hello:index。Symfony2使用一个灵活的字符串注释来指向不同的控制器。这是最通用的语法并告诉Symfony2到名为AcmeHelloBundle内部去查找一个名为HelloController的控制器类,然后执行方法indexAction()。
关于指向不同控制器的字符串格式用法的更多细节,请参见控制器命名模式。
注意因为我们的控制器处于AcmeHellBundle中,所以我们将路由配置有序地放置在AcmeHelloBundle中。要引导Bundle中的路由配置,必须从你的应用程序的主路由资源中导入。详见包含外部路由资源。
作为控制器参数的路由参数
我们现在已经知道_controller的参数 AcmeHelloBundle:hello:index 指向AcmeHelloBundle 中的HelloController::indexAction()方法,这里更有趣的是发送给该方法的参数:
<?php // src/Acme/HelloBundle/Controller/HelloController.php namespace Acme\HelloBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class HelloController extends Controller { public function indexAction($name) { // ... } }
控制器有个参数$name,对应所匹配路由的{name}参数(在本例中是ryan)。实际上当执行你的控制器时,Symfony2在所匹配路由中匹配带参数控制器中的每个参数。以下所示:
# src/Acme/HelloBundle/Resources/config/routing.yml hello: pattern: /hello/{first_name}/{last_name} defaults: { _controller: AcmeHelloBundle:Hello:index, color: green }
因此控制器也需要多个参数:
public function indexAction($first_name, $last_name, $color) { // ... }
注意两个占位符(`first_name`、`last_name`)和默认color变量做为控制器的参数。当路由匹配时,占位符变量与 defaults 合并成一个数组,并且用于你的控制器。
将路由参数映射到控制器参数是十分容易和灵活的。在你开发时请遵循以下思路:
控制器参数的顺序无关紧要
Symfony2可以根据路由参数名匹配控制器方法参数的特征。换句话说,它可以实现last_name参数与$last_name参数的匹配。控制器可以在随意排列参数的情况下正常工作。
public function indexAction($last_name, $color, $first_name) { // ... }
控制器所需参数必须匹配路由参数
下面会抛出一个运行时异常(RuntimeException),因为在路由定义中没有foo参数
public function indexAction($first_name, $last_name, $color, $foo) { // .. }
然而如果设置该参数可选是完全可行的。下面的例子不会抛出异常:
public function indexAction($first_name, $last_name, $color, $foo = 'bar') { // .. }
不是所有的路由参数都需要在控制器上有相应参数的
如果,举个例子,last_name对你控制器不是很重要的话,你可以完全忽略掉它:
public function indexAction($first_name, $last_name, $color, $foo) { // .. }
实际上,_controller路由参数本身就可以作为控制器参数,因为它也在路由的defaults中。当然它通常是没用的,所以我们在控制器中忽略它
每条路由也都有一个特殊的_route参数,该参数匹配路由的名字(如hello)。虽然不常用,但它一样也可以作为控制器的参数。
(译者:这么几段下来,其实只需要记住一点,那就是有控制器的参数,而没有相匹配的路由参数,而反之则无妨!)
Controller基类
出于方便的考虑,Symfony2提供了一个Controller基类,以帮助实现常用的一些控制器任务,并站你的控制器类能够访问所需的资源。通过继承该类,你可以利用其中的一些帮手方法。
在顶部使用use语句添加Controller类,然后修改HelloController去继承它。如下所示:
// src/Acme/HelloBundle/Controller/HelloController.php namespace Acme\HelloBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; class HelloController extends Controller { public function indexAction($name) { return new Response('<html><body>Hello '.$name.'!</body></html>'); } }
到目前为止,继承Controller类并没有改变任何东西。在下一节中,我们将使用几个该基类中的助手方法。这些方法只是让你可以方便地使用Symfony2的核心功能,而无论你是否使用Controller基类。其实查看核心功能的最好方式就是看Controller类本身。
在Symfony2中是否继承基类是可以选择的;它包含了非常有用的便捷方式,但并不强制使用。你也可以继承ContainerAwaer。这个服务容器对象然后可以通过container属性访问,并且它只是你需要创建任何控制器的对象。
你也可以将你的控制器定义成服务。
常用控制器任务
虽然控制器实际上可以做任何事情,但大多数控制器都是一遍又一遍地执行同一个基本任务。这些任务包括重定向、转发、渲染模板和访问核心服务,这一切在Symfony2中非常便于管理。
重定向
如果你想将用户重定向到另一页,使用特殊的RedirectResponse类,该类就是为将用户重定向到另一URL而特别设计的:
// ... use Symfony\Component\HttpFoundation\RedirectResponse; class HelloController extends Controller { public function indexAction() { return new RedirectResponse($this->generateUrl('hello', array('name' => 'Lucas'))); } }
generateUrl()方法只是一个在路由服务中调用generate()方法的快捷方法。它需要路由名和一个数组来做为参数,并返回相应的URL。更多详情请参见路由一章。
缺省情况下,redirect方法使用302(临时)重定向。如果要执行301(永久)重定向,请修改第2个参数:
public function indexAction() { return new RedirectResponse($this->generateUrl('hello', array('name' => 'Lucas')), 301); }
转发
你也可以很容易地通过forward()方法内部转发到另一个action。与重定向用户浏览器不同,它产生一个内部的子请求,并调用一个特殊的控制器。forward()方法返回的Response对象在必要的情况下可以进一步修改。Response对象是内部子请求的最终产品:
public function indexAction($name) { $response = $this->forward('AcmeHelloBundle:Hello:fancy', array( 'name' => $name, 'color' => 'green' )); // 进一步修改响应或直接返回它 return $response; }
注意forward()方法使用与路由配置中相同的控制器字符串表示。在本例中目标控制器类将是在AcmeHelloBundle中的Hellocontroller类,发送给控制器类方法的数组做为其参数。当内嵌的控制器进入模板时使用相同的接口(参见内嵌控制器)。目标控制器访问如下所示:
public function fancyAction($name, $color) { // ... 创建和返回一个 Response 对象 }
就这样,当为一个路由创建一个控制器时,fancyAction的参数顺序不是问题。Symfony2检索关键词名(如name),并将其匹配到方法参数名(如$name)。如果你改变参数的顺序,Symfony2也会将正确的值赋给每个变量。
正如其它Controller基类的方法一样,forward方法也只是Symfony2核心功能是快捷方式。转发可以通过http_kernel服务直接完成。转发返回一个Response对象。
$httpKernel = $this->container->get('http_kernel'); $response = $httpKernel->forward('AcmeHelloBundle:Hello:fancy', array( 'name' => $name, 'color' => 'green', ));
渲染模板
虽然没做要求,但大多数控制器最终将渲染一个负责为控制器生成HTML(或其他格式)的模板。renderView()方法渲染模板并返回它的内容。来自模板的内容可以用来创建一个Response对象:
return $this->render('AcmeHelloBundle:Hello:index.html.twig', array('name' => $name));
在上两个示例中,AcmeHelloBundle中的Resources/views/Hello/index.html.twig模板将被渲染。
Symfon模板引擎的更多细节请参见模板章节。
renderView方法是直接使用模板服务的快捷方式。也可以直接使用模板服务:
$templating = $this->get('templating'); $content = $templating->render('AcmeHelloBundle:Hello:index.html.twig', array('name' => $name));
访问其他服务
当继承controller基类后,你可以通过get()方法访问任何Symfony2的服务。你可以需要下列常见服务:
$request = $this->get('request'); $response = $this->get('response'); $templating = $this->get('templating'); $router = $this->get('router'); $mailer = $this->get('mailer');
Symfony2可以有无数个其它的服务,也鼓励你定义自己的服务。详情请参见服务容器章节。
管理错误
如果没找到,将返回一个404响应,通过抛出一个内建的HTTP异常将很容易做到这一点:
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; public function indexAction() { $product = // 从数据库中检索对象 if (!$product) { throw new NotFoundHttpException('The product does not exist.'); } return $this->render(...); }
NotFoundHttpException将返回一个404HTTP响应到浏览器。当在调试模式中查看页面时,堆栈跟踪所有的异常将被显示,因此异常的原因是很容易找出的。
当然,你也可以自由地抛出你控制器中的任何异常类,Symfony2将自动返回HTTP响应代码500。
throw new \Exception('Something went wrong!');
在每个示例中,一个带格式的错误页被显示给最终用户,而一个全调试错误页会被显示给开发者(当在调试模式查看该页时)。这些错误页都是可以自定义的。要想知道更多请阅读“如何自定义错误页”。
管理会话(Session)
即使HTTP协议是无状态的,Symfony2提供了一个好的会话对象来代表客户端(它可以是使用浏览器的人、bot或Web服务)。在两个请求之间,Symfony2通过使用PHP的本地会话来保存cookie中的属性。
$session = $this->get('request')->getSession(); // store an attribute for reuse during a later user request $session->set('foo', 'bar'); // in another controller for another request $foo = $session->get('foo'); // set the user locale $session->setLocale('fr');
这些属性将会在该用户会话的剩余时间中保留。
Flash消息
你也可以为了一个额外的请求在用户会话中保存一些小消息。这在处理表单时很有用:你想重定向并在下个请求中显示一个特定的消息。这些类型的消息被称为“Flash”消息。
让我们看看我们处理表单提交的示例:
public function updateAction() { if ('POST' === $this->get('request')->getMethod()) { // 进行一些处理 $this->get('session')->setFlash('notice', 'Your changes were saved!'); return new RedirectResponse($this->generateUrl(...)); } return $this->render(...); }
在处理请求之后,控制器设置了一个名为notice的flash消息,然后重定向。在下一个action的模板中,下列代码用来渲染该消息:
{% if app.session.hasFlash('notice') %} <div class="flash-notice"> {{ app.session.flash('notice') }} </div> {% endif %}
按照设计,flash消息意味着仅存活一个请求(它们总是“稍纵即逝”)。在本例中它们被设计用来跨跃重定向。
响应对象
对于控制器,唯一的要求就是返回一个Response对象。Response类是一个PHP对于HTTP响应的一个抽象,一个基于文本的消息填充HTTP头,其内容发返客户端:
// create a simple Response with a 200 status code (the default) $response = new Response('Hello '.$name, 200); // create a JSON-response with a 200 status code $response = new Response(json_encode(array('name' => $name))); $response->headers->set('Content-Type', 'application/json');
headers属性是有着许多有用方法的HeaderBag对象用来读取和改变Response头的。头名称的规范化使得 Content-Type和content-type甚至是content_type相同。
请求对象
$request = $this->get('request'); $request->isXmlHttpRequest(); // 是一个 Ajax 请求吗? $request->getPreferredLanguage(array('en', 'fr')); $request->query->get('page'); // 得到一个 $_GET 参数 $request->request->get('page'); // 得到一个 $_POST 参数
如同Response对象一样,请求头被保存在HeaderBag对象中,并可以轻易访问到。
概述
在Symfony中,控制器就是包含创建和返回任意所需逻辑的Response对象的PHP函数。控制器允许我们对于拥有多个页的应用程序保持每个页面被组织进不同控制器类和action方法的逻辑。
Symfony2通过路由匹配决定执行哪个控制器,并解析its_controller参数的文字格式到一个真实Symfony2的控制器。在控制器上的参数与路由上的参数相对应,允许你的控制器去访问来自请求的信息。
控制器可以做任何事并且包含任何逻辑,只要它返回一个Response对象。如果你继承了Controller类,你就有权力访问所有Symfony2的核心服务对象,以及执行大多数常见任务的快捷方法。
本文转自 firehare 51CTO博客,原文链接:http://blog.51cto.com/firehare/586468,如需转载请自行联系原作者