# 控制器
控制器就是通過從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()`方法獲取所有有用的對象(服務)。
在其他章節,你將學到如何在控制器內使用指定的服務來幫助你從數據庫持續化對象和獲取對象、處理表單提交、管理緩存等等。