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.

Directory Module

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:

Các phương thức hay dùng trong controller
MethodChứ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,
],
Đăng ký theo dõi ủng hộ kênh