Creational Design Patterns in Symfony Framework

Creational design patterns include: Simple factory, Abstract factory, Factory method, Builder, Prototype, and Singleton.
Let's find real examples of using these design patterns in the Symfony framework.

Simple factory

The Simple Factory pattern creates objects of various classes.

<?php
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;

class SessionHandlerFactory
{
    public static function createHandler(object|string $connection, array $options = []): AbstractSessionHandler
    {
        if ($query = \is_string($connection) ? parse_url($connection) : false) {
            parse_str($query['query'] ?? '', $query);

            if (($options['ttl'] ?? null) instanceof \Closure) {
                $query['ttl'] = $options['ttl'];
            }
        }
        $options = ($query ?: []) + $options;

        switch (true) {
            case $connection instanceof \Redis:
            case $connection instanceof \RedisArray:
            case $connection instanceof \RedisCluster:
            case $connection instanceof \Predis\ClientInterface:
            case $connection instanceof RedisProxy:
            case $connection instanceof RedisClusterProxy:
                return new RedisSessionHandler($connection);

            case $connection instanceof \Memcached:
                return new MemcachedSessionHandler($connection);

            case $connection instanceof \PDO:
                return new PdoSessionHandler($connection);

            case !\is_string($connection):
                throw new \InvalidArgumentException(sprintf('Unsupported Connection: "%s".', get_debug_type($connection)));
            case str_starts_with($connection, 'file://'):
                $savePath = substr($connection, 7);

                return new StrictSessionHandler(new NativeFileSessionHandler('' === $savePath ? null : $savePath));
            // ...
    }
}
Source code of SessionHandlerFactory on GitHub

The class of the created object is determined by the $connection parameter and one of them will be created: RedisSessionHandler, MemcachedSessionHandler, PdoSessionHandler, StrictSessionHandler, PdoSessionHandler.

Abstract factory

The Abstract factory pattern separates object factories into separate classes.

<?php
namespace Symfony\Component\HttpKernel\Controller;

final class ArgumentResolver implements ArgumentResolverInterface
{
    private ArgumentMetadataFactoryInterface $argumentMetadataFactory;
    private iterable $argumentValueResolvers;

    public function __construct(ArgumentMetadataFactoryInterface $argumentMetadataFactory = null, iterable $argumentValueResolvers = [])
    {
        $this->argumentMetadataFactory = $argumentMetadataFactory ?? new ArgumentMetadataFactory();
        $this->argumentValueResolvers = $argumentValueResolvers ?: self::getDefaultArgumentValueResolvers();
    }

    public function getArguments(Request $request, callable $controller, \ReflectionFunctionAbstract $reflector = null): array
    {
        $arguments = [];

        foreach ($this->argumentMetadataFactory->createArgumentMetadata($controller, $reflector) as $metadata) {
        // ...
        }

        return $arguments;
    }
    // ...
Source code of ArgumentResolver on GitHub
<?php
namespace Symfony\Component\HttpKernel\ControllerMetadata;

final class ArgumentMetadataFactory implements ArgumentMetadataFactoryInterface
{
    public function createArgumentMetadata(string|object|array $controller, \ReflectionFunctionAbstract $reflector = null): array
    {
        $arguments = [];
        $reflector ??= new \ReflectionFunction($controller(...));

        foreach ($reflector->getParameters() as $param) {
            $attributes = [];
            foreach ($param->getAttributes() as $reflectionAttribute) {
                if (class_exists($reflectionAttribute->getName())) {
                    $attributes[] = $reflectionAttribute->newInstance();
                }
            }

            $arguments[] = new ArgumentMetadata($param->getName(), $this->getType($param), $param->isVariadic(), $param->isDefaultValueAvailable(), $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null, $param->allowsNull(), $attributes);
        }

        return $arguments;
    }
    // ...
}
Source code of ArgumentMetadataFactory on GitHub

The $argumentMetadataFactory can be replaced with another factory that implements ArgumentMetadataFactoryInterface.

Factory method

The Factory method pattern obliges derived classes to implement an object creation method.

<?php
namespace Symfony\Component\Config\Definition\Builder;

abstract class NodeDefinition implements NodeParentInterface
{
    // ...
    abstract protected function createNode(): NodeInterface;
    // ...
}
Source code of NodeDefinition on GitHub
<?php
namespace Symfony\Component\Config\Definition\Builder;

class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinitionInterface
{
    // ...
    protected function createNode(): NodeInterface
    {
        if (null === $this->prototype) {
            $node = new ArrayNode($this->name, $this->parent, $this->pathSeparator);

            $this->validateConcreteNode($node);

            $node->setAddIfNotSet($this->addDefaults);

            foreach ($this->children as $child) {
                $child->parent = $node;
                $node->addChild($child->getNode());
            }
        }
        // ...
    }
}
Source code of ArrayNodeDefinition on GitHub
<?php
namespace Symfony\Component\Config\Definition\Builder;

// ...
class VariableNodeDefinition extends NodeDefinition
{
    // ...
    protected function createNode(): NodeInterface
    {
        $node = $this->instantiateNode();

        if (null !== $this->normalization) {
            $node->setNormalizationClosures($this->normalization->before);
        }
        // ...
    }
}
Source code of VariableNodeDefinition on GitHub

Builder

The Builder pattern provides methods for parameterizing and creating an object.

<?php
namespace Symfony\Component\PropertyAccess;

class PropertyAccessorBuilder
{
    private $magicMethods = PropertyAccessor::MAGIC_GET | PropertyAccessor::MAGIC_SET;
    private $throwExceptionOnInvalidIndex = false;
    private $throwExceptionOnInvalidPropertyPath = true;

