Liên hệ
zend framework

Service Manager cơ bản trong Zend Framework

Vai trò của Service Manager là trung tâm dịch vụ của ứng dụng ZF, cách lấy ServiceManager và đăng ký các dịch vụ, cấu hình Service Manager khởi tạo dịch vụ

Service Manager

Bạn hình dùng trong ứng dụng của bạn sẽ có nhiều dịch vụ (service), như chức dịch vụ chuyên xác thực login, dịch vụ chuyên đảm trách truy cập cơ sở dữ liệu, dịch vụ quản lý các sự kiện ứng dụng ... Dịch vụ như vậy xây dựng bởi các lớp (class) và trong hệ thống dịch vụ chỉ tạo ra một đối tượng thực thi của lớp (đã từng khởi tạo rồi thì không khởi tạo nữa).

Trong Zend Framework, lớp có tên là ServiceManager (\Zend\ServiceManager\ServiceManager) chính là trung tâm chứa tất cả các dịch vụ và quản lý các dịch vụ của ứng dụng. Lớp này kế thừa các giao diện interop\Container\ContainerInterfaceZend\ServiceManager\ServiceLacatorInterface

Lớp ServiceManager chính là triển khai mã lập trình theo mô hình Service Locator của Zend Framework

Với Zend Framework thì ServiceManager đã được ứng dụng khởi tạo và kích hoạt trung tâm này (phương thức tĩnh init() của Zend\Mvc\Application, gọi trong file index.php). ServiceManager có phương thức get("name_of_service") để lấy đối tượng dịch vụ nào đó nó đang quản lý.

ServiceManager là một trung tâm quan trọng trong logic ứng dụng, mặc định Zend Skeleton Application được cấu hình để ServiceManager đưa vào trung tâm của nó các dịch vụ khởi tạo sẵn gồm, có thể lấy bằng $ServiceManager->get($Key):

Key - Tên dịch vụ Description
Application Dối tượng Zend\Mvc\Application
ApplicationConfig Mảng cấu hình application.config.php
Config Mảng cấu hình tổng hợp từ các file module.config.php, autoload/global.php, autoload/local.php.
EventManager Đối tượng Zend\EventManager\EventManager quản lý các sự kiện, các listener
SharedEventManager Đối tượng Zend\EventManager\SharedEventManager
ModuleManager Đối tượng lớp Zend\ModuleManager\ModuleManager, quản lý các module
Request Đối tượng Zend\Http\Request biểu diễn HTTP Request khách gửi đến
Response Đối tượng Zend\Http\Response biểu diễn HTTP response sẽ trả về cho khách.
Router Đối tượng Zend\Router\Http\TreeRouteStack, phân tích định tuyển URL
ServiceManager Chính là ServiceManager
ViewManager Đối tượng Zend\Mvc\View\Http\ViewManager biểu diễn layer của mô hình MVC, quản lý hệ thống Render HTML
ControllerManager Đối tượng Zend\Mvc\Controller\ControllerManager biểu diễn layer Controller của mô hình MVC, quả lý các Controller

Ngoài ra còn nhiều dịch vụ tiện ích khác. Nhưng nên nhớ ServiceManager lưu trữ một phiên bản duy nhất của từng đối tượng lớp.

Bạn có thể sử dụng các phương thức sau của ServiceManager:

has($name) kiểm tra dịch vụ có tên $name tồn tại không

get($name) lấy dịch vụ $name

build($name, $options) khởi tạo và lấy dịch vụ mới $name

Lấy đối tượng Service Manager

Mọi dịch vụ của ứng dụng do Service Manager quản lý, do vậy khi có nhu cầu lấy Service Manager có nhiều cách, nhưng thông thường bạn có được đối tượng Service Manager thông qua Inject với hàm tạo đối tượng bằng cách đăng ký dịch vụ trong config

Lấy Service Manager từ onBootstrap

Bạn có thể lấy SM bằng cách thêm phương thức onBootstrap vào lớp Module của module nào bạn cần lấy tùy thích, ví dụng module Application mở file Module.php và thêm phương thức onBootstrap như sau:

public function onBootstrap(\Zend\Mvc\MvcEvent $e)
{
    //Lấy SM
    $serviceManager =  $e->getTarget()->getServiceManager();

    //Có $serviceManager sử dụng tùy tình huống, ví dụ
    //lấy mảng ApplicationConfig, danh sách các module

    $appConfig = $serviceManager->get('ApplicationConfig');
    $modules = $appConfig['modules'];
    var_dump($modules);
}

Ngoài ra Service Manager còn lấy được thông qua quá trình Inject khi khởi tạo các loại dịch vụ, ví dụ Inject vào Controller, Inject vào View ... những kỹ thuật này sẽ trình bày sau khi học về Controller, View ...

Đăng ký dịch vụ với setService

