Zend Framework 3: crear controladores con factories

Las factorías son un conjunto de patrones de diseño que consisten en delegar la tarea de la instanciación e inicialización de clases a otra clase en particular. Entre las ventajas se encuentran la aplicación del Principio de Inversión de Dependencia (Dependency inversion de SOLID), que sigue la filosofía de depender siempre de abstracciones (clases abstractas e interfaces) y no de implementaciones (clases concretas).

zend-framework-hero

Los factories son herramientas muy importantes cuando se programa orientado a objetos. En mi opinión, no se termina de entender su necesidad hasta el momento en que, al no implementarlos, nos damos cuenta de que que las clases que estamos escribiendo tienen concentradas sus dependencias y son prácticamente imposibles de testear unitariamente.

Cuando trabajamos con frameworks, estos suelen traer interfaces y clases abstractas para implementar algún tipo de factoría. Zend Framework 3 no es la excepción y en este artículo vamos a ver cómo utilizar los factories para crear nada más y nada menos que controladores.

Antes de seguir, en el ejemplo que voy a explicar a continuación vamos a tomar como base la aplicación esqueleto de Zend Framework 3 y el Tutorial inicial en el que se crea un pequeño ABM (CRUD) de álbumes de música. Si no siguieron ese tutorial, les recomiendo que lo hagan. Es muy bueno y permite tener una idea general del framework.

Instanciación en el controlador mismo

Lo que no hay que hacer según el Principio de Inversión de Dependencia, uno de los pilares del correcto diseño POO es instanciar directamente una clase. En nuestro caso, en el controlador. Pongamos un ejemplo. Supongamos que tenemos una clase super importante que se llama HelloWorldController. Esta clase tiene un método terriblemente crucial para nuestra aplicación. Un método que se llama getHelloWorld() y nos devuelve un string con la frase “Hello World!”, frase sin la cual nuestro programa no podría funcionar. Estoy exagerando pero es para hacer el ejemplo más claro.

Esta es nuestra clase HelloWorld

Como ven, y como corresponde, nuestra clase implementa la interface HelloWorldInterface.

Ahora, lo que estaríamos tentados a hacer en el controlador, suponiendo que tenemos un HelloWorldController es lo siguiente:

El new HelloWorld() que hacemos en el método indexAction está mal. Si bien funciona, está violando el Principio de Inversión de Dependencia. Al crear un objeto HelloWorld en el controlador estamos haciendo que nuestro controlador dependa de esa clase, es decir, debe conocerla para poder instanciarla. Por otro lado si quisiéramos hacer un test unitario de esta clase, simplemente no podríamos puesto que el método indexAction crea un objeto de otra clase y luego lo usa. No se puede simular ese otro objeto (mockearlo). Y si se prueba así como está, la prueba deja de ser unitaria.

Se estarán preguntando “¿Si no puedo hacer un new, entonces qué puedo hacer? Necesito usar esa clase” La solución es la inyección de dependencia.

Inyección de dependencia

La inyección de dependencia (Dependency injection) es algo de lo que se ha hablado muchísimo en los últimos años, especialmente en el mundo PHP. Es un concepto muy simple pero que tiene mucho peso. Consiste en evitar hacer estos new en las clases y delegarlos a factorías que inyecten la dependencia (o sea, el objeto) en el constructor del que lo necesita o a través de un setter.

Vamos de nuevo: lo que plantea la inyección de dependencia es no hacer el new y en vez de eso pasarle como parámetro en el constructor el objeto que la clase necesita para que esta lo guarde como atributo y lo use luego, cuando lo necesite. Además de pasárselo en el constructor, también se lo podemos pasar como un set, por ejemplo desde el cliente podríamos hacer setHelloWorld(new HelloWorld()). El detalle no menos importante (a pesar de que PHP es no tipado, aunque cada vez más tipado) es que la instancia que enviamos vaya como abstracción y no como clase concreta. Para eso nos vamos a valer del type hinting de PHP.

