A free and open-source book on ZF3 for beginners

Translation into this language is not yet finished. You can help this project by translating the chapters and contributing your changes.

3.10. Service Manager

Você pode imaginar a sua aplicação como um conjunto de serviços(services). Por exemplo, você pode ter um serviço de autenticação responsável por fazer login os usuários do site, um serviço gerenciador de entity por acessar o banco de dados, service manager de eventos responsável por acionar eventos e entregá-los aos event listeners, etc.

No Zend Framework, a classe ServiceManager é um container centralizado para todos serviços da aplicação. O service maanger é incorporado ao componente Zend\ServiceManager como a classe ServiceManager. O diagrama de herança de classes é mostrado na imagem 3.5 logo abaixo:

Imagem 3.5. Service manager diagrama de herança de classes Imagem 3.5. Service manager diagrama de herança de classes

O service manager é criado na inicialização do aplicativo (dentro do init()) método estático da classe Zend\Mvc\Application). Os serviços padrão disponíveis através do service manager são apresentados na tabela 3.1. Esta tabela está incompleta, porque o número real de serviços registrados no gerenciador de serviços pode ser muito maior.

Table 3.1. Serviços Padrões
Nome do Serviço Descrição
Application Permite acessar o singleton da classe Zend\Mvc\Application.
ApplicationConfig Array de configuração extraída do arquivo application.config.php.
Config Array de configuração Incorporado do module.config.php mesclado com autoload/global.php e autoload/local.php .
EventManager Permite uma nova instância da classe Zend\EventManager\EventManager. O event manager permite enviar eventos (triggers) e anexar event listeners.
SharedEventManager Permite instânciar singleton da classe Zend\EventManager\SharedEventManager O Shared Event Manager permite usar event listeners definidos por outras classes e componentes.
ModuleManager Permite acessar o singleton da classe Zend\ModuleManager\ModuleManager. module manager é responsável por carregar os módulos do aplicativo..
Request O singleton da classe Zend\Http\Request. Representa a solicitação HTTP recebida do cliente.
Response O singleton da classe Zend\Http\Response. Representa a resposta HTTP que será enviada ao cliente.
Router O singleton de Zend\Router\Http\TreeRouteStack. Executa o roteamento de URL.
ServiceManager O Próprio Service manager em si.
ViewManager O singleton da classe Zend\Mvc\View\Http\ViewManager. Responsável por preparar o view da página

Um serviço é tipicamente uma classe PHP arbitrária, mas nem sempre. Por exemplo, quando ZF3 carrega os arquivos de configuração e incorpora os dados em arrays, salva as arrays no service manager como serviços (!): ApplicationConfig e Config. O primeiro é o array carregado a partir do arquivo de configuração em nível de aplicativo application.config.php, e a mais recente é a matriz mesclada dos arquivos de configuração no nível do módulo e carregada automaticamente arquivos de configuração em nível de aplicativo. Assim, no gerenciador de serviços, você pode armazenar qualquer coisa você quer: uma classe PHP, uma variável simples ou um array.

Na tabela 3.1, você pode ver que no ZF3 quase tudo pode ser considerado como um serviço. O service manager é registrado como um serviço. Além disso, a classe Application é também registrado como um serviço.

