ZF3 Zend Framework versión 3, los cambios en el service manager o admnistrador de servicio

Lo siguiente es una traducción del manual ZF3, el cual se encuentra en el repositorio de GitHub de Oleg Krivtsov su autor. Dado que no encuentro mucho material traducido al español sobre la versión 3 de Zend Framework, me pareció bueno contribuir en este proyecto, para fomentar un conocimiento más accesible para todos.

Para ir al capítulo en el manual original aquí

3.10. Service Manager

(Administrador de servicio)

 

Usted puede imaginar la aplicación web como un conjunto de servicios. Por ejemplo, puede tener un servicio de autenticación responsable de iniciar sesión para los usuarios del sitio, el servicio gestor de entidades (Entity Manager) responsable del acceso a la base de datos, el servicio del gestor de eventos (Event Manager) responsable de activar eventos y entregarlos a los oyentes de eventos, etc.

En Zend Framework, la clase ServiceManager es un contenedor centralizado para todos los servicios de aplicaciones. El gestor de servicios se implementa en el componente Zend\ServiceManager, como la clase ServiceManager. El diagrama de herencia de clases se muestra en la figura a continuación:

El gestor de servicios se crea al iniciar la aplicación (dentro del método estático init() de la clase Zend\Mvc\Application). Los servicios estándar disponibles a través del gerente de servicios se presentan en la tabla a continuación. * Esta tabla está incompleta, porque el número real de servicios registrados en el administrador de servicios puede ser mucho mayor.

 

Nombre del servicio Descripción
Application Permite obtener el singleton de la clase Zend\Mvc\Application.
ApplicationConfig El array de configuración extraído del archivo application.config.php.
Config El array de configuraciones unidas obtenido de la unión de los archivos module.config.php unido con autoload/global.php y autoload/local.php.
EventManager Permite obtener una nueva instancia de la clase Zend\EventManager\EventManager. El manejador de eventos permite enviar (trigger) eventos y asignar escuchadores de eventos.
SharedEventManager Permite obtener una instancia singleton de la clase Zend\EventManager\SharedEventManager. El administrador de eventos compartidos permite escuchar eventos definidos por otras clases y componentes.
ModuleManager Permite obtener una instancia singleton de la clase Zend\ModuleManager\ModuleManager. El administrador de módulos es responsable de cargar los módulos de las aplicaciones.
Request La instancia singleton de la clase Zend\Http\Request. Representa la petición HTTP enviada por el cliente.
Response La instancia singleton de la clase Zend\Http\Response. Representa a la respuesta HTTP que será enviada al cliente.
Router La instancia singleton de la clase Zend\Router\Http\TreeRouteStack. Realiza el ruteo URL.
ServiceManager Instancia del propio Service manager.
ViewManager La instancia singleton de la clase Zend\Mvc\View\Http\ViewManager. Responsable de preparar la vista (capa de vista) para la presentación de las páginas.

 

Un servicio es típicamente una clase PHP arbitraria, pero no siempre. Por ejemplo, cuando ZF3 carga los archivos de configuración y fusiona los datos en arreglos anidados, guarda los arrays en el gestor de servicios como un par de servicios (!): ApplicationConfig y Config. La primera es la matriz cargada desde el archivo de configuración application.config.php de nivel de aplicación, y la posterior es la matriz combinada de archivos de configuración de nivel de módulo y archivos de configuración de nivel de aplicación cargados automáticamente. Por lo tanto, en el gestor de servicios puede almacenar cualquier cosa que desee: una clase PHP, una variable simple o una matriz.

De la tabla anterior, se puede ver que en ZF3 casi todo puede ser considerado como un servicio. El mismo gestor de servicios está registrado como un servicio. Además, la clase Application también está registrada como un servicio.

 

! Una cosa importante que deberías notar acerca de los servicios es que generalmente están almacenados en una sola instancia o instancia única (patrón Singleton). Evidentemente no necesitas una segunda instancia de la clase Application (en ese caso tendrías una pesadilla).

 

