I used this pattern once. Here is the use case for it.
Imagine that you have multiple widget classes i.e. Acme\Widget1
, Acme\Widget2
, Acme\WidgetN
.
Each widget has an advanced instantiation process so you decide to use a factory. It also has a complex dependency chain that is needed for each widget to be instantiated. I.e. Acme\Dependency1
, Acme\Dependency2
, Acme\Dependency3
.
So what you'd do is to create Acme\WidgetFactory
service with dependencies once. Then you need to specify that Acme\WidgetFactory
as factory for each widget. In case something changes in the way of widget instantiation you only need to change one class and one service definition. All 1 to N widget services stay the same.
Here is the example...
Typical way of implementation:
acme.widget1:
class: Acme\Widget1
factory: ['Acme\Widget1', 'create']
arguments: ['@acme.dependency1', '@acme.dependency2', '@acme.dependencyN']
acme.widget2:
class: Acme\Widget2
factory: ['Acme\Widget2', 'create']
arguments: ['@acme.dependency1', '@acme.dependency2', '@acme.dependencyN']
acme.widgetN:
class: Acme\WidgetN
factory: ['Acme\WidgetN', 'create']
arguments: ['@acme.dependency1', '@acme.dependency2', '@acme.dependencyN']
Here you have a strong smell of code duplication. If you want to change something you need to do it N times.
Instead here is what you can do.
acme.widget_factory:
class: Acme\WidgetFactory
arguments: ['@acme.dependency1', '@acme.dependency2', '@acme.dependencyN']
acme.widget1:
class: Acme\Widget1
factory: ['@acme.widget_factory', createWidget]
acme.widget2:
class: Acme\Widget2
factory: ['@acme.widget_factory', createWidget]
acme.widgetN:
class: Acme\WidgetN
factory: ['@acme.widget_factory', createWidget]
Code duplication is gone.
Appeared small inconvenience... Factory does not know what concrete class to instantiate. I used the following technique for it.
I tagged each widget and then during compiler pass added extra parameter to factory.
acme.widget_factory:
class: Acme\WidgetFactory
arguments: ['@acme.dependency1', '@acme.dependency2', '@acme.dependencyN']
acme.widget1:
class: Acme\Widget1
factory: ['@acme.widget_factory', createWidget]
tags:
- { name: acme.widget }
acme.widget2:
class: Acme\Widget2
factory: ['@acme.widget_factory', createWidget]
tags:
- { name: acme.widget }
acme.widgetN:
class: Acme\WidgetN
factory: ['@acme.widget_factory', createWidget]
tags:
- { name: acme.widget }
Then in DepencencyInjection\AcmeDemoExtension.php
class AcmeDemoExtension implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$widgets = $container->findTaggedServiceIds('acme.widget');
foreach ($widgets as $id => $tags) {
$definition = $container->getDefinition($id);
$definition->setArguments([$definition->getClass()]);
}
}
}
and finally in the factory...
class AcmeWidgetFactory
{
//.....
public static function createWidget($class)
{
//.....
return new $class(/* dependencies */);
//.....
}
//.....
}
So at the end when you do $this->get('acme.widget1')
the factory method with class name as a parameter is called. Factory already has all dependencies and knows the logic of class instantiation. So it does all the work and return required widget instance.
factory
param to the service. For instance theacme.widget2
service now has a factory param of['@acme.widget_factory', createWidget]
but how is that used inside of the classAcme\Widget2
. Can you show me what theAcme\Widget2
class might look like? – Loony