Khi bạn xây dựng một chức năng nào đó, và muốn nó hoạt động như một dịch vụ để các đối tượng khác cần dùng đến thì chỉ việc gọi dịch vụ của bạn, thì quy trình làm như thế nào?

Xây dựng một dịch vụ đơn giản

Ở đây sử dụng chính Skeleton Application cài đặt phần trước để thực hành, giả sử bạn cần tạo ra một lớp dịch vụ chuyên tính diện tích và chu vi hình tròn với tham số bán kính cho trước. Tên lớp đó giả sử bạn đặt tên là CircleApp, ở đây bạn đặt dịch vụ đó trong code của module Application, trong thư mục MyModel

Bước 1) Bạn tạo ra thư mục tên MyModel trong src của Application

Bước 2) Trong MyModel tạo ra file: CircleApp.php

Bước 3) Mở file CircleApp.php cập nhật code như sau

<?php
namespace Application\MyModel;

class CircleApp
{
    //Trả về chu vi theo bán kính $r
    public function Perimeter($r) {
        return 2*$r*3.14;
    }

    //Trả về diện tích theo bán kính $r
    public function Area($r) {
        return $r*$r*3.14;
    }
}

Với cách xây dựng code trong cấu trúc thư mục trên và cách khai báo namespace như vậy, bạn tạo ra một lớp CircleApp có thể sử dụng bất kỳ đâu. Ví dụ trong IndexAction

public function indexAction()
{
    $hinhtron = new \Application\MyModel\CircleApp();
    $dientich = $hinhtron->Area(150);

    echo $dientich;

    return new ViewModel();
}

Nhưng giờ bạn muốn biến CircleApp trở thành một dịch vụ thuộc ServiceManager, hãy thử thực hiện bằng cách đơn giản đầu tiên, là sử dụng phương thức setService của SM với cú pháp

$serviceManager->setService($servicename, $serviceobject);

//$servicename - tên dịch vụ, do bạn tự đặt
//đối tượng dịch vụ

Thực hành: bạn mở Module.php ra, cập nhật onBootstrap như sau:

public function onBootstrap(\Zend\Mvc\MvcEvent $e)
{
    $serviceManager =  $e->getTarget()->getServiceManager();

    $circleservice = new \Application\MyModel\CircleApp();

    $serviceManager->setService("DichvuHinhTron", $circleservice);
}

Bằng cách đó, giờ trong hệ thống Service Manager của bạn đã có một dịch vụ có tên "DichvuHinhTron", bất kỳ ở đâu có SM, bạn có thể lấy dịch vụ này ra dùng như mã sau:

$htron = $serviceManager->get("DichvuHinhTron");
$dientich = $htron->Area(10);

Lưu ý, nếu tên dịch vụ đã có và muốn ghi đề đối tượng mới thì phải thực hiện $serviceManager->setAllowOverride(true);

Đăng ký dịch vụ với setInvokableClass

Phương thức setInvokableClass cũng đăng ký dịch vụ như trên, tuy nhiên chỉ cần chỉ ra tên lớp và SM sẽ tự động khởi tạo dịch vụ khi cần, nhưng lúc này tên dịch vụ chính là tên đầy đủ của lớp, cú pháp:

$serviceManager->setInvokableClass(classname);

Ví dụ trên có thể sửa lại theo kiểu này

$serviceManager->setInvokableClass(\Application\MyModel\CircleApp::class);
//Hoặc $serviceManager->setInvokableClass("\Application\MyModel\CircleApp");

//Lấy dịch vụ
$htron = $serviceManager->get(\Application\MyModel\CircleApp::class);

Đăng ký dịch vụ với InvokableFactory

Một lớp factory thì chỉ có duy nhất một chức năng: tạo đối tượng. Trong SM có phương thức setFactory() dùng để đăng ký một dịch vụ bằng factory. Dùng factory để tạo ra đối tượng trong trường hợp muốn trước khi tạo ra đối tượng mong muốn cần thi hành một số tác vụ, hoặc tạo đối tượng khác, truyền tham số cho đối tượng cần tạo ...

Cú pháp để đăng ký như sau:

$serviceManager->setFactory('servicelassname', 'factoryclassname');

Trong Zend Framework có sẵn lớp factory đơn giản là use Zend\ServiceManager\Factory\InvokableFactory;, nó sử dụng tên lớp và khởi tạo lớp theo hàm tạo mặc định.

Ví dụ: đăng ký dịch vụ CircleApp ở trên bằng factory

<?php
use Zend\ServiceManager\Factory\InvokableFactory;
//...
//...
$serviceManager->setFactory(\Application\MyModel\CircleApp::class, InvokableFactory::class);

Câu lệnh trên kết quả tương tương cách sử dụng setInvokableClass ở trên.

Tạo Factory riêng