( * ) Pero hay una excepción importante de la regla. Puede ser confuso al principio, pero el EventManager no es un singleton. Cada vez que recuperes el servicio gestor de eventos desde el gestor de servicios, recibirás un nuevo objeto EventManager. Esto se hace por razones de rendimiento y para evitar posibles conflictos de sucesos entre diferentes componentes. Discutiremos esto más adelante en la sección Acerca de Event Manager.

 

El gestor de servicios define varios métodos necesarios para localizar y recuperar un servicio. Estos métodos son:

 

Nombre del método Descripción
has($name) Verifica si el servicio indicado está registrado.
get($name) Devuelve la instancia del servicio registrado.
build($name, $options) Siempre devuelve una nueva instancia del servicio solicitado.

 

Puedes probar si un servicio está registrado pasando su nombre al método has() del administrador de servicios. Devuelve un valor booleano true si el servicio está registrado o false si el servicio con ese nombre no está registrado.

Puedes recuperar un servicio por su nombre con la ayuda del método get() del administrador de servicios. Este método toma un único parámetro que representa el nombre del servicio. Mira el siguiente ejemplo:

 

  1. <?php
  2. // Recibe el array de configuración de la aplicación.
  3. $appConfig = $serviceManager->get(‘ApplicationConfig’);
  4. // Úsalo (por ejemplo, para obtener la lista de módulos).
  5. $modules = $appConfig[‘modules’];

 

Y el método build() siempre crea una nueva instancia del servicio cuando se llama (comparado a get(), que normalmente crea la instancia del servicio sólo una vez y devuelve el mismo en solicitudes posteriores).

 

( * ) Por lo general, no se deben obtener los servicios del administrador de servicios en cualquier lugar de su código, sino dentro de una fábrica. Una fábrica es un código responsable de la creación de un objeto. Al crear el objeto, se pueden obtener los servicios de los que este depende (dependencias) con el gestor de servicios y pasárselos al constructor del objeto. Esto también se llama inyección de dependencia.

 

! Si tiene alguna experiencia con Zend Framework 2, puede notar que las cosas son ahora un poco diferentes que antes. En ZF2, hubo un patrón ServiceLocator que permitía obtener dependencias a través del gestor de servicios en cualquier parte de su aplicación (en controladores, servicios, etc.). En ZF3, se tienen que pasar las dependencias explícitamente. Es un poco más aburrido, pero elimina las dependencias “ocultas” y hace que su código sea más claro y más fácil de entender.

 

3.10.1. Registering a Service

(Registrando un servicio)

 

Al escribir su sitio web, a menudo tendrá que registrar su propio servicio en el administrador de servicios. Una de las maneras de registrar un servicio es usar el método setService() del gestor de servicios. Por ejemplo, vamos a crear y registrar la clase de servicio del convertidor de monedas, que se utilizará, por ejemplo, en una página de carrito de la compra para convertir la moneda de EUR a USD:

 

  1. <?php
  2. // Definimos un namespace donde nuestro servicio personalizado cobrará vida.
  3. namespace Application\Service;
  4. // Definimos una clase para el servicio de convertidor de monedas
  5. class CurrencyConverter
  6. {
  7. // Convertir euros a US dólares.
  8. public function convertEURtoUSD($amount)
  9. {
  10. return $amount*1.25;
  11. }
  12. //…
  13. }

Arriba, en las líneas 6-15 definimos un ejemplo de la clase CurrencyConverter (por simplicidad, implementamos solo un método convertEURtoUSD() que es capaz de convertir euros a dólares estadounidenses).

 

  1. // Crear una instancia de la clase.
  2. $service = new CurrencyConverter();
  3. // Guardar la instancia en el Administrador de servicios (service manager).
  4. $serviceManager->setService(CurrencyConverter::class, $service);

En el ejemplo anterior instanciamos la clase con el nuevo operador y la registramos con el gestor de servicios usando el método setService() (suponemos que la variable $serviceManager es del tipo Zend\ServiceManager\ServiceManager y que fue declarada en algún otro lugar).

El método setService() toma dos parámetros: la cadena del nombre de servicio y la instancia del servicio. El nombre del servicio debe ser único dentro de todos los demás servicios posibles.

