This commit is contained in:
Marek
2026-03-24 00:04:55 +01:00
commit c5229e48ed
4225 changed files with 511461 additions and 0 deletions

View File

@@ -0,0 +1,430 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\DependencyInjection;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
trigger_deprecation('symfony/doctrine-bridge', '7.4', 'The "%s" class is deprecated, the code is incorporated into the extension classes of Doctrine bundles.', AbstractDoctrineExtension::class);
/**
* This abstract classes groups common code that Doctrine Object Manager extensions (ORM, MongoDB, CouchDB) need.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*
* @deprecated since Symfony 7.4, the code is incorporated into the extension classes of Doctrine bundles
*/
abstract class AbstractDoctrineExtension extends Extension
{
/**
* Used inside metadata driver method to simplify aggregation of data.
*/
protected array $aliasMap = [];
/**
* Used inside metadata driver method to simplify aggregation of data.
*/
protected array $drivers = [];
/**
* @param array $objectManager A configured object manager
*
* @throws \InvalidArgumentException
*/
protected function loadMappingInformation(array $objectManager, ContainerBuilder $container): void
{
if ($objectManager['auto_mapping']) {
// automatically register bundle mappings
foreach (array_keys($container->getParameter('kernel.bundles')) as $bundle) {
if (!isset($objectManager['mappings'][$bundle])) {
$objectManager['mappings'][$bundle] = [
'mapping' => true,
'is_bundle' => true,
];
}
}
}
foreach ($objectManager['mappings'] as $mappingName => $mappingConfig) {
if (null !== $mappingConfig && false === $mappingConfig['mapping']) {
continue;
}
$mappingConfig = array_replace([
'dir' => false,
'type' => false,
'prefix' => false,
], (array) $mappingConfig);
$mappingConfig['dir'] = $container->getParameterBag()->resolveValue($mappingConfig['dir']);
// a bundle configuration is detected by realizing that the specified dir is not absolute and existing
if (!isset($mappingConfig['is_bundle'])) {
$mappingConfig['is_bundle'] = !is_dir($mappingConfig['dir']);
}
if ($mappingConfig['is_bundle']) {
$bundle = null;
$bundleMetadata = null;
foreach ($container->getParameter('kernel.bundles') as $name => $class) {
if ($mappingName === $name) {
$bundle = new \ReflectionClass($class);
$bundleMetadata = $container->getParameter('kernel.bundles_metadata')[$name];
break;
}
}
if (null === $bundle) {
throw new \InvalidArgumentException(\sprintf('Bundle "%s" does not exist or it is not enabled.', $mappingName));
}
$mappingConfig = $this->getMappingDriverBundleConfigDefaults($mappingConfig, $bundle, $container, $bundleMetadata['path']);
if (!$mappingConfig) {
continue;
}
} elseif (!$mappingConfig['type']) {
$mappingConfig['type'] = 'attribute';
}
$this->assertValidMappingConfiguration($mappingConfig, $objectManager['name']);
$this->setMappingDriverConfig($mappingConfig, $mappingName);
$this->setMappingDriverAlias($mappingConfig, $mappingName);
}
}
/**
* Register the alias for this mapping driver.
*
* Aliases can be used in the Query languages of all the Doctrine object managers to simplify writing tasks.
*/
protected function setMappingDriverAlias(array $mappingConfig, string $mappingName): void
{
if (isset($mappingConfig['alias'])) {
$this->aliasMap[$mappingConfig['alias']] = $mappingConfig['prefix'];
} else {
$this->aliasMap[$mappingName] = $mappingConfig['prefix'];
}
}
/**
* Register the mapping driver configuration for later use with the object managers metadata driver chain.
*
* @throws \InvalidArgumentException
*/
protected function setMappingDriverConfig(array $mappingConfig, string $mappingName): void
{
$mappingDirectory = $mappingConfig['dir'];
if (!is_dir($mappingDirectory)) {
throw new \InvalidArgumentException(\sprintf('Invalid Doctrine mapping path given. Cannot load Doctrine mapping/bundle named "%s".', $mappingName));
}
$this->drivers[$mappingConfig['type']][$mappingConfig['prefix']] = realpath($mappingDirectory) ?: $mappingDirectory;
}
/**
* If this is a bundle controlled mapping all the missing information can be autodetected by this method.
*
* Returns false when autodetection failed, an array of the completed information otherwise.
*/
protected function getMappingDriverBundleConfigDefaults(array $bundleConfig, \ReflectionClass $bundle, ContainerBuilder $container, ?string $bundleDir = null): array|false
{
$bundleClassDir = \dirname($bundle->getFileName());
$bundleDir ??= $bundleClassDir;
if (!$bundleConfig['type']) {
$bundleConfig['type'] = $this->detectMetadataDriver($bundleDir, $container);
if (!$bundleConfig['type'] && $bundleDir !== $bundleClassDir) {
$bundleConfig['type'] = $this->detectMetadataDriver($bundleClassDir, $container);
}
}
if (!$bundleConfig['type']) {
// skip this bundle, no mapping information was found.
return false;
}
if (!$bundleConfig['dir']) {
if (\in_array($bundleConfig['type'], ['staticphp', 'attribute'], true)) {
$bundleConfig['dir'] = $bundleClassDir.'/'.$this->getMappingObjectDefaultName();
} else {
$bundleConfig['dir'] = $bundleDir.'/'.$this->getMappingResourceConfigDirectory($bundleDir);
}
} else {
$bundleConfig['dir'] = $bundleDir.'/'.$bundleConfig['dir'];
}
if (!$bundleConfig['prefix']) {
$bundleConfig['prefix'] = $bundle->getNamespaceName().'\\'.$this->getMappingObjectDefaultName();
}
return $bundleConfig;
}
/**
* Register all the collected mapping information with the object manager by registering the appropriate mapping drivers.
*/
protected function registerMappingDrivers(array $objectManager, ContainerBuilder $container): void
{
// configure metadata driver for each bundle based on the type of mapping files found
if ($container->hasDefinition($this->getObjectManagerElementName($objectManager['name'].'_metadata_driver'))) {
$chainDriverDef = $container->getDefinition($this->getObjectManagerElementName($objectManager['name'].'_metadata_driver'));
} else {
$chainDriverDef = new Definition($this->getMetadataDriverClass('driver_chain'));
}
foreach ($this->drivers as $driverType => $driverPaths) {
$mappingService = $this->getObjectManagerElementName($objectManager['name'].'_'.$driverType.'_metadata_driver');
if ($container->hasDefinition($mappingService)) {
$mappingDriverDef = $container->getDefinition($mappingService);
$args = $mappingDriverDef->getArguments();
$args[0] = array_merge(array_values($driverPaths), $args[0]);
$mappingDriverDef->setArguments($args);
} else {
$mappingDriverDef = new Definition($this->getMetadataDriverClass($driverType), [
array_values($driverPaths),
]);
}
if (str_contains($mappingDriverDef->getClass(), 'yml') || str_contains($mappingDriverDef->getClass(), 'xml')
|| str_contains($mappingDriverDef->getClass(), 'Yaml') || str_contains($mappingDriverDef->getClass(), 'Xml')
) {
$mappingDriverDef->setArguments([array_flip($driverPaths)]);
$mappingDriverDef->addMethodCall('setGlobalBasename', ['mapping']);
}
$container->setDefinition($mappingService, $mappingDriverDef);
foreach ($driverPaths as $prefix => $driverPath) {
$chainDriverDef->addMethodCall('addDriver', [new Reference($mappingService), $prefix]);
}
}
$container->setDefinition($this->getObjectManagerElementName($objectManager['name'].'_metadata_driver'), $chainDriverDef);
}
/**
* Assertion if the specified mapping information is valid.
*
* @throws \InvalidArgumentException
*/
protected function assertValidMappingConfiguration(array $mappingConfig, string $objectManagerName): void
{
if (!$mappingConfig['type'] || !$mappingConfig['dir'] || !$mappingConfig['prefix']) {
throw new \InvalidArgumentException(\sprintf('Mapping definitions for Doctrine manager "%s" require at least the "type", "dir" and "prefix" options.', $objectManagerName));
}
if (!is_dir($mappingConfig['dir'])) {
throw new \InvalidArgumentException(\sprintf('Specified non-existing directory "%s" as Doctrine mapping source.', $mappingConfig['dir']));
}
if (!\in_array($mappingConfig['type'], ['xml', 'yml', 'php', 'staticphp', 'attribute'], true)) {
throw new \InvalidArgumentException(\sprintf('Can only configure "xml", "yml", "php", "staticphp" or "attribute" through the DoctrineBundle. Use your own bundle to configure other metadata drivers. You can register them by adding a new driver to the "%s" service definition.', $this->getObjectManagerElementName($objectManagerName.'_metadata_driver')));
}
}
/**
* Detects what metadata driver to use for the supplied directory.
*/
protected function detectMetadataDriver(string $dir, ContainerBuilder $container): ?string
{
$configPath = $this->getMappingResourceConfigDirectory($dir);
$extension = $this->getMappingResourceExtension();
if (glob($dir.'/'.$configPath.'/*.'.$extension.'.xml', \GLOB_NOSORT)) {
$driver = 'xml';
} elseif (glob($dir.'/'.$configPath.'/*.'.$extension.'.yml', \GLOB_NOSORT)) {
$driver = 'yml';
} elseif (glob($dir.'/'.$configPath.'/*.'.$extension.'.php', \GLOB_NOSORT)) {
$driver = 'php';
} else {
// add the closest existing directory as a resource
$resource = $dir.'/'.$configPath;
while (!is_dir($resource)) {
$resource = \dirname($resource);
}
$container->fileExists($resource, false);
if ($container->fileExists($dir.'/'.$this->getMappingObjectDefaultName(), false)) {
return 'attribute';
}
return null;
}
$container->fileExists($dir.'/'.$configPath, false);
return $driver;
}
/**
* Loads a configured object manager metadata, query or result cache driver.
*
* @throws \InvalidArgumentException in case of unknown driver type
*/
protected function loadObjectManagerCacheDriver(array $objectManager, ContainerBuilder $container, string $cacheName): void
{
$this->loadCacheDriver($cacheName, $objectManager['name'], $objectManager[$cacheName.'_driver'], $container);
}
/**
* Loads a cache driver.
*
* @throws \InvalidArgumentException
*/
protected function loadCacheDriver(string $cacheName, string $objectManagerName, array $cacheDriver, ContainerBuilder $container): string
{
$cacheDriverServiceId = $this->getObjectManagerElementName($objectManagerName.'_'.$cacheName);
switch ($cacheDriver['type']) {
case 'service':
$container->setAlias($cacheDriverServiceId, new Alias($cacheDriver['id'], false));
return $cacheDriverServiceId;
case 'memcached':
$memcachedClass = !empty($cacheDriver['class']) ? $cacheDriver['class'] : '%'.$this->getObjectManagerElementName('cache.memcached.class').'%';
$memcachedInstanceClass = !empty($cacheDriver['instance_class']) ? $cacheDriver['instance_class'] : '%'.$this->getObjectManagerElementName('cache.memcached_instance.class').'%';
$memcachedHost = !empty($cacheDriver['host']) ? $cacheDriver['host'] : '%'.$this->getObjectManagerElementName('cache.memcached_host').'%';
$memcachedPort = !empty($cacheDriver['port']) ? $cacheDriver['port'] : '%'.$this->getObjectManagerElementName('cache.memcached_port').'%';
$cacheDef = new Definition($memcachedClass);
$memcachedInstance = new Definition($memcachedInstanceClass);
$memcachedInstance->addMethodCall('addServer', [
$memcachedHost, $memcachedPort,
]);
$container->setDefinition($this->getObjectManagerElementName(\sprintf('%s_memcached_instance', $objectManagerName)), $memcachedInstance);
$cacheDef->addMethodCall('setMemcached', [new Reference($this->getObjectManagerElementName(\sprintf('%s_memcached_instance', $objectManagerName)))]);
break;
case 'redis':
case 'valkey':
$redisClass = !empty($cacheDriver['class']) ? $cacheDriver['class'] : '%'.$this->getObjectManagerElementName('cache.redis.class').'%';
$redisInstanceClass = !empty($cacheDriver['instance_class']) ? $cacheDriver['instance_class'] : '%'.$this->getObjectManagerElementName('cache.redis_instance.class').'%';
$redisHost = !empty($cacheDriver['host']) ? $cacheDriver['host'] : '%'.$this->getObjectManagerElementName('cache.redis_host').'%';
$redisPort = !empty($cacheDriver['port']) ? $cacheDriver['port'] : '%'.$this->getObjectManagerElementName('cache.redis_port').'%';
$cacheDef = new Definition($redisClass);
$redisInstance = new Definition($redisInstanceClass);
$redisInstance->addMethodCall('connect', [
$redisHost, $redisPort,
]);
$container->setDefinition($this->getObjectManagerElementName(\sprintf('%s_redis_instance', $objectManagerName)), $redisInstance);
$cacheDef->addMethodCall('setRedis', [new Reference($this->getObjectManagerElementName(\sprintf('%s_redis_instance', $objectManagerName)))]);
break;
case 'apc':
case 'apcu':
case 'array':
case 'xcache':
case 'wincache':
case 'zenddata':
$cacheDef = new Definition('%'.$this->getObjectManagerElementName(\sprintf('cache.%s.class', $cacheDriver['type'])).'%');
break;
default:
throw new \InvalidArgumentException(\sprintf('"%s" is an unrecognized Doctrine cache driver.', $cacheDriver['type']));
}
if (!isset($cacheDriver['namespace'])) {
// generate a unique namespace for the given application
if ($container->hasParameter('cache.prefix.seed')) {
$seed = $container->getParameterBag()->resolveValue($container->getParameter('cache.prefix.seed'));
} else {
$seed = '_'.$container->getParameter('kernel.project_dir');
$seed .= '.'.$container->getParameter('kernel.container_class');
}
$namespace = 'sf_'.$this->getMappingResourceExtension().'_'.$objectManagerName.'_'.ContainerBuilder::hash($seed);
$cacheDriver['namespace'] = $namespace;
}
$cacheDef->addMethodCall('setNamespace', [$cacheDriver['namespace']]);
$container->setDefinition($cacheDriverServiceId, $cacheDef);
return $cacheDriverServiceId;
}
/**
* Returns a modified version of $managerConfigs.
*
* The manager called $autoMappedManager will map all bundles that are not mapped by other managers.
*/
protected function fixManagersAutoMappings(array $managerConfigs, array $bundles): array
{
if ($autoMappedManager = $this->validateAutoMapping($managerConfigs)) {
foreach (array_keys($bundles) as $bundle) {
foreach ($managerConfigs as $manager) {
if (isset($manager['mappings'][$bundle])) {
continue 2;
}
}
$managerConfigs[$autoMappedManager]['mappings'][$bundle] = [
'mapping' => true,
'is_bundle' => true,
];
}
$managerConfigs[$autoMappedManager]['auto_mapping'] = false;
}
return $managerConfigs;
}
/**
* Prefixes the relative dependency injection container path with the object manager prefix.
*
* @example $name is 'entity_manager' then the result would be 'doctrine.orm.entity_manager'
*/
abstract protected function getObjectManagerElementName(string $name): string;
/**
* Noun that describes the mapped objects such as Entity or Document.
*
* Will be used for autodetection of persistent objects directory.
*/
abstract protected function getMappingObjectDefaultName(): string;
/**
* Relative path from the bundle root to the directory where mapping files reside.
*/
abstract protected function getMappingResourceConfigDirectory(?string $bundleDir = null): string;
/**
* Extension used by the mapping files.
*/
abstract protected function getMappingResourceExtension(): string;
/**
* The class name used by the various mapping drivers.
*/
abstract protected function getMetadataDriverClass(string $driverType): string;
/**
* Search for a manager that is declared as 'auto_mapping' = true.
*
* @throws \LogicException
*/
private function validateAutoMapping(array $managerConfigs): ?string
{
$autoMappedManager = null;
foreach ($managerConfigs as $name => $manager) {
if (!$manager['auto_mapping']) {
continue;
}
if (null !== $autoMappedManager) {
throw new \LogicException(\sprintf('You cannot enable "auto_mapping" on more than one manager at the same time (found in "%s" and "%s"").', $autoMappedManager, $name));
}
$autoMappedManager = $name;
}
return $autoMappedManager;
}
}

