11/08/2018, 20:50

ServiceManager trong ZF2

Service Manager là một Design Pattern quan trọng được đưa vào trong ZF2, Service Manager sẽ trả về cho bạn một đối tượng được đăng ký trong ứng dụng. Service Manager cung cấp rất nhiều cách để bạn đăng ký một service. 1. Factory 1.1 Sử dụng Anonymous Function Với các bạn đã làm quen ...

Service Manager là một Design Pattern quan trọng được đưa vào trong ZF2, Service Manager sẽ trả về cho bạn một đối tượng được đăng ký trong ứng dụng.

Service Manager cung cấp rất nhiều cách để bạn đăng ký một service.

1. Factory

1.1 Sử dụng Anonymous Function

Với các bạn đã làm quen với ZF2 thông qua một số tutorials trên internet sẽ thấy các tutorial hướng dẫn bạn một cách để đăng ký một service bằng cách sử dụng anonymous function

// module.config.php
// các code khác
'service_manager' => array(
    'factories' => array(
        'AlbumModelAlbumTable' => function ($sm) {
          $dbAdapter = $sm->get('ZendDbAdaterAdapter'); 
          return new AlbumTable($dbAdapter);
      },
    ),
),

đây là một cách để đăng ký một service trong ServiceManager nhưng việc dùng anonymous function thì bạn sẽ gặp một số vấn đề sau:
* Bạn sẽ không thể sử dụng tình năng cache config của ZF2.
* Ứng dụng sẽ chậm hơn, chiếm nhiều tài nguyên hơn do phải load code trong anonymous function.

1.2 Sử dụng Factory Class

Bạn có thể sử dụng một class implements ZendServiceManagerFactoryInterface làm factory cho một service.

<?php
// AlbumServiceAlbumTableFactory.php

namespace AlbumService;

use ZendServiceManagerFactoryInterface;
use ZendServiceManagerServiceLocatorInterface;

class AlbumTableFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $sl)
    {
        $dbAdapter = $sl->get('ZendDbAdaterAdapter');
        return new AlbumTable($dbAdapter);
    }
}

hoặc cũng có thể dùng magic methods __invoke để tạo ra Service

<?php
// AlbumServiceAlbumTableFactory.php

namespace AlbumService;

use ZendServiceManagerServiceLocatorInterface;

class AlbumTableFactory
{
    public function __invoke(ServiceLocatorInterface $sl)
    {
        $dbAdapter = $sl->get('ZendDbAdaterAdapter');
        return new AlbumTable($dbAdapter);
    }
}

lúc này thì bạn có thể khai báo trong module.config.php

// module.config.php
// các code khác
'service_manager' => array(
    'factories' => array(
        'AlbumModelAlbumTable' => ' AlbumServiceAlbumTableFactory',
   ),
),

Việc sử dụng class Factory giúp bạn khắc phục được các vấn đề về cache module config. Giúp ứng dụng của bạn có thể nhẹ hơn vì ứng dụng sẽ chỉ phải load code của class lên khi bạn gọi tới $sm->get('AlbumModelAlbumTable') còn không thì phần code factory sẽ không được load.

2. Invokable

Bạn có thể sử dụng invokable khi hàm khởi tạo của class trả về của service không nhận tham số đầu vào.
ví dụ gần gũi nhất là bạn có thể thấy trong file module.config.php của module Application trong Zend Skeleton Application sẽ thấy phần config của controller có dạng

// module.config.php
// các code khác
'controllers' => array(
    'invokables' => array(
        'ApplicationControllerIndex' => 'ApplicationControllerIndexController',
   ),
),

Khi event dispatch được trigger thì ControllerManager sẽ tìm và khởi tạo controller được gọi tới. Lúc này IndexController sẽ được khởi tạo bằng cách sau:

$controllerClass = 'ApplicationControllerIndexController';
return new $controllerClass();

Nếu bạn chỉ cần khởi tạo và trả về đối tượng ( đối tượng này không cần nhận tham số, và cũng không cần set các giá trị đặc biệt). Thì bạn hoàn toàn có thể sử dụng invokable cho các service (có thể là Service, ControllerPlugin, ViewHelper, Controller, InputFilter, Validator) mà không cần phải dùng tới Factory.

3. Service

Bạn hoàn toàn có thể gán trực tiếp một đối tượng thành một service

<?php
// module.config.php
// các code khác
'service_manager' => array(
    'services' => array(
      'AlbumModelAlbum' => new AlbumModelAlbum(),
  ),
),

?>

4. Abstract Factory

AbstractFactory cho phép bạn dùng một class để khởi tạo nhiều đối tượng. Một ví dụ đơn giản về việc bạn có thể dùng TableAbstractFactory để tạo ra các Table

Để dùng AbstractFactory bạn sẽ cần implements ZendServiceManagerAbstractFactoryInterface interface này yêu cầu 2 method là canCreateServiceWithName để trả về cho service manager là Class AbstractFactory này có thể tạo được đối tượng đang được yêu cầu không. Nếu đủ điều kiện thích hợn để khởi tạo đối tượng thì service manager sẽ sử dụng phương thức createServiceWithName để trả về đối tượng cần khởi tạo.

<?php

namespace AlbumService;

use ZendServiceManagerAbstractFactoryInterface;
use ZendServiceManagerServiceLocatorInterface;

class TableAbstractFactory implements AbstractFactoryInterface
{
    public function canCreateServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName)
    {
        return preg_match('/AlbumModel(.+)Table/', $name);
    }

    public function createServiceWithName(ServiceLocatorInterface $sl, $name, $requestedName)
    {
        $dbAdapter = $sl->get('ZendDbAdapterAdapter');
        $class = "" . $name;
        return new $class($dbAdapter);
    }
}