Una vez que el servicio se almacena en el administrador de servicios, puede ser recuperado por su nombre en cualquier lugar de su aplicación con la ayuda del método get() del administrador de servicios. Mira el siguiente ejemplo:

 

  1. <?php
  2. // Obtiene el servicio convertidor de moneda CurrencyConverter.
  3. $service = $serviceManager->get(CurrencyConverter::class);
  4. // Lo usamos (convirtiendo un monto de dinero).
  5. $convertedAmount = $service->convertEURtoUSD(50);

3.10.2. Service Names

(Nombres de servicio)

 

Los diferentes servicios pueden utilizar diferentes estilos de nomenclatura. Por ejemplo, el mismo servicio de convertidor de moneda puede registrarse bajo los diferentes nombres: CurrencyConverter, currency_converter y así sucesivamente. Para introducir una convención de nomenclatura uniforme, se recomienda registrar un servicio por su nombre de clase totalmente calificado, de la siguiente manera:

 

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

En el ejemplo anterior, usamos el nombre de la clase como palabras clave. Está disponible desde PHP 5.5 y se utiliza para la resolución de nombres de clases. CurrencyConverter::class se expande al nombre completo de la clase, como \Application\Service\CurrencyConverter. (En este caso Application es el módulo dentro del cual estamos trabajando).

 

3.10.3. Overriding an Existing Service

(Sobrescribiendo un servicio existente)

 

Si está intentando registrar un nombre de servicio que ya existe, el método setService() lanzará una excepción. Pero a veces se quiere anular el servicio con el mismo nombre (para reemplazarlo por el nuevo). Para ello, puede utilizar el método setAllowOverride() del gestor de servicios:

 

  1. <?php
  2. // Permitimos que se reemplace el servicio
  3. $serviceManager->setAllowOverride(true);
  4. // Guardamos la instancia en el administrador de servicio (service manager).
  5. // No habrá excepción incluso si hay otro servicio con el mismo nombre.
  6. $serviceManager->setService(CurrencyConverter::class, $service);

 

Arriba, el método setAllowOverride() toma un único parámetro booleano que define si se debe permitir reemplazar el servicio CurrencyConverter si dicho nombre ya está presente o no.

 

3.10.4. Registering Invokable Classes

(Registrando clases invocables)

 

Lo que es malo con el método setService() es que tienes que crear la instancia de servicio antes de que realmente la necesites. Si nunca utilizas el servicio, la instanciación del servicio sólo desperdiciará el tiempo y la memoria. Para resolver este problema, el administrador de servicios nos proporciona el método setInvokableClass().

 

  1. <?php
  2. // Registramos una clase invocable
  3. $serviceManager->setInvokableClass(CurrencyConverter::class);

En el ejemplo anterior, pasamos al gestor de servicios el nombre de clase totalmente calificado del servicio en lugar de pasar su instancia. Con esta técnica, el servicio será instanciado por el gestor de servicios sólo cuando alguien llama al método get(CurrencyConverter::class). Esto también se llama carga perezosa (lazy load).

 

( * ) Los servicios a menudo dependen uno del otro. Por ejemplo, el servicio de convertidor de divisas puede utilizar el servicio de gestor de entidad (EntityManager) para leer las tasas de cambio de dinero de la base de datos. La desventaja del método setInvokableClass() es que no permite pasar parámetros (dependencias) al servicio de instanciación de objetos. Para resolver este problema, puede utilizar fábricas, como se describe a continuación.

 

3.10.5. Registering a Factory

(Registrando una fábrica)

 

Una fábrica es una clase que sólo puede hacer una cosa: crear otros objetos.

Registra una fábrica para un servicio con el método setFactory() del gestor de servicios:

La fábrica más simple es InvokableFactory – es análoga al método setInvokableClass() de la sección anterior.

 

  1. <?php
  2. use Zend\ServiceManager\Factory\InvokableFactory;
  3. // Esto es equivalente al método setInvokableClass() de la sección previa.
  4. $serviceManager->setFactory(CurrencyConverter::class, InvokableFactory::class);

Después de haber registrado la fábrica, puede recuperar el servicio del gestor de servicios como de costumbre con el método get(). El servicio será instanciado sólo cuando lo recupere del gestor de servicios (carga perezosa/lazy load).