View File

@@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Registers additional validators.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class DoctrineValidationPass implements CompilerPassInterface
{
public function __construct(
private readonly string $managerType,
) {
}
public function process(ContainerBuilder $container): void
{
$this->updateValidatorMappingFiles($container, 'xml', 'xml');
$this->updateValidatorMappingFiles($container, 'yaml', 'yml');
}
/**
* Gets the validation mapping files for the format and extends them with
* files matching a doctrine search pattern (Resources/config/validation.orm.xml).
*/
private function updateValidatorMappingFiles(ContainerBuilder $container, string $mapping, string $extension): void
{
if (!$container->hasParameter('validator.mapping.loader.'.$mapping.'_files_loader.mapping_files')) {
return;
}
$files = $container->getParameter('validator.mapping.loader.'.$mapping.'_files_loader.mapping_files');
$validationPath = '/config/validation.'.$this->managerType.'.'.$extension;
foreach ($container->getParameter('kernel.bundles_metadata') as $bundle) {
if ($container->fileExists($file = $bundle['path'].'/Resources'.$validationPath) || $container->fileExists($file = $bundle['path'].$validationPath)) {
$files[] = $file;
}
}
$container->setParameter('validator.mapping.loader.'.$mapping.'_files_loader.mapping_files', $files);
}
}