¿Suena complicado? El código lo va a hacer más simple de entender. Atención que el siguiente código no es lo que usa Zend, es solamente para hacer más simple la explicación.

La clase HelloControllerFactory instancia una clase HelloWorld y se la pasa por parámetro al constructor de HelloWorldController al crearlo. El objetivo de HelloWorldControllerFactory es crear e inicializar un HelloWorldController. Este ejemplo tiene un defecto, la clase HelloWorld se está instanciando. ¡Algo que ya sabemos que está mal! Sin embargo, para simplificar este ejemplo vamos a permitirlo. En realidad, en Zend Framework ese tipo de clases deben ser convertidas en servicios, los cuáles tienen sus propios factories. Lo veremos en otro articulo.

FactoryInterface de Zend Framework

Entonces se estarán preguntando cómo hacer esto de tener un factory para el controlador. ¿Hay que crear una clase? ¿Y cómo hacemos para que esa clase sea llamada en el momento en que se debe cargar el controlador así lo devuelve? No se preocupen por eso. Zend Framework se creó para ser extensible y quienes lo programaron pensaron en eso. Para poder crear un factory para un controlador se necesita una clase factory que implemente FactoryInterface, (que es una interface especial para factories) y luego registrarlo en module.config.php. Es decir, creamos nuestra clase siguiendo las reglas que nos impone FactoryInterface y después le avisamos a Zend que cuando se deba cargar el controlador, se invoque a nuestro factory para crearlo.

Entonces, nuestro factory a lo Zend Framework 3, digamos, debe ser así:

Dejé el PHPDoc sobre el método __invoke, que es el método que estamos obligados a implementar al utilizar la interface FactoryInterface para que quienes vienen de Zend Framework 2 puedan ver la diferencia. Ya no viene como parámetro el ServiceLocator, ahora tenemos en su lugar un ContainerInterface que hasta donde he visto se comporta igual, trayendo servicios. De todas formas en este ejemplo no lo vamos a usar.

Instanciamos (indebidamente, repito) HelloWorld en esta clase factory y se lo inyectamos vía constructor a HelloWorldController, que lo recibirá y lo guardará como atributo privado. Luego, lo usará para obtener lo que se necesita de él y, en este caso, pasárselo a la vista.

Como se puede apreciar, nuestro controlador ya no depende de la clase HelloWorld. La recibe como parámetro. Y lo que es todavía mejor,  ni siquiera sabe qué tipo de HelloWorld es ya que en realidad lo que recibe es un HelloWorldInterface, lo que permite tener el controlador bastante desacoplado del modelo. Piensen que ahora el controlador podría manejar de la misma manera a toda una familia de clases que implementen HelloWorldInterface.

Y no sólo eso, ahora nuestro controlador se puede probar unitariamente. La clase HelloWorld puede mockearse fácilmente. Incluso podemos crearnos nuestro propio mock heredando de HelloWorldInterface.

Conclusión

Hay distintas formas de hacer las cosas, pero no todas son correctas. Sinceramente creo que al principio no es tan importante prestarle atención a estas cosas. Es mejor equivocarse, hacerlas mal y verificar que efectivamente no hacer esto genera ciertos problemas. Luego, de a poco realizando una refactorización se puede mejorar el sistema y lo más importante de todo: aprender por qué hay que escribir código siguiendo los principios del paradigma en que programamos.

Soy programador web freelancer y me he desempeñado como Líder Técnico y Líder de Proyectos. Trabajo con tecnologías como PHP, Javascript, MySQL y HTML5 para el desarrollo de sitios y sistemas web. Me especializo en Zend Framework y otros frameworks MVC, como también en WordPress y otros CMS. Me apasiona el liderazgo técnico de equipos aplicando metodologías ágiles de desarrollo.
Vivo en Buenos Aires, Argentina.
 

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

*