A veces, la creación de instancias de servicio es más compleja que la creación de la instancia de servicio con un nuevo operador (como InvokableFactory). Es posible que necesite pasar algunos parámetros al constructor del servicio o invocar algunos métodos de servicio justo después de la construcción. Esta lógica de instanciación compleja se puede encapsular dentro de su propia clase fábrica personalizada. La clase fábrica típicamente implementa el FactoryInterface:

 

  1. <?php
  2. namespace Zend\ServiceManager\Factory;
  3. use Interop\Container\ContainerInterface;
  4. interface FactoryInterface
  5. {
  6. public function __invoke(ContainerInterface $container, $requestedName, array $options = null);
  7. }

Como vemos en la definición de FactoryInterface, la clase factory debe proporcionar el método mágico __invoke devolviendo la instancia de un solo servicio. El gestor de servicios se pasa al método __invoke en el parámetro $container; Se puede utilizar durante la construcción del servicio para acceder a otros servicios (para inyectar dependencias). El segundo argumento ($requestedName) es el nombre del servicio. El tercer argumento ($options) se puede utilizar para pasar algunos parámetros al servicio y solo se utiliza cuando se solicita el servicio con el método build() del gestor de servicios.

Como ejemplo, vamos a escribir una fábrica para nuestro servicio de convertidor de monedas (vea el código abajo). No usamos lógicas complejas de construcción para nuestro servicio CurrencyConverter, pero para servicios más complejos, puede que tenga que usar uno.

 

  1. <?php
  2. use Zend\ServiceManager\Factory\FactoryInterface;
  3. use Application\Service\CurrencyConverter;
  4. // Clase fábrica
  5. class CurrencyConverterFactory implements FactoryInterface
  6. {
  7. public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
  8. {
  9.     // Crea una instancia de la clase
  10. $service = new CurrencyConverter();
  11. return $service;
  12. }
  13. }

!Técnicamente, en ZF3 puedes utilizar la misma clase de fábrica para instanciar varios servicios que tienen código de instanciación similar (para ese propósito, puede utilizar el argumento $requestedName pasado al método __invoke() de la fábrica). Sin embargo, en su mayoría se creará una fábrica diferente por cada servicio.

 

3.10.6. Registering an Abstract Factory

(Registrando una fábrica abstracta)

 

Un caso aún más complejo de una fábrica es cuando es necesario determinar en tiempo de ejecución qué nombres de servicio deben registrarse. Para tal situación, usted puede utilizar una fábrica abstracta. Una clase de fábrica abstracta debe implementar la interfaz AbstractFactoryInterface:

 

  1. <?php
  2. namespace Zend\ServiceManager\Factory;
  3. use Interop\Container\ContainerInterface;
  4. interface AbstractFactoryInterface extends FactoryInterface
  5. {
  6. public function canCreate(ContainerInterface $container, $requestedName);
  7. }

Una fábrica abstracta tiene dos métodos: canCreate() y __invoke(). El primero es necesario para probar si la fábrica puede crear el servicio con el nombre determinado, y este último permite crear el servicio. Los métodos toman dos parámetros: el gestor de servicios ($container) y el nombre del servicio ($requestedName).

Comparando con la clase de fábrica habitual, la diferencia es que la clase fábrica normalmente crea sólo un tipo de servicio, pero una fábrica abstracta puede crear dinámicamente tantos tipos de servicios como quiera.

Registra una fábrica abstracta con el método setAbstractFactory() del gestor de servicios.

 

(* ) Resumiendo, las fábricas son una característica de gran alcance, pero usted debe utilizarlas sólo cuando sea realmente necesario, porque tienen un impacto negativo en el rendimiento. Es mejor usar las fábricas habituales (no abstractas).

 

3.10.7. Registering Service Aliases

(Registrando alias de servicio)

 

A veces, es posible que desee definir un alias para un servicio. El alias es como un enlace simbólico: hace referencia al servicio ya registrado. Para crear un alias, utilice el método setAlias​​() del administrador de servicios:

 

  1. <?php
  2. // Registramos un alias para el servicio CurrencyConverter
  3. $serviceManager->setAlias(‘CurConv’, CurrencyConverter::class);

Una vez registrado, puede recuperar el servicio tanto por su nombre como por su alias utilizando el método get() del administrador de servicios.

 