View File

@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass;
use Symfony\Bridge\Doctrine\Types\DatePointType;
use Symfony\Bridge\Doctrine\Types\DayPointType;
use Symfony\Bridge\Doctrine\Types\TimePointType;
use Symfony\Component\Clock\DatePoint;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
final class RegisterDatePointTypePass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (!class_exists(DatePoint::class)) {
return;
}
if (!$container->hasParameter('doctrine.dbal.connection_factory.types')) {
return;
}
$types = $container->getParameter('doctrine.dbal.connection_factory.types');
$types['date_point'] ??= ['class' => DatePointType::class];
$types['day_point'] ??= ['class' => DayPointType::class];
$types['time_point'] ??= ['class' => TimePointType::class];
$container->setParameter('doctrine.dbal.connection_factory.types', $types);
}
}

View File

@@ -0,0 +1,144 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass;
use Symfony\Bridge\Doctrine\ContainerAwareEventManager;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Reference;
/**
* Registers event listeners to the available doctrine connections.
*
* @author Jeremy Mikola <jmikola@gmail.com>
* @author Alexander <iam.asm89@gmail.com>
* @author David Maicher <mail@dmaicher.de>
*/
class RegisterEventListenersAndSubscribersPass implements CompilerPassInterface
{
private array $connections;
/**
* @var array<string, Definition>
*/
private array $eventManagers = [];
/**
* @param string $managerTemplate sprintf() template for generating the event
* manager's service ID for a connection name
* @param string $tagPrefix Tag prefix for listeners
*/
public function __construct(
private readonly string $connectionsParameter,
private readonly string $managerTemplate,
private readonly string $tagPrefix,
) {
}
public function process(ContainerBuilder $container): void
{
if (!$container->hasParameter($this->connectionsParameter)) {
return;
}
$this->connections = $container->getParameter($this->connectionsParameter);
$listenerRefs = $this->addTaggedServices($container);
// replace service container argument of event managers with smaller service locator
// so services can even remain private
foreach ($listenerRefs as $connection => $refs) {
$this->getEventManagerDef($container, $connection)
->replaceArgument(0, ServiceLocatorTagPass::register($container, $refs));
}
}
private function addTaggedServices(ContainerBuilder $container): array
{
$listenerRefs = [];
$managerDefs = [];
foreach ($this->findAndSortTags($container) as [$id, $tag]) {
$connections = isset($tag['connection'])
? [$container->getParameterBag()->resolveValue($tag['connection'])]
: array_keys($this->connections);
if (!isset($tag['event'])) {
throw new InvalidArgumentException(\sprintf('Doctrine event listener "%s" must specify the "event" attribute.', $id));
}
foreach ($connections as $con) {
if (!isset($this->connections[$con])) {
throw new RuntimeException(\sprintf('The Doctrine connection "%s" referenced in service "%s" does not exist. Available connections names: "%s".', $con, $id, implode('", "', array_keys($this->connections))));
}
if (!isset($managerDefs[$con])) {
$managerDef = $parentDef = $this->getEventManagerDef($container, $con);
while (!$parentDef->getClass() && $parentDef instanceof ChildDefinition) {
$parentDef = $container->findDefinition($parentDef->getParent());
}
$managerClass = $container->getParameterBag()->resolveValue($parentDef->getClass());
$managerDefs[$con] = [$managerDef, $managerClass];
} else {
[$managerDef, $managerClass] = $managerDefs[$con];
}
if (ContainerAwareEventManager::class === $managerClass) {
$refs = $managerDef->getArguments()[1] ?? [];
$listenerRefs[$con][$id] = new Reference($id);
$refs[] = [[$tag['event']], $id];
$managerDef->setArgument(1, $refs);
} else {
$managerDef->addMethodCall('addEventListener', [[$tag['event']], new Reference($id)]);
}
}
}
return $listenerRefs;
}
private function getEventManagerDef(ContainerBuilder $container, string $name): Definition
{
if (!isset($this->eventManagers[$name])) {
$this->eventManagers[$name] = $container->getDefinition(\sprintf($this->managerTemplate, $name));
}
return $this->eventManagers[$name];
}
/**
* Finds and orders all service tags with the given name by their priority.
*
* The order of additions must be respected for services having the same priority,
* and knowing that the \SplPriorityQueue class does not respect the FIFO method,
* we should not use this class.
*
* @see https://bugs.php.net/53710
* @see https://bugs.php.net/60926
*/
private function findAndSortTags(ContainerBuilder $container): array
{
$sortedTags = [];
foreach ($container->findTaggedServiceIds($this->tagPrefix.'.event_listener', true) as $serviceId => $tags) {
foreach ($tags as $attributes) {
$priority = $attributes['priority'] ?? 0;
$sortedTags[$priority][] = [$serviceId, $attributes];
}
}
krsort($sortedTags);
return array_merge(...$sortedTags);
}
}