Việc tạo ra một Factory dùng để khởi tạo đối tượng và đăng ký trong Service Manager, bạn có thể tạo ra một lớp triển khai giao diện Zend\ServiceManager\Factory\FactoryInterface

Đầu tiên bạn sửa lại lớp CircleApp bằng cách cho thêm thuộc tính $ServiceManager để lưu SM của hệ thống và một hàm setter thiết lập biến này.

class CircleApp
{

    //Service Manager của hệ thống
    private  $ServiceManager;
    public function setServiceManager($ServiceManager)
    {
        $this->ServiceManager = $ServiceManager;
    }


    public function Perimeter($r) {
        return 2*$r*3.14;
    }

    public function Area($r) {
        return $r*$r*3.14;
    }
}

Trở lại ví dụ trên trong thư mục MyModel sẽ tạo một lớp factory tên là CircleAppFactory, tạo ra file CircleAppFactory.php và cập nhật nội dung lớp, chú ký implement giao diện FactoryInterface định nghĩa hàm __invoke theo đúng mẫu về tham số và trả về đối tượng cần tạo.

<?php
namespace Application\MyModel;

use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;

class CircleAppFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container,
                                    $requestedName, array $options = null)
    {
        //Lưu ý:
        //$container chính là Service Manager của hệ thống

        //tạo đối tượng
        $circleappobject = new CircleApp();

        //Inject SM vào đối tượng bằng setter
        $circleappobject->setServiceManager($container);

        //trả về đối tượng
        return $circleappobject;

    }

}

Đã có Factory thì có thể tiến hành đăng ký theo cú pháp trên như sau:

$serviceManager->setFactory(\Application\MyModel\CircleApp::class,
                            \Application\MyModel\CircleAppFactory::class);

Trong ví dụ trên, ở factory đã khởi tạo đối tượng và truyền tham số $container vào CircleApp, đây chính là kỹ thuật Inject, lấy đối tượng Service Manager hay các đối tượng khác.

Ngoài giao diện FactoryInterface, bạn cũng có triển khai giao diện AbstractFactoryInterface để tạo ra factory, bạn phải triển khai hàm __invoke() đồng thời cả hàm canCreate(ContainerInterface $container, $requestedName);, lúc này bạn dùng phương thức setAbstractFactory() của SM để đăng ký. Cách này chỉ khác factory trên ở chỗ SM sẽ gọi hàm canCreate trước nếu trả về để xem có thể tạo đối tượng mới hay không. Sử dụng cách này thì có thể tạo ra nhiều đối tượng lớp dịch vụ chứ không phải duy nhất một lần như factory ở trên.

<?php
namespace Application\MyModel;


use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\AbstractFactoryInterface;

class CircleAppAbstractFactory implements AbstractFactoryInterface
{

    public function canCreate(ContainerInterface $container, $requestedName)
    {
        return class_exists($requestedName);
    }


    public function __invoke(ContainerInterface $container,
            $requestedName, array $options = null)
    {
        $circleappobject = new CircleApp();
        $circleappobject->setServiceManager($container);
        return $circleappobject;
    }
}

Cấu hình Service Manager

Thay vì việc phải sử dụng các phương thức của SM để đăng ký dịch vụ, bạn có thể sử dụng hệ thống cấu hình của ứng dụng, module để đăng ký. Mở các file cấu hình cấp độ ứng dụng hoặc cấp độ module thiết lập cấu hình dạng như sau:

<?php
return [
    //..
    // Đăng ký dịch vụ dưới key này
    'service_manager' => [
        'services' => [
           // Đăng ký đối tượng dịch vụ
        ],
        'invokables' => [
           // Đăng ký các lớp dạng invokable
        ],
        'factories' => [
           // Đăng ký các lớp dạng factory
        ],
        'abstract_factories' => [
           // Đăng ký các lớp dạng abstract factory

        ],
        'aliases' => [
           // Khóa tắt cho tên các dịch vụ (tên tắt)

        ],
        'shared' => [
            // Các dịch vụ dạng non-shared (tạo mới mỗi lầ get)
        ]
    ],

    //...
];

Ví dụ: mở file module.config.php thêm vào nội dung:


'service_manager' => [
    'factories' => [
        \Application\MyModel\CircleApp::class
                => \Application\MyModel\CircleAppFactory::class
    ],
    
    'aliases' => [
        //gọi get(\Application\MyModel\CircleApp::class)
        //tương đương get('Abc')
        'Abc' => \Application\MyModel\CircleApp::class
    ]
    ]

Đây là cách làm mà không cần gọi trức tiếp các phương thức của ServiceManager, mà hệ thống ứng dụng tự động nạp config, và kích hoạt SM nhận các tham số trên và khởi tạo theo khai báo và kết quả nhận được là tương tự. Cách này khuyến khích sử dụng

Vui lòng đăng ký ủng hộ kênh