所謂控制器是一個PHP函式,用來讀取Request物件中的資訊,並建立和回傳一個Response物件,這個回應(response)可以是HTML頁面、JSON、XML、供下載的檔案、重新定向(redirect)、404錯誤等等,控制器根據應用的需求,執行任何可能的邏輯去呈現畫面。
簡易的控制器
雖然控制器可以是任何可呼叫的(函式、物件中的方法或Closure),但大多會是一個控制器類內的方法(a method inside a controller class):// src/Controller/LuckyController.php namespace App\Controller; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; class LuckyController { /** * @Route("/lucky/number/{max}", name="app_lucky_number") */ public function number($max) { $number = random_int(0, $max); return new Response( 'Lucky number: '.$number.'' ); } }控制器就是方法number(),被放在名為LuckyController的控制器類。
這個控制器非常容易理解:
- line 2: Symfony利用PHP的命名空間(namespace)功能來命名整個控制器類。
- line 4: Symfony也是利用PHP的命名空間功能來命名整個控制器類:關鍵字use輸入(import)一個控制器必要回傳的類(class)Response。
- line 7: 實作上,類名可隨便取,但按慣例會用Controller當作後綴字。
- line 12: 由於有個{max}的通配符在路由,操作方法(action method)會有一個$max的參數。
- line 16: 控制器建立和回傳一個Response物件
對應URL到控制器
想要看到控制器呈現的結果,你必須透過路由將URL與其對應,這邊的設定是透過註解式路由,寫成@Route("/lucky/number/{max}")來達成對應。想看頁面,請用瀏覽器到這個URL:
欲見更多關於路由資訊,請看路由。
基礎的控制器和服務
Symfony為了讓大家輕鬆點,有一個基礎控制器AbstractController可以選擇使用,你可以延伸了解一些輔助方式(helper methods)。在控制器類的上方加上use的語句,並修改LuckyController以擴展(extend)它:
// src/Controller/LuckyController.php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; class LuckyController class LuckyController extends AbstractController { // ... }就是這樣!你現在已經接觸了像$this->render()這樣的方法,接下來還有很多其他的方法你會慢慢學到。
生成URL
generateUrl()方法是一個輔助方法,可以針對給定的路由產生URL:$url = $this->generateUrl('app_lucky_number', ['max' => 10]);
重新導向(Redirecting)
如果你像要將使用者重新導向其他頁面,可以使用redirectToRoute()和redirect()兩個方法:use Symfony\Component\HttpFoundation\RedirectResponse; // ... public function index() { // redirects to the "homepage" route return $this->redirectToRoute('homepage'); // redirectToRoute is a shortcut for: // return new RedirectResponse($this->generateUrl('homepage')); // does a permanent - 301 redirect return $this->redirectToRoute('homepage', [], 301); // redirect to a route with parameters return $this->redirectToRoute('app_lucky_number', ['max' => 10]); // redirects to a route and maintains the original query string parameters return $this->redirectToRoute('blog_show', $request->query->all()); // redirects externally return $this->redirect('http://symfony.com/doc'); }
渲染模板(rendering template)
如果你要回傳HTML,你會想要用渲染模板,方法render()會渲染模板,並封裝成Response物件給你:// renders templates/lucky/number.html.twig return $this->render('lucky/number.html.twig', ['number' => $number]);模板和Twig相關的說明,在文章『建立和使用模板』(原文)有更多的說明。
獲取服務(fetching services)
Symfony包含相當多有用的物件,稱為服務(service)。可以用來渲染模板、發送信件、查詢資料庫和任何你想得到的工作。如果你要在控制器裡使用服務,請用它的類名(或介面(interface))宣告參數,Symfony會自動提供你所需的服務:
use Psr\Log\LoggerInterface; // ... /** * @Route("/lucky/number/{max}") */ public function number($max, LoggerInterface $logger) { $logger->info('We are logging!'); // ... }太棒了!
其他還有哪些服務類型可以使用?如果想知道,請在控制台執行指令debug:autowiring:
php bin/console debug:autowiring
如果需要控制參數的確切值,可以使用其名稱綁定(bind)參數:
// 省略程式碼和其他服務一樣,你可以在控制器中用常規構造函數注入(regular constructor injection)。
想要知道更多關於服務的資訊,請看服務容器(Service Container)這篇文章。
產生控制器
為了節省時間,你可以安裝Symfony Maker,並藉由它產生一個新的控制器類:php bin/console make:controller BrandNewController created: src/Controller/BrandNewController.php如果想要產生針對Doctrine實體(entity)完整的CRUD,執行:
php bin/console make:crud Product
錯誤處理及404頁面
如果找不到東西,應該回傳一個404的回應,請拋(throw)一個特殊形態的例外(exception)來達成:use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; // ... public function index() { // retrieve the object from database $product = ...; if (!$product) { throw $this->createNotFoundException('The product does not exist'); // the above is just a shortcut for: // throw new NotFoundHttpException('The product does not exist'); } return $this->render(...); }方法createNotFoundException()只是一個NotFoundHttpException特殊物件的捷徑,最終會觸發Symfony內的一個404 HTTP回應。
如果你拋一個延伸的例外或HttpException的實例(instance),Symfony會自帶適當的HTTP狀態碼(status code),否則回應就會是HTTP status code 500。
// this exception ultimately generates a 500 status error throw new \Exception('Something went wrong!');每個情況下,錯誤頁面(error page)是顯示給一般使用者看,完整的除錯頁面(full debug error page)則是供開發者(i.e. 當你在除錯模式(Debug mode) - The parameters Key: Parameters (Variables))。
若想要客製化顯示給使用者看的錯誤頁面,請參考如何將錯誤頁面客製化的文章(How to Customize Error Pages article)。
回應物件作為控制器參數
如果想要讀取查詢參數、取得請求的標頭(header)或取得上船的檔案?所有的資訊都存在Symfony的Request物件中,要在你的控制器內取得,只要用請求類名宣告成參數:use Symfony\Component\HttpFoundation\Request; public function index(Request $request, $firstName, $lastName) { $page = $request->query->get('page', 1); // ... }更多資訊請參考Request Object(原文)
管理Session
Symfony提供session的服務,可以讓你從請求中取得更多關於使用者的資訊,預設狀態下就可以使用session,不過只有在讀取或寫入的時候才會啟動。Session儲存區和其他設定都是由config/packages/framework.yaml檔案內的framework.session configuration管控。
要取得session,請新增一個參數並宣告型態為SessionInterface:
use Symfony\Component\HttpFoundation\Session\SessionInterface; public function index(SessionInterface $session) { // stores an attribute for reuse during a later user request $session->set('foo', 'bar'); // gets the attribute set by another controller in another request $foobar = $session->get('foobar'); // uses a default value if the attribute doesn't exist $filters = $session->get('filters', []); }儲存的屬性會放在session哩,跟使用者其他的session一起。
想要了解更多,請看Session。
Flash Messages
你可以儲存一些特別的訊息在使用者的session,稱為"Flash",設計上,flash message只會使用一次: 一旦你檢索後就會自動從session裡面移除,這個特性讓flash message特別適合存放用戶通知。例如:想像你需要處理一個表單(form)的遞交:
use Symfony\Component\HttpFoundation\Request; public function update(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(...); }在處理完這個請求後,控制器會設定flash message到session並重新導向,訊息的鍵(key)(這個例子裡,鍵就是notice)可以任意指定:你可以用這個key來檢索訊息。
在下一頁的模板中(如果放在你的基礎模板中會更好),用app.flashes()來讀取session內任何flash message:
{# templates/base.html.twig #} {# read and display just one flash message type #} {% for message in app.flashes('notice') %}常見使用notice、warning和error作為不同類型flash message的鍵(key),當然你也可以使用任何符合你需求的鍵。{{ message }}{% endfor %} {# read and display several types of flash messages #} {% for label, messages in app.flashes(['success', 'warning']) %} {% for message in messages %}{{ message }}{% endfor %} {% endfor %} {# read and display all flash messages #} {% for label, messages in app.flashes %} {% for message in messages %}{{ message }}{% endfor %} {% endfor %}
請求和回應
如同上述,Symfony會傳一個Request物件給任何有宣告參數Request類的控制器:use Symfony\Component\HttpFoundation\Request; public function index(Request $request) { $request->isXmlHttpRequest(); // is it an Ajax request? $request->getPreferredLanguage(['en', 'fr']); // retrieves GET and POST variables respectively $request->query->get('page'); $request->request->get('page'); // retrieves SERVER variables $request->server->get('HTTP_HOST'); // retrieves an instance of UploadedFile identified by foo $request->files->get('foo'); // retrieves a COOKIE value $request->cookies->get('PHPSESSID'); // retrieves an HTTP request header, with normalized, lowercase keys $request->headers->get('host'); $request->headers->get('content-type'); }Request類有幾個公開屬性(public properties)和方法,可返回有關請求的所有資訊。
跟Request一樣,Response物件也有公開屬性headers,ResponseHeaderBag提供一些好用的方法來設定回應的標頭檔(header),標頭名稱(header names)一般標準使用Content-Type,等同於content-type和content_type。
控制器唯一的條件就是要回傳Response物件:
use Symfony\Component\HttpFoundation\Response; // creates a simple Response with a 200 status code (the default) $response = new Response('Hello '.$name, Response::HTTP_OK); // creates a CSS-response with a 200 status code $response = new Response(''); $response->headers->set('Content-Type', 'text/css');有個特殊的類可以讓部分的回應簡單一點,下面會列舉一部份。想要了解更多關於Request和Response(還有特殊類的Response)的資訊,請查看HttpFoundation component documentation。
回傳JSON回應
如果要在控制器內回傳JSON,可以用輔助方法json(),這會回傳一個已經自動編碼(encode)的JsonResponse特殊物件。// ... public function index() { // returns '{"username":"jane.doe"}' and sets the proper Content-Type header return $this->json(['username' => 'jane.doe']); // the shortcut defines three optional arguments // return $this->json($data, $status = 200, $headers = [], $context = []); }如果能在你的應用裡使用serializer service,可以幫你把資料連載(serialize)成JSON格式,不然也可以用函式json_encode。
媒體文件回應(Streaming File Responses)
你可以在控制器內用輔助方式file()操作檔案:public function download() { // 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 download() { // 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物件給使用者。為了簡化一點,你可能會延伸基礎控制器AbstractController的類,因為這樣可以讓你快速取得方法render()和redirectToRoute()的捷徑。
在其他文章裡,你會學到如何在控制器裡使用特定的服務,幫助你從資料庫獲取物件、處理表單提交、處理緩存等等。
繼續前進!
接下來,來學習『Symfony4.2入門教學【第五篇】模板(Templates)』(rendering template with Twig)。文章原文:
https://symfony.com/doc/4.2/controller.html
系列文章:
- Symfony4.2入門教學【第一篇】安裝
- Symfony4.2入門教學【第二篇】建立第一個頁面(Routes)
- Symfony4.2入門教學【第三篇】路由(Routing)
- Symfony4.2入門教學【第五篇】模板(Templates)
- Symfony4.2入門教學【第六篇】設定(Configuration)
沒有留言:
張貼留言