Skip to content

Using a Factory to Create Services

Edit this page

A factory is a class or a callable whose main purpose is to create and return other objects. The factory design pattern is useful when the creation of an object requires some special logic that shouldn't live in the object itself, such as selecting the concrete class to instantiate based on some condition or running some code after the instantiation.

Symfony's service container supports this pattern: instead of instantiating the class of a service directly, the container can call a method on your factory to create the object.

Static Factories

Suppose you have a factory that configures and returns a new NewsletterSender object by calling the static createNewsletterSender() method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/Email/NewsletterSenderStaticFactory.php
namespace App\Email;

// ...

class NewsletterSenderStaticFactory
{
    public static function createNewsletterSender(): NewsletterSender
    {
        $newsletterSender = new NewsletterSender();

        // ...

        return $newsletterSender;
    }
}

To make the NewsletterSender object available as a service, use the factory option to define which method of which class must be called to create its object:

1
2
3
4
5
6
7
# config/services.yaml
services:
    # ...

    App\Email\NewsletterSender:
        # the first argument is the class and the second argument is the static method
        factory: ['App\Email\NewsletterSenderStaticFactory', 'createNewsletterSender']

If the factory method needs arguments, define them with the arguments option, as explained later in Using a Factory to Create Services.

Tip

When configuring your services with PHP, you can use first-class callable syntax to define the factory:

1
2
3
NewsletterSender::class => [
    'factory' => NewsletterSenderStaticFactory::createNewsletterSender(...),
],

This syntax also works with global functions:

1
2
3
4
'date' => [
    'class' => \DateTime::class,
    'factory' => \date_create(...),
],

Note

When using a factory to create services, the value chosen for class has no effect on the resulting service. The actual class name only depends on the object that is returned by the factory. However, the configured class name may be used by compiler passes and therefore should be set to a sensible value.

Using the Class as Factory Itself

When the static factory method belongs to the same class as the created instance, you don't need to repeat the class name. Suppose the NewsletterSender class has a create() method that must be called to create the object:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/Email/NewsletterSender.php
namespace App\Email;

// ...

class NewsletterSender
{
    public static function create(): self
    {
        $newsletterSender = new self();

        // ...

        return $newsletterSender;
    }
}

Use the constructor option to tell the container which static method of the class creates the object:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src/Email/NewsletterSender.php
namespace App\Email;

use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;

#[Autoconfigure(constructor: 'create')]
class NewsletterSender
{
    public static function create(): self
    {
        $newsletterSender = new self();

        // ...

        return $newsletterSender;
    }
}

The same result can be achieved with the factory option by passing null as the factory class:

1
2
3
4
5
6
# config/services.yaml
services:
    # ...

    App\Email\NewsletterSender:
        factory: [null, 'create']

If the factory method needs arguments, define them with the arguments option, as explained later in Using a Factory to Create Services.

Non-Static Factories

If your factory is using a regular method instead of a static one to configure and create the service, instantiate the factory itself as a service too. Configuration of the service container then looks like this:

1
2
3
4
5
6
7
8
9
10
11
# config/services.yaml
services:
    # ...

    # first, create a service for the factory
    App\Email\NewsletterSenderFactory: ~

    # second, use the factory service as the first argument of the 'factory'
    # option and the factory method as the second argument
    App\Email\NewsletterSender:
        factory: ['@App\Email\NewsletterSenderFactory', 'createNewsletterSender']

Invokable Factories

Suppose you now change your factory method to __invoke() so that your factory service can be used as a callback:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/Email/InvokableNewsletterSenderFactory.php
namespace App\Email;

// ...
class InvokableNewsletterSenderFactory
{
    public function __invoke(): NewsletterSender
    {
        $newsletterSender = new NewsletterSender();

        // ...

        return $newsletterSender;
    }
}

Services can be created and configured via invokable factories by omitting the method name:

1
2
3
4
5
6
# config/services.yaml
services:
    # ...

    App\Email\NewsletterSender:
        factory: '@App\Email\InvokableNewsletterSenderFactory'

Passing Arguments to the Factory Method

Tip

Arguments to your factory method are autowired if that's enabled for your service.

If you need to pass arguments to the factory method you can use the arguments option. For example, suppose the createNewsletterSender() method in the previous examples takes the twig service as an argument:

1
2
3
4
5
6
7
# config/services.yaml
services:
    # ...

    App\Email\NewsletterSender:
        factory:   ['@App\Email\NewsletterSenderFactory', createNewsletterSender]
        arguments: ['@twig']

Expression-Based Factories

Instead of a PHP class, the factory can be an expression. This allows you to select the created object at runtime:

1
2
3
4
5
6
# config/services.yaml
services:
    App\Email\NewsletterSenderInterface:
        # use the "traceable_newsletter" service when debug is enabled, "newsletter" otherwise
        # "@=" indicates that this is an expression
        factory: '@=parameter("kernel.debug") ? service("traceable_newsletter") : service("newsletter")'

Factory expressions also support the arg() function, which returns an argument of the definition itself (e.g. '@=arg(0).createNewsletterSender() ?: service("default_newsletter_sender")').

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
TOC
    Version