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));
// ...
}
}
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;
}
// ...
<?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;
}
// ...
}
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;
// ...
}
<?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());
}
}
// ...
}
}
<?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);
}
// ...
}
}
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);
}
}
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;
}
// ...
}
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];
}
}
The getInstance method will always return only one object of a particular validator.
Resources
Design patternsSymfony framework