Using a Factory to Create Services
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")').