Uma coisa importante que você deve observar sobre os serviços é que eles são tipicamente rmazenado em uma única instância única (isso também é chamado o padrão singleton). Obviamente, você não precisa da segunda instância da classe @ Application (nesse caso você Eu teria um pesadelo.

Mas existe uma exceção importante da regra acima. Pode ser confuso no início, mas o EventManager ão é um singleton. Toda vez que você recupera o service manager do event manager, você recebe um objeto novo(new). Isso é feito por motivos de desempenho e para evitar possíveis conflitos de eventos entre componentes diferentes. Discutiremos isso mais adiante na seção Sobre o Event Manager mais adiante neste capítulo.

O service manager define vários métodos necessários para localizar e recuperar um serviço do service manager (veja a tabela 3.2 abaixo).

Table 3.2. ServiceManager métodos
Nome do Método Descrição
has($name) Verifica se esse serviço está registrado.
get($name) Recupera a instância de um serviço registrado.
build($name, $options) Sempre retorna uma nova instância do serviço solicitado.

Você pode testar se um serviço está registrado, passando seu nome para o service manager's. através do método has(). Ele retorna um booleano true se o serviço estiver registrado, ou false se o serviço com esse nome não estiver registrado.

Você pode recuperar um serviço pelo seu nome mais tarde com a ajuda do método get() do service manager's. Esse método usa um único parâmetro que representa o nome do serviço. Veja o exemplo:

<?php 

// Retrieve the application config array.
$appConfig = $serviceManager->get('ApplicationConfig');

// Use it (for example, retrieve the module list).
$modules = $appConfig['modules'];

E o método build() sempre cria uma nova instância do serviço quando você o chama (comparando com get(), que normalmente cria a instância do serviço apenas uma vez e a retorna em solicitações posteriores).

Você normalmente recuperará serviços do service manager não em qualquer lugar do seu código, mas dentro de um factory. Uma factory é um código responsável pela criação de um objeto. Ao criar o objeto, você pode recuperar serviços dos quais depende o service manager e passar esses serviços (dependências) para o construtor (constructor) do objeto. Isso também é chamado de injeção de dependência(dependency injection).

Se você tiver alguma experiência com o Zend Framework 2, poderá perceber que as coisas agora estão um pouco diferentes do que antes. No ZF2, havia o padrão ServiceLocator que permitia obter dependências do gerenciador de serviços em qualquer parte da sua aplicação (em controllers, services, etc.) No ZF3, você tem que passar dependências explicitamente. É um pouco mais chato, mas remove as dependências "ocultas" e torna seu código mais claro e fácil de entender.

3.10.1. Registrando um Serviço

Ao escrever seu site, você frequentemente precisará registrar seu próprio serviço no service manager. Uma das maneiras de registrar um serviço é usando o método setService() do service manager. Por exemplo, vamos criar e registrar a classe de serviço de conversor de moeda, que será usado, por exemplo, em uma página do carrinho de compras para converter moeda EUR em USD

<?php 
// Define a namespace where our custom service lives.
namespace Application\Service;

// Define a currency converter service class.
class CurrencyConverter 
{
    // Converts euros to US dollars.
    public function convertEURtoUSD($amount) 
    {
        return $amount*1.25;
    }
	
    //...
}

Acima, nas linhas 6-15, definimos um exemplo da classe CurrencyConverter (para simplificar, implementamos apenas um único método convertEURtoUSD() que é capaz de converter euros em dólares americanos).

// Create an instance of the class.
$service = new CurrencyConverter();
// Save the instance to service manager.
$serviceManager->setService(CurrencyConverter::class, $service);

No exemplo acima, instanciamos a classe com o operador new, e registramos com o service manager com o o método setService() (assumimos que a variável $serviceManager é da classe type Zend\ServiceManager\ServiceManager, e que foi declarado em algum outro lugar).

O método setService() usa dois parâmetros: o nome do serviço e a instância do serviço. O nome do serviço deve ser exclusivo em todos os outros serviços possíveis.

Depois que o serviço é armazenado no service manager, você pode recuperá-lo pelo nome em qualquer aplicação com a ajuda do método get() do service manager. Olhe para o seguinte exemplo:

<?php 
// Retrieve the currency converter service.
$service = $serviceManager->get(CurrencyConverter::class);

// Use it (convert money amount).
$convertedAmount = $service->convertEURtoUSD(50);

3.10.2. Nomes de serviço

Serviços diferentes podem usar diferentes estilos de nomenclatura. Por exemplo, o mesmo serviço de conversor de moeda pode ser registrado sob os diferentes nomes: CurrencyConverter, currency_converter e assim por diante. Para introduzir alguma convenção de nomenclatura uniforme, recomenda-se registrar um serviço seu nome de classe totalmente, como podemos ver abaixo:

$serviceManager->setService(CurrencyConverter::class);

No exemplo acima, usamos a palavra-chave class. Está disponível desde o PHP 5.5 e é usado para classe resolução de nomes. CurrencyConverter::class é expandido para o nome completo da classe, como \Application\Service\CurrencyConverter.

3.10.3. Substituindo um serviço existente

Se você está tentando registrar o nome do serviço que já está presente, no setService() método exibirá um errro. Mas às vezes você deseja substituir o serviço com o mesmo nome (para substituí-lo por um novo). Para este propósito, você pode usar o método setAllowOverride() do service manager:

<?php 
// Allow to replace services
$serviceManager->setAllowOverride(true);

// Save the instance to service manager. There will be no exception
// even if there is another service with such a name.
$serviceManager->setService(CurrencyConverter::class, $service);

Acima, o método setAllowOverride() apenas aceita um único parâmetro booleano que define se para permitir que você substitua o serviço "CurrencyConverter" se tal nome já estiver presente, ou não.

3.10.4. Registrando Classes Invocáveis (Invokable)

O que é ruim com o método setService() é que você tem que criar a instância do serviço antes que você realmente precise. Se você nunca usar o serviço, a instanciação do serviço será desperdice o tempo e a memória. Para resolver esse problema, o service manager fornece a você método setInvokableClass().

<?php 
// Register an invokable class
$serviceManager->setInvokableClass(CurrencyConverter::class);

No exemplo acima, passamos para o service manager o nome completo da classe do o serviço em vez de passar sua instância. Com esta técnica, o serviço será instanciado pelo service manager somente quando alguém chamar o método get(CurrencyConverter::class). Isso também é chamado de lazy loading.

Os serviços geralmente dependem uns dos outros. Por exemplo, o serviço de conversor de moeda pode usar o serviço gentity manager para ler as taxas de câmbio do banco de dados. A desvantagem do método setInvokableClass() é que ele não permite passar parâmetros (dependências) para o serviço na instanciação de objetos. Para resolver esse problema, você pode usar factories, conforme descrito abaixo.

3.10.5. Registrando uma Fábrica (Factory)

A factory é uma classe que pode fazer apenas uma coisa - para criar outros objetos.

Você registra uma factory para um serviço com o método setFactory() do service manager:

A fábrica mais simples é InvokableFactory é análoga ao método da seção anterior setInvokableClass().

<?php 
use Zend\ServiceManager\Factory\InvokableFactory;

// This is equivalent to the setInvokableClass() method from previous section.
$serviceManager->setFactory(CurrencyConverter::class, InvokableFactory::class);

Depois de ter registrado a fábrica, você pode recuperar o serviço do service manager como de costume com o método get(). O serviço será instanciado somente quando você recuperá-lo do service manager (lazy loading).

Às vezes, a instanciação de serviços é mais complexa do que apenas criar a instância de serviço com o operador new (como InvokableFactory faz). Você pode precisar passar alguns parâmetros para o construtor do serviço ou Invoque alguns métodos de serviço logo após a construção. Esta lógica de instanciação complexa pode ser encapsulado dentro de sua própria classe factory. A classe de fábrica geralmente implementa o FactoryInterface[Zend\ServiceManager\Factory\ FactoryInterface]:

<?php
namespace Zend\ServiceManager\Factory;

use Interop\Container\ContainerInterface;

interface FactoryInterface
{
    public function __invoke(ContainerInterface $container, 
                        $requestedName, array $options = null);
}

Como vemos na definição do FactoryInterface, a classe da factory deve fornecer o método mágico __invoke retornando a instância de um único serviço. O service manager é passado para o método __invoke como o parâmetro $container; pode ser usado durante a construção de o serviço para acessar outros serviços (para injetar dependências). O segundo argumento ($requestedName) é o nome do serviço. O terceiro argumento ($options) pode ser usado para passar alguns parâmetros para o serviço, e é usado somente quando você solicita o serviço com o método build() do service manager.

Como exemplo, vamos escrever uma factory para o nosso serviço de conversão de moeda (observe o código abaixo). Nós não usamos lógicas de construção complexas para o nosso serviço CurrencyConverter, mas para serviços mais complexos. serviços, talvez seja necessário usar um.

<?php 
namespace Application\Service\Factory;

use Zend\ServiceManager\Factory\FactoryInterface;
use Application\Service\CurrencyConverter;

// Factory class
class CurrencyConverterFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, 
                     $requestedName, array $options = null) 
    {
        // Create an instance of the class.
        $service = new CurrencyConverter();	
	
        return $service;
    }
}

