I have been trying to get my head around this
http://symfony.com/doc/current/service_container/factories.html
But there seems to be a piece missing that tires it all together OR I am completely missing the point.
The example has a factory Class
class NewsletterManagerFactory
{
public static function createNewsletterManager()
{
$newsletterManager = new NewsletterManager();
// ...
return $newsletterManager;
}
}
And then that factory is configured via a service to be the factory class and method for NewsletterManager
service.
services:
app.newsletter_manager_factory:
class: AppBundle\Email\NewsletterManagerFactory
app.newsletter_manager:
class: AppBundle\Email\NewsletterManager
factory: 'app.newsletter_manager_factory:createNewsletterManager'
So now we have a NewsletterManager
Class that is aware of the NewsletterManagerFactory
Class via the factory:
param in services.yml
Question
How do you make use of this configuration? What is now exposed inside of NewsletterManager
that allows me to call createNewsletterManager
in the factory Class?
The two services are still completely separate as far as I can tell?
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.
This is simple. If you gonna call
$nm = $container->get('app.newsletter_manager')
then the newsletter manager will be created by factory automatically.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With