owned this note
owned this note
Published
Linked with GitHub
# Controller
控制器是一個你創建的php函數,它能夠獲取http請求(request)並構建和返回一個http響應(response)(作為Symfony的Response對象),Response可能是一個html頁面、xml文檔、一個序列化的json array、圖像、redirection、404錯誤 或者一些其他你能夠想像的。
控制器包含了你應用程式需要渲染頁面的任何邏輯。
以下為symfony簡單的控制器。將輸出```hello word```:
```php=
use Symfony\Component\HttpFoundation\Response;
public function helloAction()
{
return new Response('Hello world!');
}
```
控制器的目標都是相同的:創建並返回一個Response對象。
在這個過程中,它可能會從請求中讀取信息,加載資料庫資源,發送郵件,在用戶session中設置訊息。但是所有的情況,控制器將最終返回 Response 對像給客戶端。
下面是一些常見的例子:
- 控制器A準備了一個首頁上的Response對象。
- 控制器B從請求中讀取{slug}參數,從資料庫中載入文章,並創建一個顯示該p文章的Response對象。如果{slug}不能被資料庫中檢索到,那麼控制器將創建並返回一個帶404狀態碼的Response對象。
- 控制器C處理關於聯絡人的表單提交。它從請求中讀取表單資料,將聯絡人資料存入資料庫並發送包含聯絡人人資訊的電子郵件給網站管理員。最後,它創建一個Response對象將用戶的瀏覽器重定向到聯絡人人表單的“感謝”頁面。
### 請求(request)、控制器、響應(response)的生命週期
symfony處理的每一個請求都會有相同的生命週期。框架會負責把很多重複的任務用一個控制器來執行,控制器執行你自定義的應用代碼:
1. 每個請求都被單個前端控制器(如```app.php```生產環境 或```app_dev.php```開發環境)文件處理,前端控制器負責引導框架;
2. 前端控制器的唯一工作是去初始化Symfony引擎(調用Kernel)並傳入一個Request對象來讓它處理。
3. Symfony核心要求路由器去檢查這個請求;
4. 路由查看並match請求資訊,並將其指向一個特定的路由,該路由決定調用哪個控制器;
5. 執行控制器,控制器中的代碼將創建並返回一個Response對象;
6. HTTP頭和Response對象的內容將發回客戶端。
創建控制器與創建頁面一樣方便,同時映射一個URI到該控制器。