Khai báo trong module.config.php

<?php
// module.config.php
// các code khác
'service_manager' => array(
    'abstract_fatories' => array(
      'AlbumServiceTableAbstractFactory'
  ),
),

?>

5. Alias

Bạn có thể hiểu alias là cách bạn đặt một khác cho một service (service này phải được khai báo các khởi tạo bằng invokable hoặc factory không thể alias cho một đối tượng được tạo bằng AbstractFactory)

// module.config.php
// các code khác
'controllers' => array(
    'invokables' => array(
        'ApplicationControllerIndex' => 'ApplicationControllerIndexController',
   ),
    'alias' => array(
        'ApplicationIndex' => 'ApplicationControllerIndex'
    ),
),

Lúc này trong route bạn có thể sử dụng ApplicationIndex thay cho ApplicationControllerIndex cả 2 key này đều trả về cùng một đối tượng là instance của ApplicationControllerIndexController

6. Shared

Mặc định khi các bạn gọi tới một service qua service manager thì service manager sẽ trả về đối tượng trước đó đã được khởi tạo rồi.
Bạn có thể hiểu là SerivceManager chỉ thực hiện khởi tạo trong lần gọi đầu tiên, n lần sau thì vẫn sẽ trả về cùng một đối tượng.

Khi bạn set shared= false cho một service thì nó sẽ tạo một đối tượng mới trong mỗi lần gọi

<?php
// module.config.php
// các code khác
'service_manager' => array(
    'shared' => array(
      'AlbumModelAlbum' => false,
  ),
),

?>

với cấu hình như trên mỗi lần bạn gọi tới $serviceLocator->get('AlbumModelAlbum'); thì service manager sẽ trả về cho bạn một đối tượng mới.

7. Initializer

Đây là phương thức cho phép chúng ta thay đổi các giá trị của service (ví đụ set một giá trị custom .v.v..) mà không cần phải thay đổi code trong Factory (hoặc service được tạo trong thư viện khác).

Dưới đây là ví dụ về dùng initializer để thêm custom DateTime function cho Doctrine 2

<?php

namespace AlbumService;

use ZendServiceManagerInitializerInterface;
use ZendServiceManagerServiceLocatorInterface;
use DoctrineORMEntityManager;
use DoctrineExtensionsQueryMysqlMonth;
use DoctrineExtensionsQueryMysqlYear;

class EntityManagerInitializer implements InitializerInterface
{
    public function initialize( $instance, ServiceLocatorInterface $serviceLocator )
    {
        if (!$instance instanceof EntityManager) {
            return ;
        }

        $instance->getConfiguration()->addCustomDatetimeFunction('YEAR', Year::class);
        $instance->getConfiguration()->addCustomDatetimeFunction('MONTH', Month::class);

        return $instance;
    }
}

Khai báo trong module.config.php

<?php

// module.config.php
// các code khác
'service_manager' => array(
    'initializers' => array(
        'AlbumServiceEntityManagerInitializer'
    )
),

8. Delegators

Delegator được đưa vào từ Zend Framework 2.2.0 nếu bạn nào muốn sử dụng Delegator thì phải đảm bảo phiên bản Zend Framework của các bạn phải >= 2.2.0

Giống như Initializer. Delegator cho phép chúng ta init một đối tượng khi chúng được khởi tạo, Log, Set ngày tháng v.v... Điểm khác biệt giữa Initializer và Delegator là Delegator chỉ được gọi khi có đối tượng phù hợn ( cụ thế là đối tượng được set bằng key và Delegator nằm trong array value) còn Initializer được gọi trong tất cả các service được gọi tới. Nếu để ý kĩ ví dụ về Initializer bạn sẽ thấy đoạn

if (!$instance instanceof EntityManager) {
    return ;
}

Đây là đoạn chúng ta define đề Initializer chỉ tác động lên đối tượng EntityManager tuy nhiên Initializer lại tác động cả các lớp kế thừa từ lớp EntityManager. Nếu không muốn điều đó xảy ra bạn sẽ phải dùng tới Delegator class của bạn sẽ phải implements ZendServiceDelegatorFactoryInterface interface này yêu cầu bạn phải cung cấp một method createDelegatorWithName tham số $callback chính là phương thức thật sẽ trả về đối tượng. sau khi gọi phương thức $callback() thì bạn đã khởi tạo được đối tượng. Giờ thì bạn sẽ thực hiện các logic khác, Ghi log, set giá trị default, v.v...

<?php 
namespace AlbumService;

use ZendServiceDelegatorFactoryInterface;
use ZendServiceManagerServiceLocatorInterface;
use DoctrineExtensionsQueryMysqlMonth;
use DoctrineExtensionsQueryMysqlYear;

class EntityManagerDelegatorFactory implements DelegatorFactoryInterface
{
    public function createDelegatorWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName, $callback)
    {
        $entityManager = $callback();
        $entityManager->getConfiguration()->addCustomDatetimeFunction('YEAR', Year::class);
        $entityManager->getConfiguration()->addCustomDatetimeFunction('MONTH', Month::class);

        return $entityManager;
    }
}
<?php

// module.config.php
// các code khác
'service_manager' => array(
    'delegators' => array(
        'DoctrineORMEntityManager' => 'AlbumServiceInitializerEntityManagerDelegatorFactory'
    )
),

Giờ Khi bạn gọi tới

$services->get('DoctrineORMEntityManager') thì các nghiệp vụ trong EntityManagerDelegatorFactory sẽ được thực hiện.

0