Controller của Zend Framework
Một controller cung cấp sự tương giữa ứng dụng, model, các view: nó nhận HTTP request gửi đến, nó sử dụng các model và view tương ứng để tạo ra HTTP response.
Controller của module
code của nó thường đặt trong thư mục con có tên Controller
, như hình dưới đây chụp lại cấu trúc thư mục của Skeleton Zend ở ví dụ phần trước.
Zend Skeleton Application đã cung cấp cho bạn module tên là Application và bên trong nó có một controller tên lớp là IndexController. IndexController là controller Index có mã như dưới đây:
<?php namespace Application\Controller; use Zend\Mvc\Controller\AbstractActionController; use Zend\View\Model\ViewModel; class IndexController extends AbstractActionController { public function indexAction() { return new ViewModel(); } }
Từ mã trên, bạn thấy
- Controller thường định nghĩa namespace riêng (Application\Controller).
- Controller Index của module Application được xác định với namespace Application\Controller
- Một controller thường xây dựng từ một lớp kế thừa từ lớp AbstractActionController hoặc AbstractController
- Mặc định, một lớp controller thường chứa một phương thức action là indexAction(), thường trong ứng dụng bạn sẽ xây dựng nhiều phương thức action như vậy trong nó.
ZF3 có cách tổ chức các phương thức action (phương thức gọi tương ứng với phân tích URL) bằng tên action theo sau là hậu tố Action, như indexAction()
Mỗi action trong Controller thi hành một tác vụ và trả một trang Web. Ví dụ bạn xây dựng các action: indexAction là trang chủ, aboutAction() hiện thị trang giới thiệu, contactUsAction() hiện thị trang liên hệ ...
Lớp cơ sở của Controller
Các controller bạn xây dựng, kế thừa từ lớp cơ sở của ZF là AbstractActionController hoặc AbstractController, như vậy tại các phương thức của controller có thể gọi các phương thức sau:
Method | Chức năng |
---|---|
getRequest() | Lấy đối tượng Zend\Http\Request, nó biểu diễn HTTP Resquest |
getResponse() | Lấy đối tượng Zend\Http\Response, nó biểu diễn HTTP Response |
getEventManager() | Lấy đối tượng Event Manager (Zend\EventManager\EventManager) của hệ thống, đối tượng quản lý các sự kiện (phát sự kiện và lắng nghe các sự kiện) |
getEvent() | Trả về đối tượng Zend\Mvc\MvcEvent, biểu diễn sự kiện mà controller đáp ứng yêu cầu (thường là sự kiện dispatch ) |
getPluginManager() | Trả về đối tượng Zend\Mvc\Controller\PluginManager, hệ thống dùng để đăng ký các plugin (chức năng mở rộng cho Controller, ví dụ Plugin URL) |
plugin($name, $options) | Truy cập vào Plugin có tên là $name , ví dụ thay vì gọi $this->url() có thể gọi $this->plugin("url") |
Controller lấy thông tin HTTP Resquest
Như trên đã trình bày, trong các phương thức action của controller có thể gọi getRequest để lấy đối tượng Request, có được đối tượng này sẽ có được các thông tin gửi đến từ khách (xem HTTP Resquest để biết các phương thức hay dùng)
Ví dụ
public function indexAction() { $request = $this->getRequest(); if ($request->isGet()) echo "HTTP Method là GET<br>"; echo "Cookie:<br>"; echo $request->getCookie()."<br>"; return new ViewModel(); }
Đọc các biến POST / GET
Để nhận các biến GET hoặc POST trong yêu cầu HTTP (như PHP thuần ở các biên $_GET, $_POST), bạn hoàn toàn chỉ cần dùng Request lấy được từ phương thức getRequest()
như cách phần trên. Ngoài ra trong Controller còn cung cấp thêm một Plugin là params()
để lấy các biến này linh hoạt hơn
Ví dụ
// Lấy biến từ GET $getVar = $this->params()->fromQuery('var_name', 'default_val'); // Lấy biến từ POST $postVar = $this->params()->fromPost('var_name', 'default_val');
Trong đó 'defautl_val'
là giá trị trả về nếu tên biến 'var_name'
không tồn tại.
Chèn dữ liệu vào HTTP Response
HTTP Response là đối tượng trừu tượng hóa HTTP Message sẽ trả về cho khách, như trên trong Controller lấy đối tượng này bằng phương thức getResponse(). Thường thì ít khi bạn can thiệp vào đối tượng này mà đối tượng này tự động chứa các thông tin theo logic hoạt động của MVC trong Zend Framework. Tuy nhiên, bạn vẫn có thể can thiệp thiết lập các giá trị mong muốn cho HTTP Response sau khi lấy được đối tượng này bằng các phương thức mô tả tại HTTP Respone
Ví dụ:
Thiết lập mã trả về thành 404 (trang không thấy)
$this->getResponse()->setStatusCode(404);
Thiết lập dòng header
$headers = $this->getResponse()->getHeaders(); $headers->addHeaderLine( "Content-type: application/octet-stream");
Thiết lập nội dung trả về
$this->getResponse()->setContent('Some content');
Nếu sau khi thiết lập mà phương thức Action trả về luôn Response, thì quá trình trao đổi MVC của ứng dụng sẽ dừng lại và trả về đúng HTTP Response đó
Ví dụ
public function indexAction() { $headers = $this->getResponse()->getHeaders(); $headers->addHeaderLine( "Content-Type:application/json"); $data = ['name' => 'XT', 'website'=>'xuanthulab.net']; $this->getResponse()->setContent(json_encode($data)); return $this->getResponse(); }
Túi chứa biến trong Controller
Sau khi có được thông tin từ HTTP Request, bạn thực hiện một số tác vụ với dữ liệu đó (thường sẽ sử dụng layer model), sau đó trả dữ liệu về. Dữ liệu trả về từ Action lưu trong các biến, và ZF có một lớp gọi là ViewModel mà Action sử dụng để chuyển các biến vào đó sau đó trả về đối tượng ViewModel như một túi chứa dữ liệu.
Bạn mở action indexAction
của Controller IndexController
trong module Application
của chương trình Skeleton ra bạn thấy nó tạo
và trả về một ViewModel ở dòng cuối:
return new ViewModel();
Lớp ViewModel là một thành phần của zend-view trong xây dựng MVC, ViewModel sẽ được chuyển cho View để dựng mã HTML. Cấu trúc ViewModel và cách sử dụng nó rất đa dạng, ở đây chỉ đề cập đơn giản về thiết lập chứa dữ liệu của action
Thiết lập dữ liệu ngay khi khởi tạo ViewModel
Cú pháp cơ bản sẽ là:
return new ViewModel([ 'name1' => $value1, 'name2' => $value2, //... ]);
Ví dụ:
public function indexAction() { $title = 'Lập trình Zend Framework'; $des = 'Học Zend Framework bắt đầu từ Zend Skeleton Application'; $content = "ZEND"; return new ViewModel([ 'tieude' => $title, 'mota' => $des, 'noidung' => $content ]); }
Như vậy dữ liệu truyền cho ViewModel bạn thấy nó là một mảng kết hợp, mỗi tên khóa key tương ứng với một dữ liệu. Dữ liệu mảng này lưu trong ViewModel và sau này để PhpRender sử dụng để dựng HTML
Một số phương thức của ViewModel
Method | Chi tiết |
---|---|
getVariable($name, $default) |
Trả về giá trị tên biến $name (nếu không tồn tại trả về $default) |
setVariable($name, $value) |
Thiết lập biến. |
setVariables($variables, $overwrite) |
Thiết lập một nhóm biến trong mảng $variables |
getVariables() |
Lấy mảng các biến. |
clearVariables() |
Xóa toàn bộ các biến |
Nếu sử dụng phương thức bạn có thể thực hiện lại ví dụ trên như sau:
public function indexAction() { $title = 'Lập trình Zend Framework'; $des = 'Học Zend Framework bắt đầu từ Zend Skeleton Application'; $content = "ZEND"; $view = new ViewModel(); //gán một biến $view->setVariable('tieude',$title); //gán nhiều biến $view->setVariables(['mota' => $des, 'noidung' => $content]); return $view; }
Trả về báo lỗi từ action của Controller
Trong một số trường hợp, nếu dữ liệu người dùng truy vấn không hợp lệ thì action bạn xây dựng có thể trả về trang thông báo lỗi với mã cụ thể ví dụ 404.
Để làm điều này có thể đơn giản là thiết lập mã 404 có Response và return không xử lý tiếp code của Action
Ví dụ:
public function indexAction() { // Lấy ID ở của từ URL request $id = (int)$this->params()->fromQuery('id', null); // Kiểm tra id, giả sự nếu id == 1000 sẽ báo lỗi if ($id == 1000) { $this->getResponse()->setStatusCode(404); return; } return new ViewModel(); }
Hãy chạy thử ví dụ với địa chỉ http://localhost/zf3/?id=1000 để xem lỗi
Một cách nữa, là phát sinh một Exception
public function indexAction() { // Lấy ID ở của từ URL request $id = (int)$this->params()->fromQuery('id', null); // Kiểm tra id, giả sự nếu id == 1000 sẽ báo lỗi if ($id == 1000) { throw new \Exception("Mã ID=$id không tìm thấy trong dữ liệu"); } return new ViewModel(); }
Đăng ký Controller
Các lớp controller xây dựng trong một module theo cấu trúc và viết mã như trên để hệ thông MVC của Zend Framework nhận ra lớp đó là một controller thì cần phải đăng ký theo hệ thống cấu hình của module
Bạn mở file module.config.php
đoạn mã đăng ký controller vào hệ thống đó là:
'controllers' => [ 'factories' => [ Controller\IndexController::class => InvokableFactory::class, ], ],
Như vậy bạn thấy ở đăng ký controller bằng cách khai báo thêm một phần tử mảng trong key controllers
của hệ thống config,
và cụ thể thêm vào tại key mảng chi tiết factories
(ngoài ra còn có kiểu khai báo trong key chi tiết invokables
(tìm hiểu chi tiết phần sau).
Phần tử mảng biểu diễn đăng ký controller gồm có thành phần key (Controller\IndexController::class) sau này sẽ trở thành tên dùng ám chỉ đến controller trong các thiết lập khác,
và thành value như (InvokableFactory::class).
Với khai báo như vậy trong config, thì ứng dụng Zend Framework với thành phần gọi là Service Manager (chi tiết phần sau) sẽ biết có controller và khởi tạo nó khi cần
Cách mô tả cặp key / value để đăng ký controller như trên có thể thực hiện bằng một số cách thức.
Đăng ký controller với InvokableFactory
Trong ZF có lớp Zend\ServiceManager\Factory\InvokableFactory được dùng để khởi tạo đối tượng lớp (theo mô hình lập trình factory). Áp dụng khai báo đăng ký controller có sử dụng InvokableFactory, thì thực hiện bằng cách khai báo
'factories' => [ class_controller => InvokableFactory::class, ],
class_controller là tên đầy đủ của lớp controller (gồm cả namespace) ví dụ như 'Application\Controller\IndexController'
,
tên này sẽ được lớp InvokableFactory dùng để tìm đến lớp định nghĩa controller khi cần và cũng trở thành tên ám chỉ controller của các cấu hình khác. Trong PHP 7 dùng text 'Application\Controller\IndexController'
có thể thay thế bằng Application\Controller\IndexController::class
Đăng ký với một lớp Factory tự xây dựng
Cách đăng ký ở phần trên, thì mỗi khi cần lấy controller theo tên key đăng ký thì SM sẽ tạo ra lớp controller nếu đối tượng lớp chưa tồn tại bằng khởi tạo hàm tạo mặc định (new Controller()
).
Nhưng nhiều trường hợp bạn muốn trước khi controller được tạo bạn muốn thi hành một số tác vụ, bạn muốn tự tạo ra một số lớp liên quan khác trước, bạn muốn truyền một số loại dữ liệu vào controller khi tạo (inject)... thì lúc này bạn cần xây dựng một lớp khởi tạo (Factory riêng),
và đăng ký controller theo factory đó.
Ví dụ bạn tạo ra một Factory trong thư mục Factory trong thư mục src của module Application, dùng để khởi tạo ra lớp controller IndexController
Bạn khai báo lớp này tên là IndexControllerFactory, theo cấu trúc mẫu như sau:
<?php namespace Application\Controller\Factory; use Zend\ServiceManager\Factory\FactoryInterface; use Application\Service\CurrencyConverter; use Application\Controller\IndexController; class IndexControllerFactory implements FactoryInterface { public function __invoke(ContainerInterface $container, $requestedName, array $options = null) { //các code muốn thi hành trước khi tạo controller //các lớp muốn tạo ra trước khi tạo controller ở đây // khởi tạo controller theo các loại hàm khởi tạo mong muốn // như new IndexController($a, $b ...); // hoặc sử dụng hàm setter để inject return new IndexController($objectinject); } }
Chú ý các Factory bạn tạo đúng theo cấu trúc này, tức triển khai theo giao diện FactoryInterface
Đã có lớp Factory riêng, việc đăng ký controller sẽ thực hiện tương tự như trên
'factories' => [ Controller\IndexController::class => Controller\Factory\IndexControllerFactory::class, ],
Mẹo biến chính lớp Controller thành chính lớp Factory
Bạn đơn giản, thêm vào lớp IndexController phương thức __invoke với cấu trúc như sau:
public function __invoke($container) { //$container chính là đối tượng Service Manager của hệ thống //thực hiện tạo các đối tượng khác ở đây //Quan trọng return $this; }
Lúc đó có thể khai báo đăng ký
'factories' => [ Controller\IndexController::class => Controller\IndexController::class, ],