>NOTE
雖然名稱相似,但前端控制器與我們在本章節所說的控制器是不同的,前端控制器是你web/目錄中的一個PHP小文件,所有的請求都直接經過它。一個典型的應用程序將有一個用於生產的前端控制器(如app.php)和一個用於開發的前端控制器(如app_dev.php)。你可以永遠不需要去對前端控制器編輯、查看或者有所擔心。“控制器class”用一種方便的方法組織各自的“controllers”,也被稱為actions,它們都在一個class裡(如,updateAction(), deleteAction(), 等)。所以,在控制器類裡一個控制器就是一個方法。它們會持有你創建的code,並返回Response響應對象。
### 一個簡單的控制器
雖然一個控制器可以是任何的可被調用的PHP(函數、對象的方法或Closure),在Symfony,控制器通常是在控制器class中的一個方法,控制器也常被稱為action:
```php=
// src/AppBundle/Controller/HelloController.php
namespace AppBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
class HelloController
{
public function indexAction($name)
{
return new Response('<html><body>Hello '.$name.'!</body></html>');
}
}
```
這裡面的控制器是```indexAction```方法,它隸屬於一個控制器類```HelloController```。
這個控制器非常的簡單:
- 第2行:Symfony利用php命名空間函數去命名整個控制器class
- 第4行:Symfony充分利用了PHP5.3的名稱空間的功能:use關鍵字導入Response類,是我們控制器必須返回的;
- 第6行:類名是一個串聯的控制器類名稱(例如hello)加上Controller關鍵字。這是一個規定,為控制器提供一致性,並允許它們引用控制器名稱(例如hello)作為路由配置。
- 第8行:在控制器class中的每個action都有著後綴Action,並且這個action名(index)被引用到路由配置文件中。在下一節中,我們將使用路由映射一個URI到該action,並展示如何將路由符號({name})變成action的參數($name);
- 第10行:控制器創建並返回一個Response對象。
### 將URI映射到控制器
我們的新控制器返回一個簡單的HTML頁面。為了能夠在指定的URI中渲染該控制器,我們需要為它創建一個路由。我們將在路由章節中討論路由組件的細節,但現在我們為我們的控制器創建一個簡單路由:
```php=
// src/AppBundle/Controller/HelloController.php
namespace AppBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class HelloController
{
/**
* @Route("/hello/{name}", name="hello")
*/
public function indexAction($name)
{
return new Response('<html><body>Hello '.$name.'!</body></html>');
}
}
```
現在,你來到```/hello/ryan```(例如,如果你使用內建的web服務```http://localhost:8000/hello/ryan```),那麼它就會執行```HelloController::indexAction()```控制器,並且將```ryan```賦給```$name```變量。創建這樣一個頁面就能夠讓路由跟控制器做簡單的關聯。
### 把路由參數傳入控制器
我們現在已經知道路由指向AppBundle中的```HelloController::indexAction()```方法。還有更有趣的就是控制器方法的參數傳遞:
```php=
// src/AppBundle/Controller/HelloController.php
// ...
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
/**
* @Route("/hello/{name}", name="hello")
*/
public function indexAction($name)
{
// ...
}
```
控制器有個參數```$name```,對應所match路由的{name}參數(如果你訪問```/hello/ryan```, 在本例中是```ryan```)。實際上當執行你的控制器時,Symfony在所match路由中match帶參數控制器中的每個參數。所以這個```{name}```值被傳入到```$name```。只需要確保佔位符號(placeholder)的名稱和參數名稱一樣就行。
以下是更有趣的例子,這裡的控制器有兩個參數:
```php=
// src/AppBundle/Controller/HelloController.php
// ...
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class HelloController
{
/**
* @Route("/hello/{firstName}/{lastName}", name="hello")
*/
public function indexAction($firstName, $lastName)
{
// ...
}
}
```
將路由參數映射到控制器參數是十分容易和靈活的。在你開發時請遵循以下思考模式:
1. 控制器參數的順序無關緊要
Symfony可以根據路由參數名match控制器方法參數。換句話說,它可以實現```last_name```參數與```$last_name```參數的match。控制器可以在隨意排列參數的情況下正常工作。
```php=
public function indexAction($lastName, $firstName)
{
// ...
}
```
2. 控制器所需參數必須match路由參數
下面會拋出一個執行時異常(RuntimeException),因為在路由定義中沒有foo參數:
```php=
public function indexAction($firstName, $lastName, $foo)
{
// ...
}
```
如果參數是可選的。下面的例子不會拋出異常:
```php=
public function indexAction($firstName, $lastName, $foo = 'bar')
{
// ...
}
```
3.不是所有的路由參數都需要在控制器上有回應參數的
舉個例子,```last_name```對你控制器不是很重要的話,你可以完全忽略掉它:
```php=
public function indexAction($firstName)
{
// ...
}
```
### 基本 Controller class
出於方便的考慮,Symfony提供了一個可選的基本Controller class。如果你繼承它,不會改變你控制器的任何工作原理,而且你還能夠很容易的繼承一些有幫助方法和服務容器:允許你在系統中訪問每一個有用的對象,類似一個array object一樣。這些有用的object被稱為服務,並且symfony附帶這些服務對象,可以渲染模板,還可以記錄日誌信息(log)等。
在頂端使用```use```語句添加Controller class,然後修改```HelloController```去繼承它。如下所示:
```php=
// src/AppBundle/Controller/HelloController.php
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class HelloController extends Controller
{
// ...
}
```
而無論你是否使用Controller基本class,這些方法只是讓你可以方便地使用Symfony的核心功能。其實查看核心功能的最好方式就是看Controller class本身。
### 生成URL
```generateUrl()```能夠生成一個URL給路由器的輔助方法。
```php=
$url = $this->generateUrl('app_lucky_number', ['max' => 10]);
```
### 重新導向(redirection)
如果你想將用戶重定向到另一個頁面,請使用 redirectToRoute() 方法:
```
public function indexAction()
{
return $this->redirectToRoute('homepage');
// redirectToRoute is equivalent to using redirect() and generateUrl() together:
// return $this->redirect($this->generateUrl('homepage'));
}
```
預設情況下,redirectToRoute()方法執行302(臨時)重定向。如果要執行301(永久)重定向,請修改第2個參數:
```
public function indexAction()
{
return $this->redirectToRoute('homepage', array(), 301);
}
```
從定向到外部網站,使用redirect()並傳入外部URL:
```
public function indexAction()
{
return $this->redirect('http://symfony.com/doc');
}
```
更多細節,請參考路由。
:::success
TIP
比創建一個專門從事重定向用戶的Response對象來說 redirectToRoute()方法是個簡單的捷徑,它相當於:
```php=
use Symfony\Component\HttpFoundation\RedirectResponse;
public function indexAction()
{
return new RedirectResponse($this->generateUrl('homepage'));
}
```
:::
### 渲染模板
如果你使用HTML,你應該想要去渲染一個模板。
render()方法可以用來渲染模板並可以把輸出的內容放到你的Response 對象:
```php=
// renders app/Resources/views/hello/index.html.twig
return $this->render('hello/index.html.twig', array('name' => $name));
```
模板也可以防止在更深層次的子目錄。但應該避免創建不必要的多層結構:
```php=
// renders app/Resources/views/hello/greetings/index.html.twig
return $this->render('hello/greetings/index.html.twig', array(
'name' => $name
));
```
模板可以在任何格式的文件中以一種通用的方式去渲染內容。雖然在大多數情況下,你使用模板來渲染HTML內容,模板也可以很容易地生成JavaScript,CSS,XML或者你能想到的任何其他格式。
>Note
模板的命名模式
你也可以把模板放在一個```bundle```的```Resources/views```目錄下並引用它們的特殊快捷語法,例如```@App/Hello/index.html.twig ```或者 ```@App/layout.html.twig```。這些將分別存放在bundle的```Resources/views/Hello/index.html.twig```和```Resources/views/layout.html.twig```
### 訪問其他服務
Symfony塞了很多有用對象,成為服務。這些服務用於呈現模板,發送電子郵件,查詢資料庫,以及你能夠想到的任何其他的”工作“。當你安裝一個新的bundle,它也可能會帶來更多的服務。
當繼承基本controller class後,你可以通過```get()```方法訪問任何Symfony的服務。下面列舉了一些常見服務:
```php=
$templating = $this->get('templating');
$router = $this->get('router');
$mailer = $this->get('mailer');
```
到底存在哪些服務?我想要看所有的服務,請使用```debug:container```命令行查看:
```$ php bin/console debug:container```
### 管理錯誤和404頁面
如果有些動作沒找到,將返回一個404響應。為此,你需要拋出一個異常。如果你繼承了基礎的Controller class,你可以執行以下操作:
```php=
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響應。
當然,你也可以自由地拋出你控制器中的任何Exception class,Symfony將自動返回HTTP響應代碼500。
```php=
throw new \Exception('Something went wrong!');
```
在每個示例中,一個帶格式的錯誤頁被顯示給最終用戶,而一個全是錯誤的調試頁會被顯示給開發者(當在調試模式```app_dev.php```查看該頁時 - 可查看 配置Symfony(和環境))。
這些錯誤頁都是可以自定義的。
### Request對像作為一個控制器參數
如果你需要獲取查詢參數,抓取請求頭或者獲得一個上傳文件?
這些信息都存儲在Symfony的Request對象。在你的控制器裡獲取它們,只需要添加Request對像作為一個參數並強制類型為Request類:
```php=
use Symfony\Component\HttpFoundation\Request;
public function indexAction($firstName, $lastName, Request $request)
{
$page = $request->query->get('page', 1);
// ...
}
```
### 管理Session
Symfony提供了一個好用的Session對象,它能夠存儲有關用戶的訊息(它可以是使用瀏覽器的人、bot或Web服務)之間的請求。預設情況下,Symfony通過使用PHP的原生Session來保存cookie中的屬性。
去獲取這個session,需要調用Request 對象的```getSession()```方法。這個方法會返回一個```SessionInterface```,它用最簡單的方法來存儲和抓取session的數據:
```php=
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的有效期限內保留。
### 請求和響應對象
正如前面所提到的,框架的Request 對象會作為控制器的參數傳入並強制指定數據類型為Request 類:
```php=
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對像也有一個公共的headers屬性。它是一個```ResponseHeaderBag```它有一些不錯的方法來getting和setting response head。head名稱的規則化使得 Content-Type等於content-type甚至等於content_type,它們都是相同的。
對於控制器,唯一的要求就是返回一個Response對象。 Response類是一個PHP對於HTTP響應的一個抽象,一個基於文本的資料填充HTTP head,其內容返回客戶端:
```php=
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');
```
<br/>
當你創建了一個頁面,你需要在頁面中寫一些業務邏輯的代碼。在symfony中,這些就是控制器,它是一個能夠做任何事情的php函數,目的是把最終的Response對象返回給用戶。
而且你能夠繼承基本Controller class,使工作變得輕鬆。例如,你不用把html代碼寫到控制器,您可以使用render()來渲染模板,並返回模板的內容。