Tecnicamente, no ZF3 você pode usar a mesma classe da factory para instanciar vários serviços que possuem código de instanciação (para esse propósito, você pode usar o argumento $requestedName passado para o método __invoke() da factory). No entanto, principalmente você criará uma fábrica diferente para cada serviço.

3.10.6. Registrando uma Fábrica Abstrata (Abstract Factory)

Ainda mais complexo de uma factory é quando você precisa determinar na execução em real time em que os nomes dos serviços devem ser registrados. Para tal situação, você pode usar uma abstract factory. Uma classe de fábrica abstrata deve implementar a interface AbstractFactoryInterface:

<?php 
namespace Zend\ServiceManager\Factory;

use Interop\Container\ContainerInterface;

interface AbstractFactoryInterface extends FactoryInterface
{
    public function canCreate(ContainerInterface $container, $requestedName);
}

Uma abstract factory tem dois métodos: canCreate() e __invoke (). O primeiro é necessário para testar se a factory pode criar o serviço com o nome certo, e este último permite realmente criar o serviço. Os métodos usam dois parâmetros: service manager ($container) e nome do serviço ($requestedName).

Comparando com a usual factory classe, a diferença é que o classe de factory usual tipicamente cria apenas um único tipo de serviço, mas uma abstract factory pode dinamicamente criar tantos tipos de serviços quanto quiser.

