Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony3 Factory as Service

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?

like image 709
Jake N Avatar asked Dec 16 '16 13:12

Jake N


2 Answers

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.

like image 157
Stepashka Avatar answered Oct 31 '22 04:10

Stepashka


This is simple. If you gonna call

$nm = $container->get('app.newsletter_manager')

then the newsletter manager will be created by factory automatically.

like image 39
Rawburner Avatar answered Oct 31 '22 04:10

Rawburner