The factory design pattern comes in all sorts and forms but has one simple purpose: it creates objects.
Even if you’ve never heard of design patterns or factories, chances are you may have already used or even created them unknowingly.
A very practical use of the factory pattern in Symfony is creating services. Let’s say we have a fictive image manipulation library that works like this:
namespace App\Controller;
use Foo\Bar\ImageManipulator;
class BookController
{
private ParameterBagInterface $parameterBag;
public function __construct(ParameterBagInterface $parameterBag)
{
$this->parameterBag = $parameterBag;
}
public function generateCover(Book $book): Response
{
$outputFile = (new ImageManipulator())
->setApiKey(
$this->parameterBag->get('image_api_key')
)
->setStorageType(
new SomeProvider(
$this->parameterBag->get('image_provider_username'),
$this->parameterBag->get('image_provider_password')
)
)
->setQuality('80')
->setFileType('jpeg')
->setSource($book->getCoverFile())
->setWidth(200)
->setHeight(100)
->generate();
// ....
}
}
It’s pretty straightforward, but if I’d told you we’d need to use the service in another controller to generate some other image, you’ll probably notice that we can abstract a large part of our code, namely:
(new ImageManipulator())
->setApiKey(
$this->parameterBag->get('image_api_key')
)
->setStorageType(
new SomeProvider(
$this->parameterBag->get('image_provider_username'),
$this->parameterBag->get('image_provider_password')
)
)
->setQuality('80')
->setFileType('jpeg');
This is a perfect situation for implementing the factory pattern. We want to define a class that creates a fully configured, re-usable ImageManipulator
object.
Abstracting that piece of code into a factory will look something like this:
namespace App\Factory;
use Foo\Test\ImageManipulator;
class ImageManipulatorFactory
{
private ParameterBagInterface $parameterBag;
public function __construct(ParameterBagInterface $parameterBag)
{
$this->parameterBag = $parameterBag;
}
public function create(): ImageManipulator
{
return (new ImageManipulator())
->setApiKey(
$this->parameterBag->get('image_api_key')
)
->setStorageType(
new SomeProvider(
$this->parameterBag->get('image_provider_username'),
$this->parameterBag->get('image_provider_password')
)
)
->setQuality('80')
->setFileType('jpeg');
}
}
Which will make our controller look a lot slimmer:
namespace App\Controller;
use Foo\Bar\ImageManipulator;
class BookController
{
private ImageManipulatorFactory $imageManipulatorFactory;
public function __construct(ImageManipulatorFactory $imageManipulatorFactory)
{
$this->imageManipulatorFactory = $imageManipulatorFactory;
}
public function generateCover(Book $book): Response
{
$manipulator = $this->imageManipulatorFactory->create();
$outputFile = $manipulator
->setSource($book->getCoverFile())
->setWidth(200)
->setHeight(100)
->generate();
// ....
}
}
It looks a lot better this way, doesn’t it?
But what if we could make it even slightly better? In Symfony, instead of having to inject the factory and call the create
method to get the service, we can immediately inject the fully configured ImageManipulator
class itself.
Open your services.yaml
file (or whatever config file you’d like to use), and add this:
services:
App\Factory\ImageManipulatorFactory: ~
Foo\Test\ImageManipulator:
factory: ['@App\Factory\ImageManipulatorFactory', 'create']
Simply explained, this tells Symfony that if Foo\Test\ImageManipulator
gets injected anywhere, instead of injecting that exact class, it should execute the create
method of our factory, and inject whatever that method returns. In the docs you’ll see that you can also apply this practice on static or invokable factories.
So now we can simply inject the ImageManipulator
library itself into our controller instead of the factory, and leave out one extra line of code:
namespace App\Controller;
use Foo\Test\ImageManipulator;
class BookController
{
private ImageManipulator $imageManipulator;
public function __construct(ImageManipulator $imageManipulator)
{
$this->imageManipulator = $imageManipulator;
}
public function generateCover(Book $book): Response
{
$outputFile = $this->imageManipulator
->setSource($book->getCoverFile())
->setWidth(200)
->setHeight(100)
->generate();
;
// ....
}
}
You can of course also do this on your own services. If for instance you have an auto-wired App\Service\ImageManipulator
service, adding it to the services
config key as we did above will overwrite the normal auto-wiring behavior, and will still have the same effect.
Side note: you could argue that this is less intuitive/pragmatic compared to simply injecting the factory, since you can’t immediately tell that the ImageManipulator
library is being created by the factory. Still, it’s not too hard finding that out with a little help of your IDE.