View File

@@ -0,0 +1,169 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
/**
* Base class for the doctrine bundles to provide a compiler pass class that
* helps to register doctrine mappings.
*
* The compiler pass is meant to register the mappings with the metadata
* chain driver corresponding to one of the object managers.
*
* For concrete implementations, see the RegisterXyMappingsPass classes
* in the DoctrineBundle resp.
* DoctrineMongodbBundle, DoctrineCouchdbBundle and DoctrinePhpcrBundle.
*
* @author David Buchmann <david@liip.ch>
*/
abstract class RegisterMappingsPass implements CompilerPassInterface
{
/**
* The $managerParameters is an ordered list of container parameters that could provide the
* name of the manager to register these namespaces and alias on. The first non-empty name
* is used, the others skipped.
*
* The $aliasMap parameter can be used to define bundle namespace shortcuts like the
* DoctrineBundle provides automatically for objects in the default Entity/Document folder.
*
* @param Definition|Reference $driver Driver DI definition or reference
* @param string[] $namespaces List of namespaces handled by $driver
* @param string[] $managerParameters list of container parameters that could
* hold the manager name
* @param string $driverPattern Pattern for the metadata chain driver service ids (e.g. "doctrine.orm.%s_metadata_driver")
* @param string|false $enabledParameter Service container parameter that must be
* present to enable the mapping (regardless of the
* parameter value). Pass false to not do any check.
* @param string $configurationPattern Pattern for the Configuration service name,
* for example 'doctrine.orm.%s_configuration'.
* @param string $registerAliasMethodName Method name to call on the configuration service. This
* depends on the Doctrine implementation.
* For example addEntityNamespace.
* @param string[] $aliasMap Map of alias to namespace
*/
public function __construct(
protected Definition|Reference $driver,
protected array $namespaces,
protected array $managerParameters,
protected string $driverPattern,
protected string|false $enabledParameter = false,
private readonly string $configurationPattern = '',
private readonly string $registerAliasMethodName = '',
private readonly array $aliasMap = [],
) {
if ($aliasMap && (!$configurationPattern || !$registerAliasMethodName)) {
throw new \InvalidArgumentException('configurationPattern and registerAliasMethodName are required to register namespace alias.');
}
}
/**
* Register mappings and alias with the metadata drivers.
*/
public function process(ContainerBuilder $container): void
{
if (!$this->enabled($container)) {
return;
}
$mappingDriverDef = $this->getDriver($container);
$chainDriverDefService = $this->getChainDriverServiceName($container);
// Definition for a Doctrine\Persistence\Mapping\Driver\MappingDriverChain
$chainDriverDef = $container->getDefinition($chainDriverDefService);
foreach ($this->namespaces as $namespace) {
$chainDriverDef->addMethodCall('addDriver', [$mappingDriverDef, $namespace]);
}
if (!\count($this->aliasMap)) {
return;
}
$configurationServiceName = $this->getConfigurationServiceName($container);
// Definition of the Doctrine\...\Configuration class specific to the Doctrine flavour.
$configurationServiceDefinition = $container->getDefinition($configurationServiceName);
foreach ($this->aliasMap as $alias => $namespace) {
$configurationServiceDefinition->addMethodCall($this->registerAliasMethodName, [$alias, $namespace]);
}
}
/**
* Get the service name of the metadata chain driver that the mappings
* should be registered with.
*
* @throws InvalidArgumentException if non of the managerParameters has a
* non-empty value
*/
protected function getChainDriverServiceName(ContainerBuilder $container): string
{
return \sprintf($this->driverPattern, $this->getManagerName($container));
}
/**
* Create the service definition for the metadata driver.
*
* @param ContainerBuilder $container Passed on in case an extending class
* needs access to the container
*/
protected function getDriver(ContainerBuilder $container): Definition|Reference
{
return $this->driver;
}
/**
* Get the service name from the pattern and the configured manager name.
*
* @throws InvalidArgumentException if none of the managerParameters has a
* non-empty value
*/
private function getConfigurationServiceName(ContainerBuilder $container): string
{
return \sprintf($this->configurationPattern, $this->getManagerName($container));
}
/**
* Determine the manager name.
*
* The default implementation loops over the managerParameters and returns
* the first non-empty parameter.
*
* @throws InvalidArgumentException if none of the managerParameters is found in the container
*/
private function getManagerName(ContainerBuilder $container): string
{
foreach ($this->managerParameters as $param) {
if ($container->hasParameter($param)) {
$name = $container->getParameter($param);
if ($name) {
return $name;
}
}
}
throw new InvalidArgumentException(\sprintf('Could not find the manager name parameter in the container. Tried the following parameter names: "%s".', implode('", "', $this->managerParameters)));
}
/**
* Determine whether this mapping should be activated or not. This allows
* to take this decision with the container builder available.
*
* This default implementation checks if the class has the enabledParameter
* configured and if so if that parameter is present in the container.
*/
protected function enabled(ContainerBuilder $container): bool
{
return !$this->enabledParameter || $container->hasParameter($this->enabledParameter);
}
}