Você registra uma abstract factory com o método setAbstractFactory() do service manager.

Abstract factory são um recurso poderoso, mas você deve usá-las somente quando realmente necessário, porque Eles afetam negativamente o desempenho. É melhor usar as fábricas usuais (não abstratas).

3.10.7. Registrando Aliases de Serviço

Às vezes, você pode querer definir um alias para um serviço. O alias é como um link simbólico: faz referência ao serviço já registrado. Para criar um alias, você usa o método setAlias​​() do service manager:

<?php 
// Register an alias for the CurrencyConverter service
$serviceManager->setAlias('CurConv', CurrencyConverter::class);

Uma vez cadastrado, você pode recuperar o serviço pelo seu nome e alias usando o método get() do service manager.

3.10.8. Serviços compartilhados e não compartilhados

Por padrão, os serviços são armazenados no service manager somente em instância única. Isso também é chamado do design patterns singleton Por exemplo, quando você tenta recuperar o serviço CurrencyConverter duas vezes, você receberá o mesmo objeto. Isso também é chamado de serviço compartilhado (shared).

Mas, em algumas situações (raras), você precisará criar uma nova instância de um serviço sempre que alguém solicitar do service manager. Um exemplo é o EventManager - você obtém uma nova instância a cada vez que o solicita.

Para marcar um serviço como não compartilhado, você pode usar o método setShared() do service manager:

$serviceManager->setShared('EventManager', false);

3.10.9. Configuração do Service Manager

Em seu site, você normalmente usa a configuração do service manager para registrar seus serviços (em vez de chamar métodos do service manager, conforme descrito acima).

Para registrar automaticamente um serviço no service manager, geralmente é usado um arquivo de configuração para o com uma chave service_manager. Você pode colocar essa chave dentro de um arquivo de configuração no nível do aplicativo ou em um nível de módulo arquivo de configuração.

Se você está colocando essa chave em um arquivo de configuração de nível de módulo, seja Cuidado com o perigo de sobrescrever o nome durante a incorporação de configurações. Não registre o mesmo nome de serviço em módulos diferentes.

A chave de configuração service_manager deve se parecer como:

<?php 
return [
    //...

    // Register the services under this key
    'service_manager' => [
        'services' => [
            // Register service class instances here
            //...
        ],
        'invokables' => [
            // Register invokable classes here
            //...
        ],
        'factories' => [
            // Register factories here
            //...
        ],
        'abstract_factories' => [
            // Register abstract factories here
            //...
        ],
        'aliases' => [
            // Register service aliases here
            //...
        ],
        'shared' => [
            // Specify here which services must be non-shared
        ]
  ],
  
  //...
];

No exemplo acima, você pode ver que o service_manager pode conter vários subchaves para registrar serviços de maneiras diferentes:

Como exemplo, vamos registrar nosso serviço CurrencyConverter e criar um alias para ele:

<?php 
use Zend\ServiceManager\Factory\InvokableFactory;
use Application\Service\CurrencyConverter;

return [
    //...

    // Register the services under this key
    'service_manager' => [
        'factories' => [
            // Register CurrencyConverter service.
            CurrencyConverter::class => InvokableFactory::class
        ],
        'aliases' => [
            // Register an alias for the CurrencyConverter service.
            'CurConv' => CurrencyConverter::class
        ],        
  ],
  
  //...
];

Top