    private $cacheItemPool;

    private $readInfoExtractor;

    private $writeInfoExtractor;

    public function enableMagicMethods(): static
    {
        $this->magicMethods = PropertyAccessor::MAGIC_GET | PropertyAccessor::MAGIC_SET | PropertyAccessor::MAGIC_CALL;

        return $this;
    }

    public function disableMagicMethods(): static
    {
        $this->magicMethods = PropertyAccessor::DISALLOW_MAGIC_METHODS;

        return $this;
    }
	
    // ...
   
    public function setWriteInfoExtractor(?PropertyWriteInfoExtractorInterface $writeInfoExtractor): static
    {
        $this->writeInfoExtractor = $writeInfoExtractor;

        return $this;
    }

    public function getWriteInfoExtractor(): ?PropertyWriteInfoExtractorInterface
    {
        return $this->writeInfoExtractor;
    }
    
    public function getPropertyAccessor(): PropertyAccessorInterface
    {
        $throw = PropertyAccessor::DO_NOT_THROW;

        if ($this->throwExceptionOnInvalidIndex) {
            $throw |= PropertyAccessor::THROW_ON_INVALID_INDEX;
        }

        if ($this->throwExceptionOnInvalidPropertyPath) {
            $throw |= PropertyAccessor::THROW_ON_INVALID_PROPERTY_PATH;
        }

        return new PropertyAccessor($this->magicMethods, $throw, $this->cacheItemPool, $this->readInfoExtractor, $this->writeInfoExtractor);
    }
}
Source code of PropertyAccessorBuilder on GitHub

The get*, set*, enable*, disable* methods set the parameters that will be applied to the created object in the getPropertyAccessor method.

Prototype

The Prototype pattern obliges derived classes to implement an object cloning method.
In its pure form, this pattern was not found in the Symfony framework. There is a very similar implementation in the Request class, in the __clone() magic method, except that the Request class does not have a base class with an abstract clone method declaration.

<?php
namespace Symfony\Component\HttpFoundation;

class Request
{
    // ...
    public function __clone()
    {
        $this->query = clone $this->query;
        $this->request = clone $this->request;
        $this->attributes = clone $this->attributes;
        $this->cookies = clone $this->cookies;
        $this->files = clone $this->files;
        $this->server = clone $this->server;
        $this->headers = clone $this->headers;
    }
    // ...
}
Source code of Request on GitHub

In the __clone method, all references to objects are also copied, except the session.

Singleton

The Singleton pattern provides a method for creating an object that is created only once.
In its pure form, the pattern could not be found in the Symfony framework. The ConstraintValidatorFactory class is very similar in implementation to this pattern.

<?php
namespace Symfony\Component\Validator;

class ConstraintValidatorFactory implements ConstraintValidatorFactoryInterface
{
    protected $validators = [];

    public function __construct()
    {
    }

    public function getInstance(Constraint $constraint): ConstraintValidatorInterface
    {
        $className = $constraint->validatedBy();

        if (!isset($this->validators[$className])) {
            $this->validators[$className] = 'validator.expression' === $className
                ? new ExpressionValidator()
                : new $className();
        }

        return $this->validators[$className];
    }
}
Source code of ConstraintValidatorFactory on GitHub

The getInstance method will always return only one object of a particular validator.

Resources

Design patterns
Symfony framework