ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 控制器 控制器就是通过从Symfony的`Request`对象读取信息,并返回一个`Response`对象的PHP函数。这个响应可以是HTML页面,JSON,XML,下载文件,重定向,404错误或者其他你能想到的东西。控制器执行你的应用需要渲染页面内容的任意逻辑。 请看以下示例,它将渲染一个输入随机幸运数字的页面: ~~~ // src/AppBundle/Controller/LuckyController.php namespace AppBundle\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Component\HttpFoundation\Response; class LuckyController { /** * @Route("/lucky/number") */ public function numberAction() { $number = mt_rand(0, 100); return new Response( '<html><body>Lucky number: '.$number.'</body></html>' ); } } ~~~ 但是在实际中,你的控制器可能要做更多的工作来创建响应。你或许需要从请求中读取信息,加载数据库资源,发送邮件,或者设置用户会话信息。但最终控制器必须返回客户端一个`Response`对象。 如果你还没有创建第一个页面,请重新查看[创建页面](244171)章节。 # 简单的控制器 由于控制器可以是任意的PHP `Callable`(函数,类方法,或者一个闭包),控制器通常都是一个控制器类的方法。 ~~~ // src/AppBundle/Controller/LuckyController.php namespace AppBundle\Controller; use Symfony\Component\HttpFoundation\Response; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; class LuckyController { /** * @Route("/lucky/number/{max}") */ public function numberAction($max) { $number = mt_rand(0, $max); return new Response( '<html><body>Lucky number: '.$number.'</body></html>' ); } } ~~~ 这里的控制器就是`numberAction()`方法,他在控制器类`LuckyController`中,这个控制器十分直观。 * 第二行:Symfony利用PHP命名空间来命名整个控制器类。 * 第四行:Symfony又利用PHP的命名空间`use`关键字来引入`Response`类,它是控制器必须返回的。 * 第七行:这个类名可以是任何合法的计算机名称,但应该以`Controller`结尾(这点并非必须,但某些快捷功能依赖于这一点。)。 * 第十二行:控制器类的动作方法都以`Action`为后缀(同样的,这也不是必需的,但某些快捷功能需要这样)。由于路由中有`{max}`这个占位符,因此方法中可以有`$max`参数。 * 第十六行:控制器创建并返回`Response`对象。 # 映射URL到控制器 为了查看控制器结果,你需要通过路由映射到这个控制器。在上述代码中则是`@Route("/lucky/number/{max}")`这条注释。 查看页面请访问以下链接:[http://localhost:8000/lucky/number/100](http://localhost:8000/lucky/number/100) 更多详情,请查看[路由](244172) # 基础控制器和服务 方便起见,Symfony提供了可选的基础控制器类`Controller`。如果你继承它,你会额外获取大量有用的方法和服务容器(参看访问其他服务章节):一个类似数组的对象,它可以让你访问系统中每个有用的对象。这些有用的对象被称为服务,而且Symonfy内置了用于渲染Twig模板以及记录日志等等大量的服务。 添加`use`语法引用`Controller`类,修改代码让`LuckyController`继承`Controller`类。 ~~~ // src/AppBundle/Controller/LuckyController.php namespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class LuckyController extends Controller { // ... } ~~~ 助手方法只是Symfony核心功能快捷方式。查看核心功能最好的方法就是阅读`Controller`类代码。 # 生成URL `generateUrl()`方法就是一个通过指定路由生成URL的助手方法。 `$url = $this->generateUrl('blog_show', array('slug' => 'slug-value'));` # 重定向 如果你想重定向用户到另个页面,使用`redirectToRoute()`和`redirect()`助手方法。 ~~~ public function indexAction() { //重定向到“homepage”路由 return $this->redirectToRoute('homepage'); // 执行301永久重定向 return $this->redirectToRoute('homepage', array(), 301); // 重定向到带参数的路由 return $this->redirectToRoute('blog_show', array('slug' => 'my-page')); // 重定向到外部地址 return $this->redirect('http://symfony.com/doc'); } ~~~ 更多信息,请查看[路由](244172) `redirect()`方法不检测目标地址。如果你重定向某些用户提供的URL,你的应用可能就会打开一些未经验证的会带来安全隐患的页面。 `redirectToRoute()`方法仅是创建一个重定向用户的特殊Response对象的快捷方式,它等价于: ~~~ use Symfony\Component\HttpFoundation\RedirectResponse; public function indexAction() { return new RedirectResponse($this->generateUrl('homepage')); } ~~~ # 渲染模板 如果要提供HTML页面,你就需要渲染模板。`render()`方法用来渲染模板,并把模板内容传递给`Response`对象并返回给你。 ~~~ // 渲染文件 app/Resources/views/lucky/number.html.twig return $this->render('lucky/number.html.twig', array('name' => $name)); ~~~ 模板也可以在子目录中。只要避免创建无谓的目录结构就可以。 ~~~ // 渲染文件 app/Resources/views/lottery/lucky/number.html.twig return $this->render('lottery/lucky/number.html.twig', array( 'name' => $name, )); ~~~ Symfony的模板系统和Twig将会在[创建和使用模板](244174) # 访问其他服务 Symonfy包含了大量有用的对象,它们被称作**服务**,它们可以用来渲染模板,发送邮件,查询数据库或者其他你要进行的工作。当你安装一个新的`Bundle`,它会引入更多的**服务** 当继承了基础`Controller`类,你就可以通过控制器类`get()`方法访问Symfony的任何服务。以下是几个你可能需要的常用服务。 ~~~ $templating = $this->get('templating'); $router = $this->get('router'); $mailer = $this->get('mailer'); ~~~ 其他的服务呢?请使用debug:container命令行命令来列出所有的服务。 `php bin/console debug:container` For more information, see the Service Container chapter. 更多信息,请查看[服务容器](http://symfony.com/doc/current/service_container.html)章节 获取容器配置参会苏,请使用`getParameter()`方法。 ~~~ $from = $this->getParameter('app.mailer.from'); ~~~ # 管理错误页面和404页面 页面未找到时,你需要返回`404`页面,在Symfony中需要抛出一个特殊的异常。如果你的控制器继承自`Controller`类,则执行以下代码: ~~~ public function indexAction() { // retrieve the object from database $product = ...; if (!$product) { throw $this->createNotFoundException('The product does not exist'); } return $this->render(...); } ~~~ `createNotFoundException()`方法仅是创建一个特殊的NotFoundHttpException对象的快捷方法,这个异常最终会使Symfony返回一个HTTP 404代码的响应。当然,在控制器里你可以抛出任何异常-Symfony会自动的返回HTTP500响应代码(服务器内部错误)。 `throw new \Exception('Something went wrong!');` 错误页面会展示给终端用户,而开发者会看到一个完整的调试错误页面(当你使用`app_dev.php`前端控制器-参看[imports关键字:加载其他配置文件](http://symfony.com/doc/current/configuration.html#page-creation-environments)) 你也可以自定义错误页面,详看[如何自定义错误页面](http://symfony.com/doc/current/controller/error_pages.html) # 把`Request`对象作为控制器参数 如果你想读取查询参数,获取请求头数据,或者访问上传文件该怎么办?所有这些信息都存储在Symfony的`Request`对象中。为了从控制器中获取它的实例,只须把它添加成方法参数并把类型提示设成`Request` ~~~ use Symfony\Component\HttpFoundation\Request; public function indexAction(Request $request, $firstName, $lastName) { $page = $request->query->get('page', 1); // ... } ~~~ # 管理Session Symfony提供了一个良好的Session对象,你可以用它存储用户请求信息,默认情况下Symfony通过原生PHP会话存储会话数据到Cookie中。 获取Session,请调用`Request`对象的`getSession()`方法。这个方法返回一个声明了如何从session中获取和存储数据的接口:`SessionInterface` ~~~ use Symfony\Component\HttpFoundation\Request; public function indexAction(Request $request) { $session = $request->getSession(); // store an attribute for reuse during a later user request $session->set('foo', 'bar'); // get the attribute set by another controller in another request $foobar = $session->get('foobar'); // use a default value if the attribute doesn't exist $filters = $session->get('filters', array()); } ~~~ 存储属性保留在用户会话剩余session部分。 # 闪存信息 你可以存储特殊的信息,闪存信息,到用户session中。按照设计,闪存信息只会被使用一次,当你获取它们后,系统就会自动从session中剔除它们。这个特性对于制作用户提醒信息是十分有用的 例如,假设你在处理表单提交: ~~~ use Symfony\Component\HttpFoundation\Request; public function updateAction(Request $request) { // ... if ($form->isSubmitted() && $form->isValid()) { // do some sort of processing $this->addFlash( 'notice', 'Your changes were saved!' ); // $this->addFlash() is equivalent to $request->getSession()->getFlashBag()->add() return $this->redirectToRoute(...); } return $this->render(...); } ~~~ 处理请求后,控制器会设置一个闪存信息到session中然后重定向。信息的键(例子中的`notice`)可以是任何字符串,你会用它来获取消息。 在跳转后的页面的模板中(或者在你的布局基础模板中),从session中读取闪存信息。 Twig ~~~ {# app/Resources/views/base.html.twig #} {% for flash_message in app.session.flashBag.get('notice') %} <div class="flash-notice"> {{ flash_message }} </div> {% endfor %} ~~~ PHP ~~~ <!-- app/Resources/views/base.html.php --> <?php foreach ($view['session']->getFlash('notice') as $message): ?> <div class="flash-notice"> <?php echo "<div class='flash-error'>$message</div>" ?> </div> <?php endforeach ?> ~~~ 通常使用notce,warning,error这些键来区分闪存消息的类型,但你可以使用任何你想用的键。 你可以使用`peek()`方法来获取消息,这样它就不会被直接剔除。 # `Request`对象和`Response`对象 之前提到的,框架会传递一个`Request`对象到任何控制器中类型提示是`Request`的参数。 ~~~ use Symfony\Component\HttpFoundation\Request; public function indexAction(Request $request) { $request->isXmlHttpRequest(); // is it an Ajax request? $request->getPreferredLanguage(array('en', 'fr')); // retrieve GET and POST variables respectively $request->query->get('page'); $request->request->get('page'); // retrieve SERVER variables $request->server->get('HTTP_HOST'); // retrieves an instance of UploadedFile identified by foo $request->files->get('foo'); // retrieve a COOKIE value $request->cookies->get('PHPSESSID'); // retrieve an HTTP request header, with normalized, lowercase keys $request->headers->get('host'); $request->headers->get('content_type'); } ~~~ `Request`类提供了几个公共属性和方法用来帮助你获取所需的请求信息。 和Request类似,`Response`对象也有一个公共的头部属性。这就是ResponseHeaderBag类,它有几个很好的方法用来获取和设置相应头部。头部名会归一化,这样Content-Type就会和`content-type`甚至`content_type`等价了。 控制器只需要返回一个`Response`对象。Response类是Http响应的抽象层-基于文本的消息会随同头部信息一起返回客户端。 ~~~ use Symfony\Component\HttpFoundation\Response; // create a simple Response with a 200 status code (the default) $response = new Response('Hello '.$name, Response::HTTP_OK); // create a CSS-response with a 200 status code $response = new Response('<style> ... </style>'); $response->headers->set('Content-Type', 'text/css'); ~~~ 前面有几个用来生成某种响应的特殊类: 针对文件,有`BinaryFileResponse`类,参见[提供文件](http://symfony.com/doc/current/components/http_foundation.html#component-http-foundation-serving-files) 针对响应流,有`StreamResponse`类,参见[响应流化](http://symfony.com/doc/current/components/http_foundation.html#streaming-response) 了解了基本的,你可以继续通过[HttpFoundation组件文档](http://symfony.com/doc/current/components/http_foundation.html#component-http-foundation-request)研究Symfony的`Request`和`Response`类。 # JSON助手工具 `json()`助手函数在Symfony3.1中引入。 从控制器中返回JSON数据,请使用基础控制器类的json()方法。它会返回一个能够自动编码数据的特殊类:`JsonResponse` ~~~ // ... public function indexAction() { // returns '{"username":"jane.doe"}' and sets the proper Content-Type header return $this->json(array('username' => 'jane.doe')); // the shortcut defines three optional arguments // return $this->json($data, $status = 200, $headers = array(), $context = array()); } ~~~ 如果序列化服务可用,传递个`json()`的内容就会用它来编码,否则则使用`json_encode`函数来编码。 # 文件助手 版本3.2新功能。 ~~~ fileAction() { // send the file contents and force the browser to download it // 发送文件,强制浏览器下载文件 return $this->file('/path/to/some_file.pdf'); } ~~~ `file()`方法提供了几个参数来配置行为。 ~~~ use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\HttpFoundation\ResponseHeaderBag; public function fileAction() { // load the file from the filesystem $file = new File('/path/to/some_file.pdf'); return $this->file($file); // rename the downloaded file return $this->file($file, 'custom_name.pdf'); // display the file contents in the browser instead of downloading it return $this->file('invoice_3241.pdf', 'my_invoice.pdf', ResponseHeaderBag::DISPOSITION_INLINE); } ~~~ # 最后的思考 当你要创建一个页面,你最终会在页面中包含逻辑。在Symfony中,这就是控制器,它是PHP函数,你可以所任何事情,只要你最后返回一个`Response`对象。 为了工作顺利,你最好继承基础的`Controller`类,因为: 提供了许多快捷方法(譬如`render()`和`redirectToRoute()`)。 通过`get()`方法获取所有有用的对象(服务)。 在其他章节,你将学到如何在控制器内使用指定的服务来帮助你从数据库持续化对象和获取对象、处理表单提交、管理缓存等等。