Symfony. Factorías de servicios
Cómo emplear una factoría para crear y configurar servicios
En este artículo quiero hablaros acerca de las factorías de servicios, una de esas características de Symfony que a menudo pasan desapercibidas hasta que llega el día en que aparece un caso de uso a al que se ajustan como anillo al dedo.
Mediante las factorías de servicios podremos emplear una clase que se encargará de construir y configurar un determinado tipo de servicio, de modo que ganemos versatilidad a la hora de trabajar con ellos. No en vano el “factory pattern” es uno de los patrones de diseño más extendidos.
En este artículo primero hablaré de las factorías de servicios para a continuación explicaros la situación donde me pareció interesante aplicarlas. Este último apartado es realmente interesante ya que mientras implementaba lo que había diseñado pude aprender una nueva característica de las factorías en Symfony.
Así que, como siempre, ¡vamos a ello!
Factorías de servicios
Lo primero de todo quiero hablar de cómo podemos declarar en Symfony factorías de servicios. Básicamente lo que nos permite esta característica es delegar a una clase (factoría) la creación de la instancia de un servicio en vez de que sea el container quien se encargue de crearla.
El proceso por el cual podemos crear una factoría es bastante sencillo. Supongamos que tengamos un servicio denominado FooService
el cual queremos instanciar en el container mediante una factoría.
Lo primero que haremos será crear la clase FooServiceFactory
del siguiente modo:
class FooServiceFactory
{
public static function createFooService()
{
$fooService = new FooService();
// configure service
return $fooService;
}
}
Y a continuación, en nuestro archivo services.yaml configuraremos nuestro FooService
para que cuando sea añadido al container
la instancia sea creada mediante nuestro FooServiceFactory
:
# config/services.yaml
services:
# some code... App\Service\FooService:
factory: ['App\Service\FooServiceFactory', 'createFooService']
Como veis, la propiedad factory
recibe dos argumentos: la clase a emplear como factoría y el método (estático) a invocar.
No obstante, también es posible emplear un método de una instancia de nuestra factoría e incluso crear una clase invocable en el caso de que queramos abreviar la declaración de la factoría.
En este caso bastará con referenciar el servicio:
# config/services.yaml
services:
# some code...App\Service\FooService:
factory: '@App\Service\FooServiceFactory'
y declarar en nuestra clase FooServiceFactory
el método __invoke
:
class FooServiceFactory
{
public function __invoke()
{
$fooService = new FooService();
// configure service
return $fooService;
}
}
Nota. Esta característica fue introducida en la versión 4.3 de Symfony.
Pasando argumentos a nuestra factoría
En el caso de que necesitemos pasar algún servicio o parámetro al método de nuestra factoría podemos emplear la propiedad arguments
en la declaración del servicio dentro del archivo yaml
:
# config/services.yaml
services:
# some code...App\Service\FooService:
factory: '@App\Service\FooServiceFactory'
arguments: ['@doctrine.entity_manager', '%app.some_param%']
O directamente gracias a la magia de autowiring podemos directamente tipar los argumentos del método al igual que hacemos en las acciones de nuestros controladores:
use Doctrine\ORM\EntityManagerInterface;...class FooServiceFactory
{
public function __invoke(EntityManagerInterface $em)
{
$fooService = new FooService();
// configure service
return $fooService;
}
}
Hasta aquí el uso de factorías, ahora pasemos a lo interesante.
Factoría de factorías
Llegamos al principal motivo del artículo. En un proyecto en el que estoy trabajando es necesario procesar una petición POST
(¡wow, qué gran problema!) desde un servicio al que llamaremos FormProcessor
.
Dentro de este servicio la idea será crear un objeto en base a los parámetros de la petición POST
(como si de un formulario se tratase) para persistirlo posteriormente a base de datos. Este objeto pertenecerá a una clase que extenderá de una clase abstracta (podemos llamarla PostAbstractClass
), es decir, tenemos una herencia de clases desde una clase “padre”.
Por supuesto, cada clase que extiende de PostAbstractClass
tiene una serie de propiedades exclusivas además de las que comparte con el resto de clases que extienden de PostAbstractClass
. Sin embargo, la lógica de creación de cada una de estas clases tiene algo más de miga que un simple new Class()
.
Lo primero que se me ocurrió fue tener una clase PostClassFactory
a la que delegar la creación de los objetos:
class PostClassFactory
{
public function create(array $params)
{
$obj = null
if (some condition based on params) {
$obj = new A();
// logic to set $obj
} else if (some condition based on params) {
$obj = new B();
}
// ...
return $obj;
}
}
e inyectarla en el servicio FormProcessor
:
class FormProcessor
{
private $postClassFact; public function __construct(PostClassFactory $postClassFact)
{
$this->postClassFact = $postClassFact;
} public function process(array $params)
{
$obj = $this->postClassFact->create($params);
// rest of code
}}
Sin embargo, a medida que aparecían distintos tipos de clases heredando de PostAbstractClass
, la lógica de la factoría PostClassFactory
iba aumentando por lo que me pregunté:
¿No será posible tener una factoría por cada tipo de
PostAbstractClass
e inyectar la correcta en el servicioFormProcessor
de algún modo?
Es decir, tener algo similar a lo siguiente:

Como podéis ver, estoy declarando una interfaz PostClassFactoryInterface
la cual implementan los distintos tipos de factorías para dentro de ellas generar la lógica de creación de cada una de las subclases de PostAbstractClass
.
Ahora lo ideal sería poder pasar cualquiera de estos objetos al constructor de la clase FormProcessor
(aprovechando que todos implementan la misma interfaz) y que haya “algo” que se encargue de escoger la factoría correcta:
class FormProcessor
{
private $postClassFact;public function __construct(PostClassFactoryInterface $postClassFact)
{
$this->postClassFact = $postClassFact;
}public function process(array $params)
{
$obj = $this->postClassFact->create($params);
// rest of code
}}
Y es aquí donde entran las factorías de servicios de Symfony.
Para que este mecanismo funcione lo que haremos será crear una factoría de servicios del tipo PostClassFactoryInterface
:

Y decirle a Symfony que el servicio cuyo alias es PostClassFactoryInterface
se creará mediante esta factoría:
# services.yamlservices:
... App\Service\PostClassFactoryInterface:
factory: '@App\Service\CreatePostClassFactory'
De este modo a la clase FormProcessor
le llegará la factoría adecuada y además tendremos separadas en distintas clases que implementan PostClassFactoryInterface
la lógica de creación de nuestros objetos PostAbstractClass
, quedándonos un código muy poco acoplado y fácil de extender.
Conclusiones
Como veis, las factorías de servicios de Symfony nos otorgan una gran flexibilidad a la hora de controlar la forma en que se instancian nuestros servicios.
Pese a conocer ya desde hace tiempo esta característica, lo que más me ha llamado la atención es la posibilidad de asignar la factoría a una interface
de modo que en tiempo de ejecución podamos controlar la instanciación del servicio que necesitamos.
Algo que creo que resulta muy muy interesante.
¿Quieres recibir más artículos como este?
Si te ha gustado este artículo te animo a que te suscribas a la newsletter que envío cada domingo con publicaciones similares a esta y más contenido recomendado: 👇👇👇