控制器
控制器是一個你創(chuàng)建的php函數(shù),它能夠獲取http請求信息并構建和返回一個http響應(作為Symfony的Response對象),Response可能是一個html頁面、xml文檔、一個序列化的json數(shù)組、圖像、重定向、404錯誤或者一些其他你能夠想像的??刂破靼四銘贸绦蛐枰秩卷撁娴娜魏芜壿?。
看一下symfony簡單的控制器。下面控制器將輸出 hello word
:
use Symfony\Component\HttpFoundation\Response; public function helloAction(){ return new Response('Hello world!');}
控制器的目標都是相同的:創(chuàng)建并返回一個Response
對象。在這個過程中,它可能會從請求中讀取信息,加載數(shù)據(jù)庫資源,發(fā)送郵件,在用戶session中設置信息。但是所有情況下,控制器將最終返回 Response
對象給客戶端。
沒有什么神奇的不用擔心還有別的要求!下面是一些常見的例子:
控制器A準備了一個首頁上的
Response
對象。-控制器B從請求中讀取
{slug}
參數(shù),從數(shù)據(jù)庫中載入一條博文,并創(chuàng)建一個顯示該博文的Response
對象。如果{slug}
不能被數(shù)據(jù)庫中檢索到,那么控制器將創(chuàng)建并返回一個帶404狀態(tài)碼的Response
對象。控制器C處理關于聯(lián)系人的表單提交。它從請求中讀取表單信息,將聯(lián)系人信息存入數(shù)據(jù)庫并發(fā)送包含聯(lián)系人信息的電子郵件給網(wǎng)站管理員。最后,它創(chuàng)建一個
Response
對象將用戶的瀏覽器重定向到聯(lián)系人表單的“感謝”頁面。
請求、控制器、響應的生命周期 ?
symfony處理的每一個請求都會有相同的生命周期??蚣軙撠煱押芏嘀貜偷娜蝿沼靡粋€控制器最終執(zhí)行,控制器執(zhí)行你自定義的應用代碼:
每個請求都被單個前端控制器(如
app.php
生產(chǎn)環(huán)境 或app_dev.php
開發(fā)環(huán)境)文件處理,前端控制器負責引導框架;前端控制器的唯一工作是去初始化Symfony引擎(調(diào)用
Kernel
)并傳入一個Request
對象來讓它處理。Symfony核心要求路由器去檢查這個請求;
路由查看并匹配請求信息,并將其指向一個特定的路由,該路由決定調(diào)用哪個控制器;
執(zhí)行控制器,控制器中的代碼將創(chuàng)建并返回一個Response對象;
HTTP頭和Response對象的內(nèi)容將發(fā)回客戶端。
創(chuàng)建控制器與創(chuàng)建頁面一樣方便,同時映射一個URI到該控制器。
雖然名稱相似,但前端控制器與我們在本章節(jié)所說的控制器是不同的,前端控制器是你web/
目錄中的一個PHP小文件,所有的請求都直接經(jīng)過它。一個典型的應用程序?qū)⒂幸粋€用于生產(chǎn)的前端控制器(如app.php
)和一個用于開發(fā)的前端控制器(如app_dev.php
)。你可以永遠不需要去對前端控制器編輯、查看或者有所擔心。本章的“控制器類”用一種方便的方法組織各自的“controllers”,也被稱為actions,它們都在一個類里(如,updateAction()
, deleteAction()
, 等)。所以,在控制器類里一個控制器就是一個方法。它們會持有你創(chuàng)建的代碼,并返回Response
響應對象。
一個簡單的控制器 ?
雖然一個控制器可以是任何的可被調(diào)用的PHP(函數(shù)、對象的方法或Closure
),在Symfony,控制器通常是在控制器類中的一個方法,控制器也常被稱為action:
// src/AppBundle/Controller/HelloController.phpnamespace 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命名空間函數(shù)去命名整個控制器類
第4行:Symfony充分利用了PHP5.3的名稱空間的功能:
use
關鍵字導入Response
類,是我們控制器必須返回的;第6行:類名是一個串聯(lián)的控制器類名稱(例如
hello
)加上Controller
關鍵字。這是一個約定,為控制器提供一致性,并允許它們引用控制器名稱(例如hello
)作為路由配置。第8行:在控制器類中的每個action都有著后綴
Action
,并且這個action名(index
)被引用到路由配置文件中。在下一節(jié)中,我們將使用路由映射一個URI到該action,并展示如何將路由占位符({name}
)變成action的參數(shù)($name
);第10行:控制器創(chuàng)建并返回一個
Response
對象。
將URI映射到控制器 ?
我們的新控制器返回一個簡單的HTML頁。為了能夠在指定的URI中渲染該控制器,我們需要為它創(chuàng)建一個路由。 我們將在路由章節(jié)中討論路由組件的細節(jié),但現(xiàn)在我們?yōu)槲覀兊目刂破鲃?chuàng)建一個簡單路由:
Annotations:// src/AppBundle/Controller/HelloController.phpnamespace 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>'); }}
YAML:# app/config/routing.ymlhello: path: /hello/{name} # uses a special syntax to point to the controller - see note below defaults: { _controller: AppBundle:Hello:index }.
XML:<!-- app/config/routing.xml --><?xml version="1.0" encoding="UTF-8" ?><routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> <route id="hello" path="/hello/{name}"> <!-- uses a special syntax to point to the controller - see note below --> <default key="_controller">AppBundle:Hello:index</default> </route></routes>
PHP:// app/config/routing.phpuse Symfony\Component\Routing\Route;use Symfony\Component\Routing\RouteCollection; $collection = new RouteCollection();$collection->add('hello', new Route('/hello/{name}', array( // uses a special syntax to point to the controller - see note below '_controller' => 'AppBundle:Hello:index',))); return $collection;
現(xiàn)在,你來到 /hello/ryan
(例如,如果你使用內(nèi)置的web服務http://localhost:8000/hello/ryan
),那么它就會執(zhí)行HelloController::indexAction()
控制器,并且將ryan
賦給$name
變量。創(chuàng)建這樣一個頁面就能夠讓路由跟控制器做簡單的關聯(lián)。
簡單吧?
把路由參數(shù)傳入控制器 ?
我們現(xiàn)在已經(jīng)知道路由指向AppBundle中的HelloController::indexAction()
方法。還有更有趣的就是控制器方法的參數(shù)傳遞:
// src/AppBundle/Controller/HelloController.php// ...use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; /** * @Route("/hello/{name}", name="hello") */public function indexAction($name){ // ...}
控制器有個參數(shù)$name
,對應所匹配路由的{name}
參數(shù)(如果你訪問/hello/ryan
, 在本例中是ryan
)。實際上當執(zhí)行你的控制器時,Symfony在所匹配路由中匹配帶參數(shù)控制器中的每個參數(shù)。所以這個{name}
值被傳入到$name
。只需要確保占位符的名稱和參數(shù)名稱一樣就行。
以下是更有趣的例子,這里的控制器有兩個參數(shù):
Annotations:// src/AppBundle/Controller/HelloController.php// ... use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; class HelloController{ /** * @Route("/hello/{firstName}/{lastName}", name="hello") */ public function indexAction($firstName, $lastName) { // ... }}
YAML:# app/config/routing.ymlhello: path: /hello/{firstName}/{lastName} defaults: { _controller: AppBundle:Hello:index }
XML:<!-- app/config/routing.xml --><?xml version="1.0" encoding="UTF-8" ?><routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> <route id="hello" path="/hello/{firstName}/{lastName}"> <default key="_controller">AppBundle:Hello:index</default> </route></routes>
PHP:// app/config/routing.phpuse Symfony\Component\Routing\Route;use Symfony\Component\Routing\RouteCollection; $collection = new RouteCollection();$collection->add('hello', new Route('/hello/{firstName}/{lastName}', array( '_controller' => 'AppBundle:Hello:index',))); return $collection;
將路由參數(shù)映射到控制器參數(shù)是十分容易和靈活的。在你開發(fā)時請遵循以下思路:
Symfony可以根據(jù)路由參數(shù)名匹配控制器方法參數(shù)。換句話說,它可以實現(xiàn)last_name
參數(shù)與$last_name
參數(shù)的匹配。控制器可以在隨意排列參數(shù)的情況下正常工作。
public function indexAction($lastName, $firstName){ // ...}
2.控制器所需參數(shù)必須匹配路由參數(shù)
下面會拋出一個運行時異常(RuntimeException
),因為在路由定義中沒有foo
參數(shù):
public function indexAction($firstName, $lastName, $foo){ // ...}
如果參數(shù)是可選的,那該多好。下面的例子不會拋出異常:
public function indexAction($firstName, $lastName, $foo = 'bar'){ // ...}
3.不是所有的路由參數(shù)都需要在控制器上有響應參數(shù)的
如果,舉個例子,last_name
對你控制器不是很重要的話,你可以完全忽略掉它:
public function indexAction($firstName){ // ...}
你也可以從你的路由器傳入其他的參數(shù)到你的控制器參數(shù)。請看 如何從路由向控制器傳遞額外的信息
Controller基類 ?
出于方便的考慮,Symfony提供了一個可選的Controller
基類。如果你繼承它,它不會改變你控制器的任何工作原理,而且你還能夠很容易的繼承一些幫助方法和服務容器(可看,下面的訪問其他容器):允許你在系統(tǒng)中訪問每一個有用的對象,類似一個數(shù)組對象一樣。這些有用的對象被稱為服務,并且symfony附帶這些服務對象,可以渲染模板,還可以記錄日志信息等。
在頂部使用use語句添加Controller
類,然后修改HelloController
去繼承它。如下所示:
// src/AppBundle/Controller/HelloController.phpnamespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class HelloController extends Controller{ // ...}
而無論你是否使用Controller
基類,這些幫助方法只是讓你可以方便地使用Symfony的核心功能。其實查看核心功能的最好方式就是看Controller
類本身。
生成URL ?
generateUrl()
能夠生成一個URL給路由器的輔助方法。
重定向 ?
如果你想將用戶重定向到另一個頁面,請使用 redirectToRoute()
方法:
public function indexAction(){ return $this->redirectToRoute('homepage'); // redirectToRoute is equivalent to using redirect() and generateUrl() together: // return $this->redirect($this->generateUrl('homepage'));}
默認情況下,redirectToRoute()
方法執(zhí)行302(臨時)重定向。如果要執(zhí)行301(永久)重定向,請修改第2個參數(shù):
public function indexAction(){ return $this->redirectToRoute('homepage', array(), 301);}
從定向到外部網(wǎng)站,使用redirect()
并傳入外部URL:
public function indexAction(){ return $this->redirect('http://symfony.com/doc');}
更多細節(jié),請參考框架起步之路由。
比創(chuàng)建一個專門從事重定向用戶的Response
對象來說 redirectToRoute()
方法是個簡單的捷徑,它相當于:
use Symfony\Component\HttpFoundation\RedirectResponse; public function indexAction() { return new RedirectResponse($this->generateUrl('homepage')); }
渲染模板 ?
如果你使用HTML,你應該想要去渲染一個模板。render()
方法可以用來渲染模板并可以把輸出的內(nèi)容放到你的Response
對象:
// renders app/Resources/views/hello/index.html.twig return $this->render('hello/index.html.twig', array('name' => $name));
模板也可以防止在更深層次的子目錄。但應該避免創(chuàng)建不必要的深層結(jié)構:
// renders app/Resources/views/hello/greetings/index.html.twigreturn $this->render('hello/greetings/index.html.twig', array( 'name' => $name));
模板可以在任何格式的文件中以一種通用的方式去渲染內(nèi)容。雖然在大多數(shù)情況下,你使用模板來渲染HTML內(nèi)容,模板也可以很容易地生成JavaScript,CSS,XML或者你能想到的任何其他格式。要了解如何使不同的模板格式,參考創(chuàng)建并使用模板中的“模板格式”。
訪問其他服務 ?
Symfony塞了很多有用對象,成為服務。這些服務用于呈現(xiàn)模板,發(fā)送電子郵件,查詢數(shù)據(jù)庫,以及你能夠想到的任何其他的”工作“。當你安裝一個新的bundle,它也可能會帶來更多的服務。
當繼承controller
基類后,你可以通過get()
方法訪問任何Symfony的服務。下面列舉了一些常見服務:
$templating = $this->get('templating'); $router = $this->get('router'); $mailer = $this->get('mailer');
到底存在哪些服務?我想要看所有的服務,請使用debug:container
命令行查看:
$ php bin/console debug:container
更多信息請看 服務容器
管理錯誤和404頁面 ?
如果有些動作沒找到,將返回一個404響應。為此,你需要拋出一個異常。如果你繼承了基礎的Controller
類,你可以執(zhí)行以下操作:
public function indexAction(){ // retrieve the object from database $product = ...; if (!$product) { throw $this->createNotFoundException('The product does not exist'); } return $this->render(...);}
createNotFoundException()
方法創(chuàng)建了一個特殊的NotFoundHttpException
對象,來觸發(fā)symfony內(nèi)部的http的404響應。
當然,你也可以自由地拋出你控制器中的任何Exception
類,Symfony將自動返回HTTP響應代碼500。
throw new \Exception('Something went wrong!');
在每個示例中,一個帶格式的錯誤頁被顯示給最終用戶,而一個全是錯誤的調(diào)試頁會被顯示給開發(fā)者(當在調(diào)試模式app_dev.php
查看該頁時 - 可查看 配置Symfony(和環(huán)境))。
這些錯誤頁都是可以自定義的。要想知道更多請閱讀“如何自定義錯誤頁”。
Request對象作為一個控制器參數(shù) ?
如果你需要獲取查詢參數(shù),抓取請求頭或者獲得一個上傳文件?這些信息都存儲在Symfony的Request
對象。在你的控制器里獲取它們,只需要添加Request
對象作為一個參數(shù)并強制類型為Request
類:
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,需要調(diào)用Request
對象的getSession()
方法。這個方法會返回一個SessionInterface
,它用最簡單的方法來存儲和抓取session的數(shù)據(jù):
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的有效期內(nèi)保留。
Flash Message ?
你也可以在用戶session中存儲一些指定的消息,這個消息被稱為“flash message”(消息條子)。證據(jù)規(guī)定,flash消息只能夠使用一次:當你取回它們的時候它們會自動的消失。這種特性使得“flash”消息特別適合存儲用戶通知。
讓我們看看我們處理表單提交的示例:
use Symfony\Component\HttpFoundation\Request; public function updateAction(Request $request){ $form = $this->createForm(...); $form->handleRequest($request); if ($form->isValid()) { // do some sort of processing $this->addFlash( 'notice', 'Your changes were saved!' ); // $this->addFlash is equivalent to $this->get('session')->getFlashBag()->add return $this->redirectToRoute(...); } return $this->render(...);}
在處理請求之后,控制器設置了一個名為notice的flash消息,然后重定向。名稱(notice
)并不重要 – 它就是一個確定消息的識別符。
接下來是模板(或者是更好的,在你的基礎布局模板),從session中讀取每一條信息:
XML:{% for flash_message in app.session.flashBag.get('notice') %} <div class="flash-notice"> {{ flash_message }} </div>{% endfor %}
PHP:<?php foreach ($view['session']->getFlash('notice') as $message): ?> <div class="flash-notice"> <?php echo "<div class='flash-error'>$message</div>" ?> </div><?php endforeach ?>
通常使用的notice
,warning
和error
作為不同類型提示信息的鍵,但是你可以使用任何你需要的鍵。
你可以使用peek()
方法來獲取消息,它可以讓消息保存住.
請求和響應對象 ?
正如前面所提到的,框架的Request
對象會作為控制器的參數(shù)傳入并強制指定數(shù)據(jù)類型為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
對象也有一個公共的headers
屬性。它是一個ResponseHeaderBag
它有一些不錯的方法來getting和setting響應頭。頭名稱的規(guī)范化使得 Content-Type
等于content-type
甚至等于content_type
,它們都是相同的。
對于控制器,唯一的要求就是返回一個Response對象。Response類是一個PHP對于HTTP響應的一個抽象,一個基于文本的消息填充HTTP頭,其內(nèi)容發(fā)返客戶端:
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');
也有一些特殊的類能夠簡化某種響應:
對于JSON:這是一個
JosnResponse
??刹榭?創(chuàng)建一個JOSN響應。對于文件操作:這是
BinaryFileResponse
??刹榭?Serving Files。對于流響應,有
StreamedResponse
。請看:流化一個響應
JSON Helper ?
3.1json()
helper從symfony3.1開始被引入。
返回JSON類型在基于API的應用程序中是日益流行的。出于這個原因,控制器基類定義了json()
方法,來創(chuàng)建一個JsonResponse
并自動編碼給定的內(nèi)容:
// ...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());}
如果Serializer服務在你的應用程序中啟用,那么內(nèi)容傳遞到json()
就會自動編碼。否則,你將要使用json_encode函數(shù)。
你現(xiàn)在已經(jīng)了解了Symfony Request
和Response
對象的基礎,你還可以參考HttpFoundation組件以了解更多。
創(chuàng)建靜態(tài)頁面 ?
你可以在沒有控制器的情況下創(chuàng)建一個靜態(tài)頁面(只需要一個路由和模板)。參考 不使用自定義控制器時如何渲染模板。
總結(jié) ?
當你創(chuàng)建了一個頁面,你需要在頁面中寫一些業(yè)務邏輯的代碼。在symfony中,這些就是控制器,它是一個能夠做任何事情的php函數(shù),目的是把最終的Response
對象返回給用戶。
而且你能夠繼承Controller
基類,使工作變得輕松。例如,你不用把html代碼寫到控制器,您可以使用render()
來渲染模板,并返回模板的內(nèi)容。
在其他章節(jié)中,你會看到控制器是如何使用從數(shù)據(jù)庫中讀取對象和進行持久化的,處理表單提交,操作緩存以及更多。
Keep Going ?
接下來,集中學習使用Twig渲染模板。