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 ... Các dịch vụ như vậy được xây dựng từ các lớp khác nhau, các dịch vụ này lại phụ thuộc vào một dịch vụ khác, nên rất cần có một cơ chế quả lý tập trung.
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\ContainerInterface
và Zend\ServiceManager\ServiceLacatorInterface
, có nghĩa là nó hỗ trợ tiêu chuẩn PSR-11
:
xem psr-11
Lớp ServiceManager chính là triển khai mã lập trình theo mô hình Service Locator của Zend Framework, tức triển khai nguyên lý IoC - Nguyên lý lập trình IoC Inversion of control
Tích hợp Zend ServiceManager vào dự án thì gõ lệnh Composer:
composer require zendframework/zend-servicemanager
Ở đây ta sẽ tìm hiểu Zend ServiceManager trong dự án độc lập sau đó xem nó áp dụng
vào chương trình khung Zend Framework như thế nào.
Tạo một thư mục dự án, đặt tên SM-Example
, trong thư mục đó gõ lệnh:
composer require zendframework/zend-servicemanager
Mở thư mục code bằng VSC ra để biên tập, đầu tiên sẽ thiết lập Autoload của dự án, với
namespace có tên SMExample
sẽ nạp từ thư mục src
(hãy tạo thư mục này). Vậy mở file composer.json
thêm vào Autoload như sau:
{ "require": { "zendframework/zend-servicemanager": "^3.4" }, "autoload": { "psr-4": { "SMExample\\": "src/" } } }
Tiếp theo để xuất thiết lập cho autoload.php
của vendor
gõ lệnh sau:
composer dump
Cấu hình và lấy dịch vụ từ Service Manager
Mỗi dịch vụ đăng ký vào Service Manager có một chuỗi là tên, key trỏ đến dịch vụ đó. Dịch vụ đó có thể là một đối tượng, một lớp có khả năng khởi tạo, một lớp khởi tạo qua Factory ...
Khởi tạo một Service Manager, cần cung cấp cho nó một mảng cấu hình, trong mảng này có định nghĩa tên và dịch vụ, mảng có cấu trúc như sau:
// Triển khai thực tế, mảng config thương lưu ở một file .php, rồi include vào $config = [ 'services' => [ // Nơi cấu hình đăng ký đối tượng là dịch vụ: // tên-dịch-vụ => đối-tượng ], 'invokables' => [ // Nơi đăng ký các lớp dạng invokable, khởi tạo không cần tham số // tên-dịch-vụ => lớp-tạo-dịch-vụ ], 'factories' => [ // Nơi Đăng ký các lớp dạng factory, khởi tạo cần tham số // tên-dịch-vụ => lớp-tạo-dịch-vụ ], 'abstract_factories' => [ // Đăng ký các lớp dạng abstract factory, tạo đối tượng căn cứ theo tên ], 'aliases' => [ // Khóa tắt cho tên các dịch vụ (tên tắt) // tên-dịch-vụ-tắt => tên-dịch-vụ ], 'shared' => [ // Các dịch vụ dạng non-shared (tạo mới mỗi lần get) ] ]; $servicemanager = new ServiceManager($config);
Để kiểm tra có một dịch vụ nào đó không dùng phương thức has
,
để lấy dịch vụ dùng phương thức get
if ($servicemanager->has("nameservice")) { $service = $servicemanager->get("nameservice"); //... other code }
Có thể dùng build($name, $options) khởi tạo và lấy dịch vụ mới $name
Để thực hành, trước tiên tạo ra một lớp dịch vụ, ClassA
như sau:
<?php declare(strict_types=1); namespace SMExample; class ClassA { private $mgs; public function __construct() { $this->mgs = uniqid(); } };
Đăng ký đối tượng và lớp Invokable vào SM
Đối với các đối tượng, để đăng ký vào SM thì khai báo trong key services
của mảng cấu hình config hoặc gọi trực tiếp
$servicemanager->setService("nameservice", $service_object)
.
Đối với các lớp dịch vụ có thể khởi tạo mà phương thức khởi tạo không cần tham số thì có
thể đăng ký lớp đó như là dịch vụ bằng cách khai báo trong phần invokables
của cấu hình
hoặc dùng phương thức
$servicemanager->setInvokableClass("nameservice", "classService");
Ví dụ: test1.php
<?php declare(strict_types=1); use SMExample\ClassA; use Zend\ServiceManager\ServiceManager; require "vendor/autoload.php"; // KHỞI TẠO SM VÀ ĐĂNG KÝ DỊCH VỤ VÀO SM $classA = new ClassA(); // Đối tượng dịch vụ $config = [ 'services' => [ 'service1' => $classA // đăng ký đối tượng với tên service1 ], 'invokables' => [ 'service2' => ClassA::class, // đăng ký lớp dịch vụ invokable, với tên service2 'service3' => "SMExample\ClassA", // đăng ký invokable với tên service3 ClassA::class => ClassA::class // đăng ký invokable với tên trùng tên đầy đủ lớp ] ]; $servicemanager = new ServiceManager($config); // ĐỌC CÁC DỊCH VỤ TỪ SM $s1 = $servicemanager->get('service1'); $s2 = $servicemanager->get('service2'); $s3 = $servicemanager->get('service3'); $s4 = $servicemanager->get(ClassA::class); var_dump($s1, $s2, $s3, $s4);
Như vậy những tên dịch vụ invokable trỏ đến cùng
tên lớp chỉ khởi tạo một đối tượng dịch vụ, dịch vụ service2
,
service3
, ClassA::class
xác định 1 dịch vụ.
Đăng ký dịch vụ với Factory
Với loại dịch vụ invokable ở trên, lớp khởi tạo dịch vụ phải có hàm tạo không tham số, còn nếu lớp đó khởi tạo có tham số thì cần dùng kỹ thuật Factory, một lớp gọi là Factory thì nó được dùng để khởi tạo ra lớp khác.
Ví dụ, tạo ra lớp dịch vụ /src/ClassB.php
<?php declare(strict_types=1); namespace SMExample; class ClassB { /*** * @var ClassB - lưu dịch vụ ClassA, Inject qua khởi tạo */ private $service; private $mgs; public function __construct($mgs, ClassA $service) { $this->mgs = $mgs; $this->service = $service; } };
Do lớp này khởi tạo cần có 2 tham số, nên không thể đăng ký là Invokable như ví dụ trên,
nó cần được đăng ký vào khu vực factories
, tuy nhiên cần có một lớp chuyên
để khởi tạo gọi là Factory, lớp này triển khai từ giao diện
Zend\ServiceManager\Factory\FactoryInterface
,
cần định nghĩa phương thức __invoke
trả về đối tượng cần tạo.
Ví dụ định nghĩa lớp ClassBFactory
dùng để tạo đối tượng lớp ClassB
như sau:
// src/ClassBFactory.php <?php declare(strict_types=1); namespace SMExample; use Interop\Container\ContainerInterface; use Zend\ServiceManager\Factory\FactoryInterface; class ClassBFactory implements FactoryInterface { public function __invoke(ContainerInterface $container, $requestedName, array $options = null) { // $requestedName - tên dịch vụ cần tạo (tham khảo tên này nếu cần) $serviceA = $container->get(ClassA::class); // lấy dịch vụ từ SM $serviceB = new ClassB("Dịch vụ B", $serviceA); return $serviceB; } }
Giờ có thể đăng ký vào SM ở khu vực factories
như sau (test2.php):
<?php declare(strict_types=1); use SMExample\ClassA; use SMExample\ClassB; use SMExample\ClassBFactory; use Zend\ServiceManager\ServiceManager; require "vendor/autoload.php"; $config = [ 'invokables' => [ ClassA::class => ClassA::class, ], 'factories' => [ ClassB::class => ClassBFactory::class ], ]; $servicemanager = new ServiceManager($config); // Lấy dịch vụ từ Service Manager $sb = $servicemanager->get(ClassB::class); var_dump($sb);
Cũng cần lưu ý, có sẵn lớp tên là InvokableFactory
, cho phép đưa lớp khởi tạo không
tham số vào khu vực factories
<?php declare(strict_types=1); use SMExample\ClassA; use SMExample\ClassB; use SMExample\ClassBFactory; use Zend\ServiceManager\Factory\InvokableFactory; use Zend\ServiceManager\ServiceManager; require "vendor/autoload.php"; $config = [ 'factories' => [ ClassB::class => ClassBFactory::class, ClassA::class => InvokableFactory::class ], ]; $servicemanager = new ServiceManager($config); // Lấy dịch vụ từ Service Manager $sb = $servicemanager->get(ClassB::class); var_dump($sb);
Đăng ký dịch vụ với FactoryAbstract
Bạn có thể triên khai giao diện Zend\ServiceManager\Factory\AbstractFactoryInterface
để xây dựng một lớp gọi là AbstractFactory, lớp triển khai này được dùng để đăng ký các
dịch vụ, khai báo ở khu vực abstract_factories
. Lớp này, khi nhận một tên
truy cập (tên lớp), nó cho biết có tạo được dịch vụ không (phương thức canCreate)
và thực hiện tạo dịch vụ theo tên đó (phương thức __invoke)
Ví dụ triển khai một lớp có tên MyAbstractFactory
<?php namespace SMExample; use Interop\Container\ContainerInterface; use Zend\ServiceManager\Factory\AbstractFactoryInterface; class MyAbstractFactory implements AbstractFactoryInterface { /** * Trả về true (tạo được dịch vụ) nếu tên truy vấn dịch vụ trùng với tên lớp ClassA, ClassB */ public function canCreate(ContainerInterface $container, $requestedName) { return in_array($requestedName, [ ClassA::class, ClassB::class ]); } // Tạo dịch vụ theo tên lớp truy vấn $requestedName public function __invoke(ContainerInterface $container, $requestedName, array $options = null) { $ob = null; switch ($requestedName) { case ClassA::class : $ob = new ClassA(); break; case ClassB::class : $serviceA = $container->get(ClassA::class); $ob = new ClassB("ClassB", $serviceA); break; default: break; } return $ob; } }
Áp dung: (test3.php)
<?php declare(strict_types=1); use SMExample\ClassA; use SMExample\ClassB; use SMExample\MyAbstractFactory; use Zend\ServiceManager\ServiceManager; require "vendor/autoload.php"; $config = [ 'abstract_factories' => [ MyAbstractFactory::class ], ]; $servicemanager = new ServiceManager($config); // Lấy dịch vụ từ Service Manager $s1 = $servicemanager->get(ClassA::class); $s2 = $servicemanager->get(ClassB::class); var_dump($s1, $s2);
Như vậy chỉ cần đưa một lớp MyAbstractFactory::class
đăng ký vào SM, thì
truy vấn đến ClassA::class
nó sẽ tạo ra dịch vụ tương tứng, truy
vấn đến ClassB::class
thì tạo dịch vụ lớp ClassB
Service Manager trong Zend Framework Skeleton
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.
Lấy Service Manager từ onBootstrap
Bạn có thể lấy SM của ứng dụng khung 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 ...
Cấu hình các dịch vụ cho Service Manager của ZF Skeleton
Cấu hình đăng ký các dịch vụ vào SM của ứng dụng khung khai báo trong các file config
(như trong module.config.php của Module nào đó, trong autoload/global.php, autoload/local.php ...).
Mảng config của SM khai báo sau key service_manager
<?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 ] ] //...
Ngoài ra trong ứng dụng khung Zend Framework có nhiều lớp nhất là các PluginManager chính là lớp kế thừa Service Manager