Zend Framework y su mensaje “Deprecated….ServiceLocatorAwareInterface.”

Bueno, hace un tiempo actualizando mis trabajos y mi sitio me encontré con un molesto aviso de mi framework.

En mi caso el aviso era casi exactamente este:

“Deprecated: You are retrieving the service locator from within the class MiModulo\Controller\MiController. Please be aware that ServiceLocatorAwareInterface is deprecated and will be removed in version 3.0, along with the ServiceLocatorAwareInitializer. You will need to update your class to accept all dependencies at creation, either via constructor arguments or setters, and use a factory to perform the injections.

MiServidor\DirectorioDeMiProyecto\vendor\zendframework\zend-mvc\src\Controller\AbstractController.php on line 258”

¿Qué quiere decir este mensaje?

Básicamente nos dice que en ZF3 se eliminará la interfaz ServiceLocatorAwareInterface de la clase abstracta AbstractController, la cual implementan los controladores del framework a través del AbstractActionController.

¿Para qué sirve esta interfaz?

Esta interfaz sirve para inyectar el ServiceManager y poder invocar el método getServiceLocator() (localizador de servicios).

El service manager implementa el patrón de diseño “ServiceLocator”, el cual se usa para la tarea de almacenamiento (liviano) y recuperación de objetos desde cualquier parte del código.

Generalmente se usa así:

<?php
namespace MiModulo\Controller;
use Zend\Mvc\Controller\AbstractActionController;
class MiController extends AbstractActionController
{
    // …..
    
    public function indexAction(){
        $instanciaServicioOdependencia=$this->getServiceLocator()->get(‘mi_servicio_o_dependencia’);
        // hago algo con la instancia … o no  y continua la secuencia ….
       return new array();
    }
}

// …..

?>

¿Y qué problema hay con esto?

Esto genera sin duda un anti-patrón ya que expone toda la clase de servicio de localización en lugar de sólo las piezas (dependencias) que serán necesarios, hace a las clases impredecibles, frágiles, y muy difíciles para generar pruebas unitarias, ya que pueden llegar al localizador de servicios y recuperar cualquier cosa. Esto arroja el principio base de la inversión de control por la ventana.

 

¿Qué es la inversión de control y su relación con la inversión e inyección de dependencia?

 

Simplemente patrones de diseño. Simplemente es el mecanismo de darle a un objeto dado sus dependencias sin tener que instanciarlas dentro de el mismo para que se libere de ese “lastre” de acoplamiento.

 

Si quieres la respuesta larga leer sobre esto mira AQUÍ

 

¿Cómo lo arreglamos?

 

Una forma más limpia de hacer esto, es inyectar de forma explícita únicamente los servicios de los que depende la clase: la inyección de dependencia.

 

Si quieres saber sobre la inyección de dependencias puedes leer AQUÍ.

 

En Zend Framework podemos hacer la inyección de dependencias por constructor a través de las fábricas.

Tenemos dos opciones para las fábricas en el caso de nuestros controladores:

La forma correcta de arreglarlo (la de las buenas practicas) :

Crear una fábrica para nuestro controlador donde se le inyectan las dependencias y nos lo devuelven pronto para andar.

Declaramos la fábrica en nuestro archivo de configuración de nuestro modulo module.config.php bajo el array de clave ‘factories’, e insertamos un método constructor con las dependencias en el controlador.

Sería algo así:

//module.config.php:
<?php