View File

@@ -0,0 +1,44 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass;
use Symfony\Bridge\Doctrine\Types\UlidType;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Uid\AbstractUid;
final class RegisterUidTypePass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (!class_exists(AbstractUid::class)) {
return;
}
if (!$container->hasParameter('doctrine.dbal.connection_factory.types')) {
return;
}
$typeDefinition = $container->getParameter('doctrine.dbal.connection_factory.types');
if (!isset($typeDefinition['uuid'])) {
$typeDefinition['uuid'] = ['class' => UuidType::class];
}
if (!isset($typeDefinition['ulid'])) {
$typeDefinition['ulid'] = ['class' => UlidType::class];
}
$container->setParameter('doctrine.dbal.connection_factory.types', $typeDefinition);
}
}

View File

@@ -0,0 +1,64 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\DependencyInjection\Security\UserProvider;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* EntityFactory creates services for Doctrine user provider.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Christophe Coevoet <stof@notk.org>
*
* @final
*/
class EntityFactory implements UserProviderFactoryInterface
{
public function __construct(
private readonly string $key,
private readonly string $providerId,
) {
}
public function create(ContainerBuilder $container, string $id, array $config): void
{
$container
->setDefinition($id, new ChildDefinition($this->providerId))
->addArgument($config['class'])
->addArgument($config['property'])
->addArgument($config['manager_name'])
;
}
public function getKey(): string
{
return $this->key;
}
public function addConfiguration(NodeDefinition $node): void
{
$node
->children()
->scalarNode('class')
->isRequired()
->info('The full entity class name of your user class.')
->cannotBeEmpty()
->end()
->scalarNode('property')->defaultNull()->end()
->scalarNode('manager_name')->defaultNull()->end()
->end()
;
}
}