ServiceManager trong ZF2
PHP
74
Zend Framework 2
1
ZF2
1
White

Ôm Boom viết ngày 01/11/2016

Service Manager

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.

Làm sao để đăng ký một service trong service manager?

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(
        'Album\Model\AlbumTable' => function ($sm) {
          $dbAdapter = $sm->get('Zend\Db\Adater\Adapter'); 
          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 Zend\ServiceManager\FactoryInterface làm factory cho một service.

<?php
// Album\Service\AlbumTableFactory.php

namespace Album\Service;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

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

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

<?php
// Album\Service\AlbumTableFactory.php

namespace Album\Service;

use Zend\ServiceManager\ServiceLocatorInterface;

class AlbumTableFactory
{
    public function __invoke(ServiceLocatorInterface $sl)
    {
        $dbAdapter = $sl->get('Zend\Db\Adater\Adapter');
        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(
        'Album\Model\AlbumTable' => ' Album\Service\AlbumTableFactory',
   ),
),

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('Album\Model\AlbumTable') 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(
        'Application\Controller\Index' => 'Application\Controller\IndexController',
   ),
),

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 = 'Application\Controller\IndexController';
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(
      'Album\Model\Album' => new \Album\Model\Album(),
  ),
),

?>

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 Zend\ServiceManager\AbstractFactoryInterface 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 Album\Service;

use Zend\ServiceManager\AbstractFactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

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

    public function createServiceWithName(ServiceLocatorInterface $sl, $name, $requestedName)
    {
        $dbAdapter = $sl->get('Zend\Db\Adapter\Adapter');
        $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(
      'Album\Service\TableAbstractFactory'
  ),
),

?>

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(
        'Application\Controller\Index' => 'Application\Controller\IndexController',
   ),
    'alias' => array(
        'ApplicationIndex' => 'Application\Controller\Index'
    ),
),

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

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(
      'Album\Model\Album' => false,
  ),
),

?>

với cấu hình như trên mỗi lần bạn gọi tới $serviceLocator->get('Album\Model\Album'); 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 Album\Service;

use Zend\ServiceManager\InitializerInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Doctrine\ORM\EntityManager;
use DoctrineExtensions\Query\Mysql\Month;
use DoctrineExtensions\Query\Mysql\Year;

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(
        'Album\Service\EntityManagerInitializer'
    )
),

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 InitializerDelegator 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 Zend\Service\DelegatorFactoryInterface 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 Album\Service;

use Zend\Service\DelegatorFactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use DoctrineExtensions\Query\Mysql\Month;
use DoctrineExtensions\Query\Mysql\Year;

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(
        'Doctrine\ORM\EntityManager' => 'Album\Service\Initializer\EntityManagerDelegatorFactory'
    )
),

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

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

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Ôm Boom

10 bài viết.
31 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
13 5
Quên mật khẩu là một tính năng mà hấu hết các ứng dụng đều có. Đôi khi nó cũng vô cùng hữu ích. Nhưng đây cũng chính là một backdoor để chiếm tài...
Ôm Boom viết 6 tháng trước
13 5
White
11 2
Nhiều khi chúng ta cần một start một project nhỏ gọn, không cần phải quá cầu kỳ, nhưng lại quá quen thuộc với Eloquent của Laravel. Vậy làm sao để ...
Ôm Boom viết 1 năm trước
11 2
White
9 6
Virtual Host Virtual Host là một cấu hình trong Apache để cho phép nhiều domain cùng chạy trên một máy chủ. Có một khái niệm khác được đề cập tới ...
Ôm Boom viết hơn 2 năm trước
9 6
Bài viết liên quan
White
2 2
Bash script to fast serve Laravel project Lười gõ dòng lệnh quá nên tạo ra cái script để gõ nhanh :D laravelstart.sh /bin/bash if z "$1" ] ...
Vũ Hoàng Chung viết 11 tháng trước
2 2
Male avatar
9 1
Để bắt đầu làm thêm của riêng bạn thì ban đầu bạn phải có một theme trắng ( Blank theme ) để bắt đầu Theme trắng là gồm có các thư mục và file cơ b...
Doan Van Manh viết hơn 2 năm trước
9 1
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
10 bài viết.
31 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!