return array(
‘controllers’ => array(
       ‘factories’ => array(
           ‘MiModulo\Controller\Mi’ =>’MiModulo\Controller\MiController’,
       ),
    ),
//…. resto de la configuración … ‘router’ => array ( …..),);

?>

Lo cambiamos por esto:

//module.config.php:
<?php
return array(
 
‘controllers’ => array(
       ‘factories’ => array(
           ‘MiModulo\Controller\Mi’ =>’MiModulo\Factory\MiFactoryController’,
       ),
    ),

//…. resto de la configuración … ‘router’ => array ( …..),);

?>

Luego o previamente (esto es a gusto :D) creamos el directorio Factory y dentro nuestra clase fábrica

Nuestra clase fábrica quedaría así:

<?php

namespace MiModulo\Factory;
 
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use MiModulo\Controller\MiController;
 
class MiFactoryController implements FactoryInterface
{
    /**
    * Create service
    * @param ServiceLocatorInterface $serviceLocator
    * @return mixed
    */

public function createService(ServiceLocatorInterface $serviceLocator)
    {
   $dependencia=$serviceLocator->getServiceLocator();
       $controller = new MiController($dependencia);
       return $controller;
    }
}

?>

También para subsanar a muchos momentáneamente..En la fábrica anterior podemos inyectar el ServiceLocatorInterface (acceso al service manager a través de esta interfaz). Pero a medida que podamos deberíamos ir depurando y cargando solo los servicios que necesitamos así:

$dependencia=$serviceLocator->getServicelocator()->get(‘dependencia_o_servicio’);
$controller = new MiController($dependencia);

Ahora en nuestro controlador, asumiendo que inyectamos el ServiceManager completo podemos llamarlo como antes:

<?php
namespace MiModulo\Controller;
 
use Zend\Mvc\Controller\AbstractActionController;
use Zend\ServiceManager\ServiceLocatorInterface;
 
class MiController extends AbstractActionController
{
    /*
    * @var ServiceLocatorInteface
    */
    protected $serviceLocator = null;
   
    public function __construct(ServiceLocatorInterface $serviceLocator)
    {
       $this->setServiceLocator($serviceLocator);
    }
   
    /*
    * @return ServiceLocatorInterface
    */
    public function  getServiceLocator(){
       return $this->serviceLocator;
    }
 
    public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
    {
       $this->serviceLocator = $serviceLocator;
       return $this;
    }
   
    public function indexAction()
    {
       $instanciadelservicio=$this->serviceLocator()->get(‘un_servicio’);
    // hacemos algo con la instancia del servicio…..
       return array();
    }
}

//…..

?>

ya tenemos listo nuestro controlador con sus dependencias!

 

La otra forma de crear nuestras fábricas e inyectar las dependencias es declarando las fábricas desde nuestro Module.php

 

En nuestro Module.php agregamos dentro de la función getControllerConfig() un array con la clave ‘factories’.

Nota: no la escribimos dentro del getConfig() para alivianar la llamada y traer sólo la información de configuración que necesitamos, la de los controladores.

<?php
//Module.php
public function getControllerConfig(){
return array(
‘factories’ => array(‘
MiModulo\Controller\Mi’ => function($serviceManager){
$serviceLocator = $serviceManager->getServiceLocator();
$miController = new MiController($serviceLocator->get(‘dependencia_o_servicio’);
return $miController;
}
),
);
}

//…

?>

Claramente en el código anterior inyectamos el Servicio en nuestro Controlador.

Luego en el module.config.php:

<?php
//module.config.php:
return array(
    ‘controllers’ => array(
    //eliminamos la llamada al controlador que ingresamos en el Module.php
    )
//…. resto de la configuración … ‘router’ => array ( …..),);

?>

Y por supuesto nuestro controlador ya tiene el Servicio / dependencia incluido “de fabrica”:

<?php
namespace MiModulo\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Servicio_dependencia;
class MiController extends AbstractActionController
{

protected $servicio = null;

public function __construct(Servicio_dependencia $servicio_dependencia)
{
$this->servicio=$servicio_dependencia;
}

public function indexAction()
{
// hacemos algo con la instancia del servicio…..
return array();
}
}

?>

¿Por qué esta última opción no es una buena práctica?

Bueno, investigando por la web encontré que PHP no es bueno guardando en cache funciones de cierres o closures los callbacks que se escriben en el Module.php y al parecer no se pueden serializar. Según lo que pude entender en este apartado de Stack Overflow

¿Qué son los closures? Mira por AQUÍ

¿Qué son los callbacks? Mira por AQUÍ

¿Serializar? Mira por AQUÍ

 

Bueno, con esto espero haber podido explicar algo del problema y haber ayudado a quienes como a mi les apareció el dichoso aviso.

Suerte en sus refactorizaciones.

 

Fuentes:

 

http://blog.jorgeivanmeza.com/2009/03/serializacion-de-objetos-con-php/

 

http://www.soliantconsulting.com/blog/2016/06/refactoring-zf2-servicelocatorawareinterface-with-phpstorm

 

http://stackoverflow.com/questions/23416012/zend-framework-2-getcontrollerconfig-vs-getconfig

 

https://akrabat.com/injecting-configuration-into-a-zf2-controller/

 

https://akrabat.com/injecting-dependencies-into-your-zf2-controllers/

 

http://www.soliantconsulting.com/blog/2016/06/refactoring-zf2-servicelocatorawareinterface-with-phpstorm

 

http://www.masterzendframework.com/tutorial/howto-constructor-injection-in-zf2/

 

https://zendframework.github.io/zend-servicemanager/migration/

 

http://jonsegador.com/2011/11/funciones-anonimas-closures-en-php-5-3/

Deja un comentario