3.10.8. Shared and Non-Shared Services

(Servicios compartidos y no compartidos)

 

De forma predeterminada, los servicios se almacenan en el gestor de servicios en una sola instancia. Esto también se llama el patrón de diseño singleton. Por ejemplo, cuando intenta recuperar el servicio CurrencyConverter dos veces, recibirá el mismo objeto. Esto también se llama un servicio compartido.

Pero, en algunas situaciones (raras), tendrá que crear una nueva instancia de un servicio cada vez que alguien lo solicite desde el administrador de servicios. Un ejemplo es el EventManager: se obtiene una nueva instancia cada vez que se solicita.

Para marcar un servicio como no compartido, puede utilizar el método setShared() del administrador de servicios:

 

  1. $serviceManager->setShared(‘EventManager’, false);

3.10.9. Service Manager Configuration

(Configuración del administrador de servicio)

 

En su sitio web, normalmente utiliza la configuración del gestor de servicios para registrar sus servicios (en lugar de llamar a los métodos del gestor de servicios como se describe anteriormente).

Para registrar automáticamente un servicio dentro del gestor de servicios, normalmente se utiliza la clave de service_manager de un archivo de configuración. Puede poner esta clave dentro de un archivo de configuración a nivel de aplicación o en un archivo de configuración a nivel de módulo.

 

!! Si está poniendo esta clave en un archivo de configuración a nivel de módulo, tenga cuidado con el peligro de sobreescritura de nombres durante la fusión de configs. No registre el mismo nombre de servicio en módulos diferentes.

 

Esta clave de service_manager debe tener el siguiente aspecto:

 

  1. <?php
  2. return [
  3. //…
  4. // Registramos los servicios bajo esta clave
  5. ‘service_manager’ => [
  6. ‘services’ => [
  7. // Registramos las instancias de las clases de servicio aquí
  8. //…
  9. ],
  10. ‘invokables’ => [
  11. // Registramos las clases invocables aquí
  12. //…
  13. ],
  14. ‘factories’ => [
  15. // Registramos las fábricas aquí
  16. //…
  17. ],
  18. ‘abstract_factories’ => [
  19. // Registramos las fábricas abstractas aquí
  20. //…
  21. ],
  22. ‘aliases’ => [
  23. // Registramos los alias de los servicios aquí
  24. //…
  25. ],
  26. ‘shared’ => [
  27. // Especificamos aquí cuales servicios deben ser no compartidos
  28. ]
  29. ],
  30. //…
  31. ];

 

En el ejemplo anterior, puede ver que la clave service_manager puede contener varias sub-claves para registrar servicios de diferentes maneras:

  • La sub-clave services (línea 7) permite registrar instancias de clase;
  • La sub-clave invokables (línea 11) permite registrar el nombre de clase completo de un servicio; el servicio será instanciado usando carga perezosa;
  • La sub-clave factories (línea 15) permite registrar una fábrica, que es capaz de crear instancias de un único servicio;
  • La sub-clave abstract_factories (línea 19) puede utilizarse para registrar fábricas abstractas, que pueden registrar varios servicios por su nombre;
  • La sub-clave aliases (línea 23) proporciona la capacidad de registrar un alias para un servicio.
  • La sub-clave shared (línea 27) permite especificar qué servicios deben ser no compartidos.

 

Como ejemplo, vamos a registrar nuestro servicio CurrencyConverter y crear un alias para ello:

 

  1. <?php
  2. use Zend\ServiceManager\Factory\InvokableFactory;
  3. use Application\Service\CurrencyConverter;
  4. return [
  5. //…
  6. // Registramos los servicios bajo esta clave
  7. ‘service_manager’ => [
  8. ‘factories’ => [
  9. // Registramos el servicio CurrencyConverter.
  10. CurrencyConverter::class => InvokableFactory::class
  11. ],
  12. ‘aliases’ => [
  13. // Registramos un alias para el servicio CurrencyConverter.
  14. ‘CurConv’ => CurrencyConverter::class
  15. ],
  16. ],
  17. //…
  18. ];

2 opiniones en “ZF3 Zend Framework versión 3, los cambios en el service manager o admnistrador de servicio”

Deja un comentario