vendor/symfony/doctrine-bridge/DependencyInjection/AbstractDoctrineExtension.php line 442

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Bridge\Doctrine\DependencyInjection;
  11. use Symfony\Component\Config\Resource\GlobResource;
  12. use Symfony\Component\DependencyInjection\Alias;
  13. use Symfony\Component\DependencyInjection\ContainerBuilder;
  14. use Symfony\Component\DependencyInjection\Definition;
  15. use Symfony\Component\DependencyInjection\Reference;
  16. use Symfony\Component\HttpKernel\DependencyInjection\Extension;
  17. /**
  18.  * This abstract classes groups common code that Doctrine Object Manager extensions (ORM, MongoDB, CouchDB) need.
  19.  *
  20.  * @author Benjamin Eberlei <kontakt@beberlei.de>
  21.  */
  22. abstract class AbstractDoctrineExtension extends Extension
  23. {
  24.     /**
  25.      * Used inside metadata driver method to simplify aggregation of data.
  26.      */
  27.     protected $aliasMap = [];
  28.     /**
  29.      * Used inside metadata driver method to simplify aggregation of data.
  30.      */
  31.     protected $drivers = [];
  32.     /**
  33.      * @param array $objectManager A configured object manager
  34.      *
  35.      * @throws \InvalidArgumentException
  36.      */
  37.     protected function loadMappingInformation(array $objectManagerContainerBuilder $container)
  38.     {
  39.         if ($objectManager['auto_mapping']) {
  40.             // automatically register bundle mappings
  41.             foreach (array_keys($container->getParameter('kernel.bundles')) as $bundle) {
  42.                 if (!isset($objectManager['mappings'][$bundle])) {
  43.                     $objectManager['mappings'][$bundle] = [
  44.                         'mapping' => true,
  45.                         'is_bundle' => true,
  46.                     ];
  47.                 }
  48.             }
  49.         }
  50.         foreach ($objectManager['mappings'] as $mappingName => $mappingConfig) {
  51.             if (null !== $mappingConfig && false === $mappingConfig['mapping']) {
  52.                 continue;
  53.             }
  54.             $mappingConfig array_replace([
  55.                 'dir' => false,
  56.                 'type' => false,
  57.                 'prefix' => false,
  58.             ], (array) $mappingConfig);
  59.             $mappingConfig['dir'] = $container->getParameterBag()->resolveValue($mappingConfig['dir']);
  60.             // a bundle configuration is detected by realizing that the specified dir is not absolute and existing
  61.             if (!isset($mappingConfig['is_bundle'])) {
  62.                 $mappingConfig['is_bundle'] = !is_dir($mappingConfig['dir']);
  63.             }
  64.             if ($mappingConfig['is_bundle']) {
  65.                 $bundle null;
  66.                 $bundleMetadata null;
  67.                 foreach ($container->getParameter('kernel.bundles') as $name => $class) {
  68.                     if ($mappingName === $name) {
  69.                         $bundle = new \ReflectionClass($class);
  70.                         $bundleMetadata $container->getParameter('kernel.bundles_metadata')[$name];
  71.                         break;
  72.                     }
  73.                 }
  74.                 if (null === $bundle) {
  75.                     throw new \InvalidArgumentException(sprintf('Bundle "%s" does not exist or it is not enabled.'$mappingName));
  76.                 }
  77.                 $mappingConfig $this->getMappingDriverBundleConfigDefaults($mappingConfig$bundle$container$bundleMetadata['path']);
  78.                 if (!$mappingConfig) {
  79.                     continue;
  80.                 }
  81.             } elseif (!$mappingConfig['type']) {
  82.                 $mappingConfig['type'] = $this->detectMappingType($mappingConfig['dir'], $container);
  83.             }
  84.             $this->assertValidMappingConfiguration($mappingConfig$objectManager['name']);
  85.             $this->setMappingDriverConfig($mappingConfig$mappingName);
  86.             $this->setMappingDriverAlias($mappingConfig$mappingName);
  87.         }
  88.     }
  89.     /**
  90.      * Register the alias for this mapping driver.
  91.      *
  92.      * Aliases can be used in the Query languages of all the Doctrine object managers to simplify writing tasks.
  93.      */
  94.     protected function setMappingDriverAlias(array $mappingConfigstring $mappingName)
  95.     {
  96.         if (isset($mappingConfig['alias'])) {
  97.             $this->aliasMap[$mappingConfig['alias']] = $mappingConfig['prefix'];
  98.         } else {
  99.             $this->aliasMap[$mappingName] = $mappingConfig['prefix'];
  100.         }
  101.     }
  102.     /**
  103.      * Register the mapping driver configuration for later use with the object managers metadata driver chain.
  104.      *
  105.      * @throws \InvalidArgumentException
  106.      */
  107.     protected function setMappingDriverConfig(array $mappingConfigstring $mappingName)
  108.     {
  109.         $mappingDirectory $mappingConfig['dir'];
  110.         if (!is_dir($mappingDirectory)) {
  111.             throw new \InvalidArgumentException(sprintf('Invalid Doctrine mapping path given. Cannot load Doctrine mapping/bundle named "%s".'$mappingName));
  112.         }
  113.         $this->drivers[$mappingConfig['type']][$mappingConfig['prefix']] = realpath($mappingDirectory) ?: $mappingDirectory;
  114.     }
  115.     /**
  116.      * If this is a bundle controlled mapping all the missing information can be autodetected by this method.
  117.      *
  118.      * Returns false when autodetection failed, an array of the completed information otherwise.
  119.      */
  120.     protected function getMappingDriverBundleConfigDefaults(array $bundleConfig\ReflectionClass $bundleContainerBuilder $containerstring $bundleDir null): array|false
  121.     {
  122.         $bundleClassDir \dirname($bundle->getFileName());
  123.         $bundleDir ??= $bundleClassDir;
  124.         if (!$bundleConfig['type']) {
  125.             $bundleConfig['type'] = $this->detectMetadataDriver($bundleDir$container);
  126.             if (!$bundleConfig['type'] && $bundleDir !== $bundleClassDir) {
  127.                 $bundleConfig['type'] = $this->detectMetadataDriver($bundleClassDir$container);
  128.             }
  129.         }
  130.         if (!$bundleConfig['type']) {
  131.             // skip this bundle, no mapping information was found.
  132.             return false;
  133.         }
  134.         if (!$bundleConfig['dir']) {
  135.             if (\in_array($bundleConfig['type'], ['annotation''staticphp''attribute'])) {
  136.                 $bundleConfig['dir'] = $bundleClassDir.'/'.$this->getMappingObjectDefaultName();
  137.             } else {
  138.                 $bundleConfig['dir'] = $bundleDir.'/'.$this->getMappingResourceConfigDirectory($bundleDir);
  139.             }
  140.         } else {
  141.             $bundleConfig['dir'] = $bundleDir.'/'.$bundleConfig['dir'];
  142.         }
  143.         if (!$bundleConfig['prefix']) {
  144.             $bundleConfig['prefix'] = $bundle->getNamespaceName().'\\'.$this->getMappingObjectDefaultName();
  145.         }
  146.         return $bundleConfig;
  147.     }
  148.     /**
  149.      * Register all the collected mapping information with the object manager by registering the appropriate mapping drivers.
  150.      */
  151.     protected function registerMappingDrivers(array $objectManagerContainerBuilder $container)
  152.     {
  153.         // configure metadata driver for each bundle based on the type of mapping files found
  154.         if ($container->hasDefinition($this->getObjectManagerElementName($objectManager['name'].'_metadata_driver'))) {
  155.             $chainDriverDef $container->getDefinition($this->getObjectManagerElementName($objectManager['name'].'_metadata_driver'));
  156.         } else {
  157.             $chainDriverDef = new Definition($this->getMetadataDriverClass('driver_chain'));
  158.             $chainDriverDef->setPublic(false);
  159.         }
  160.         foreach ($this->drivers as $driverType => $driverPaths) {
  161.             $mappingService $this->getObjectManagerElementName($objectManager['name'].'_'.$driverType.'_metadata_driver');
  162.             if ($container->hasDefinition($mappingService)) {
  163.                 $mappingDriverDef $container->getDefinition($mappingService);
  164.                 $args $mappingDriverDef->getArguments();
  165.                 if ('annotation' == $driverType) {
  166.                     $args[1] = array_merge(array_values($driverPaths), $args[1]);
  167.                 } else {
  168.                     $args[0] = array_merge(array_values($driverPaths), $args[0]);
  169.                 }
  170.                 $mappingDriverDef->setArguments($args);
  171.             } elseif ('attribute' === $driverType) {
  172.                 $mappingDriverDef = new Definition($this->getMetadataDriverClass($driverType), [
  173.                     array_values($driverPaths),
  174.                 ]);
  175.             } elseif ('annotation' == $driverType) {
  176.                 $mappingDriverDef = new Definition($this->getMetadataDriverClass($driverType), [
  177.                     new Reference($this->getObjectManagerElementName('metadata.annotation_reader')),
  178.                     array_values($driverPaths),
  179.                 ]);
  180.             } else {
  181.                 $mappingDriverDef = new Definition($this->getMetadataDriverClass($driverType), [
  182.                     array_values($driverPaths),
  183.                 ]);
  184.             }
  185.             $mappingDriverDef->setPublic(false);
  186.             if (str_contains($mappingDriverDef->getClass(), 'yml') || str_contains($mappingDriverDef->getClass(), 'xml')) {
  187.                 $mappingDriverDef->setArguments([array_flip($driverPaths)]);
  188.                 $mappingDriverDef->addMethodCall('setGlobalBasename', ['mapping']);
  189.             }
  190.             $container->setDefinition($mappingService$mappingDriverDef);
  191.             foreach ($driverPaths as $prefix => $driverPath) {
  192.                 $chainDriverDef->addMethodCall('addDriver', [new Reference($mappingService), $prefix]);
  193.             }
  194.         }
  195.         $container->setDefinition($this->getObjectManagerElementName($objectManager['name'].'_metadata_driver'), $chainDriverDef);
  196.     }
  197.     /**
  198.      * Assertion if the specified mapping information is valid.
  199.      *
  200.      * @throws \InvalidArgumentException
  201.      */
  202.     protected function assertValidMappingConfiguration(array $mappingConfigstring $objectManagerName)
  203.     {
  204.         if (!$mappingConfig['type'] || !$mappingConfig['dir'] || !$mappingConfig['prefix']) {
  205.             throw new \InvalidArgumentException(sprintf('Mapping definitions for Doctrine manager "%s" require at least the "type", "dir" and "prefix" options.'$objectManagerName));
  206.         }
  207.         if (!is_dir($mappingConfig['dir'])) {
  208.             throw new \InvalidArgumentException(sprintf('Specified non-existing directory "%s" as Doctrine mapping source.'$mappingConfig['dir']));
  209.         }
  210.         if (!\in_array($mappingConfig['type'], ['xml''yml''annotation''php''staticphp''attribute'])) {
  211.             throw new \InvalidArgumentException(sprintf('Can only configure "xml", "yml", "annotation", "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')));
  212.         }
  213.     }
  214.     /**
  215.      * Detects what metadata driver to use for the supplied directory.
  216.      */
  217.     protected function detectMetadataDriver(string $dirContainerBuilder $container): ?string
  218.     {
  219.         $configPath $this->getMappingResourceConfigDirectory($dir);
  220.         $extension $this->getMappingResourceExtension();
  221.         if (glob($dir.'/'.$configPath.'/*.'.$extension.'.xml'\GLOB_NOSORT)) {
  222.             $driver 'xml';
  223.         } elseif (glob($dir.'/'.$configPath.'/*.'.$extension.'.yml'\GLOB_NOSORT)) {
  224.             $driver 'yml';
  225.         } elseif (glob($dir.'/'.$configPath.'/*.'.$extension.'.php'\GLOB_NOSORT)) {
  226.             $driver 'php';
  227.         } else {
  228.             // add the closest existing directory as a resource
  229.             $resource $dir.'/'.$configPath;
  230.             while (!is_dir($resource)) {
  231.                 $resource \dirname($resource);
  232.             }
  233.             $container->fileExists($resourcefalse);
  234.             if ($container->fileExists($discoveryPath $dir.'/'.$this->getMappingObjectDefaultName(), false)) {
  235.                 return $this->detectMappingType($discoveryPath$container);
  236.             }
  237.             return null;
  238.         }
  239.         $container->fileExists($dir.'/'.$configPathfalse);
  240.         return $driver;
  241.     }
  242.     /**
  243.      * Detects what mapping type to use for the supplied directory.
  244.      *
  245.      * @return string A mapping type 'attribute' or 'annotation'
  246.      */
  247.     private function detectMappingType(string $directoryContainerBuilder $container): string
  248.     {
  249.         $type 'attribute';
  250.         $glob = new GlobResource($directory'*'true);
  251.         $container->addResource($glob);
  252.         $quotedMappingObjectName preg_quote($this->getMappingObjectDefaultName(), '/');
  253.         foreach ($glob as $file) {
  254.             $content file_get_contents($file);
  255.             if (
  256.                 preg_match('/^#\[.*'.$quotedMappingObjectName.'\b/m'$content) ||
  257.                 preg_match('/^#\[.*Embeddable\b/m'$content)
  258.             ) {
  259.                 break;
  260.             }
  261.             if (
  262.                 preg_match('/^(?: \*|\/\*\*) @.*'.$quotedMappingObjectName.'\b/m'$content) ||
  263.                 preg_match('/^(?: \*|\/\*\*) @.*Embeddable\b/m'$content)
  264.             ) {
  265.                 $type 'annotation';
  266.                 break;
  267.             }
  268.         }
  269.         return $type;
  270.     }
  271.     /**
  272.      * Loads a configured object manager metadata, query or result cache driver.
  273.      *
  274.      * @throws \InvalidArgumentException in case of unknown driver type
  275.      */
  276.     protected function loadObjectManagerCacheDriver(array $objectManagerContainerBuilder $containerstring $cacheName)
  277.     {
  278.         $this->loadCacheDriver($cacheName$objectManager['name'], $objectManager[$cacheName.'_driver'], $container);
  279.     }
  280.     /**
  281.      * Loads a cache driver.
  282.      *
  283.      * @throws \InvalidArgumentException
  284.      */
  285.     protected function loadCacheDriver(string $cacheNamestring $objectManagerName, array $cacheDriverContainerBuilder $container): string
  286.     {
  287.         $cacheDriverServiceId $this->getObjectManagerElementName($objectManagerName.'_'.$cacheName);
  288.         switch ($cacheDriver['type']) {
  289.             case 'service':
  290.                 $container->setAlias($cacheDriverServiceId, new Alias($cacheDriver['id'], false));
  291.                 return $cacheDriverServiceId;
  292.             case 'memcached':
  293.                 $memcachedClass = !empty($cacheDriver['class']) ? $cacheDriver['class'] : '%'.$this->getObjectManagerElementName('cache.memcached.class').'%';
  294.                 $memcachedInstanceClass = !empty($cacheDriver['instance_class']) ? $cacheDriver['instance_class'] : '%'.$this->getObjectManagerElementName('cache.memcached_instance.class').'%';
  295.                 $memcachedHost = !empty($cacheDriver['host']) ? $cacheDriver['host'] : '%'.$this->getObjectManagerElementName('cache.memcached_host').'%';
  296.                 $memcachedPort = !empty($cacheDriver['port']) ? $cacheDriver['port'] : '%'.$this->getObjectManagerElementName('cache.memcached_port').'%';
  297.                 $cacheDef = new Definition($memcachedClass);
  298.                 $memcachedInstance = new Definition($memcachedInstanceClass);
  299.                 $memcachedInstance->addMethodCall('addServer', [
  300.                     $memcachedHost$memcachedPort,
  301.                 ]);
  302.                 $container->setDefinition($this->getObjectManagerElementName(sprintf('%s_memcached_instance'$objectManagerName)), $memcachedInstance);
  303.                 $cacheDef->addMethodCall('setMemcached', [new Reference($this->getObjectManagerElementName(sprintf('%s_memcached_instance'$objectManagerName)))]);
  304.                 break;
  305.             case 'redis':
  306.                 $redisClass = !empty($cacheDriver['class']) ? $cacheDriver['class'] : '%'.$this->getObjectManagerElementName('cache.redis.class').'%';
  307.                 $redisInstanceClass = !empty($cacheDriver['instance_class']) ? $cacheDriver['instance_class'] : '%'.$this->getObjectManagerElementName('cache.redis_instance.class').'%';
  308.                 $redisHost = !empty($cacheDriver['host']) ? $cacheDriver['host'] : '%'.$this->getObjectManagerElementName('cache.redis_host').'%';
  309.                 $redisPort = !empty($cacheDriver['port']) ? $cacheDriver['port'] : '%'.$this->getObjectManagerElementName('cache.redis_port').'%';
  310.                 $cacheDef = new Definition($redisClass);
  311.                 $redisInstance = new Definition($redisInstanceClass);
  312.                 $redisInstance->addMethodCall('connect', [
  313.                     $redisHost$redisPort,
  314.                 ]);
  315.                 $container->setDefinition($this->getObjectManagerElementName(sprintf('%s_redis_instance'$objectManagerName)), $redisInstance);
  316.                 $cacheDef->addMethodCall('setRedis', [new Reference($this->getObjectManagerElementName(sprintf('%s_redis_instance'$objectManagerName)))]);
  317.                 break;
  318.             case 'apc':
  319.             case 'apcu':
  320.             case 'array':
  321.             case 'xcache':
  322.             case 'wincache':
  323.             case 'zenddata':
  324.                 $cacheDef = new Definition('%'.$this->getObjectManagerElementName(sprintf('cache.%s.class'$cacheDriver['type'])).'%');
  325.                 break;
  326.             default:
  327.                 throw new \InvalidArgumentException(sprintf('"%s" is an unrecognized Doctrine cache driver.'$cacheDriver['type']));
  328.         }
  329.         $cacheDef->setPublic(false);
  330.         if (!isset($cacheDriver['namespace'])) {
  331.             // generate a unique namespace for the given application
  332.             if ($container->hasParameter('cache.prefix.seed')) {
  333.                 $seed $container->getParameterBag()->resolveValue($container->getParameter('cache.prefix.seed'));
  334.             } else {
  335.                 $seed '_'.$container->getParameter('kernel.project_dir');
  336.                 $seed .= '.'.$container->getParameter('kernel.container_class');
  337.             }
  338.             $namespace 'sf_'.$this->getMappingResourceExtension().'_'.$objectManagerName.'_'.ContainerBuilder::hash($seed);
  339.             $cacheDriver['namespace'] = $namespace;
  340.         }
  341.         $cacheDef->addMethodCall('setNamespace', [$cacheDriver['namespace']]);
  342.         $container->setDefinition($cacheDriverServiceId$cacheDef);
  343.         return $cacheDriverServiceId;
  344.     }
  345.     /**
  346.      * Returns a modified version of $managerConfigs.
  347.      *
  348.      * The manager called $autoMappedManager will map all bundles that are not mapped by other managers.
  349.      */
  350.     protected function fixManagersAutoMappings(array $managerConfigs, array $bundles): array
  351.     {
  352.         if ($autoMappedManager $this->validateAutoMapping($managerConfigs)) {
  353.             foreach (array_keys($bundles) as $bundle) {
  354.                 foreach ($managerConfigs as $manager) {
  355.                     if (isset($manager['mappings'][$bundle])) {
  356.                         continue 2;
  357.                     }
  358.                 }
  359.                 $managerConfigs[$autoMappedManager]['mappings'][$bundle] = [
  360.                     'mapping' => true,
  361.                     'is_bundle' => true,
  362.                 ];
  363.             }
  364.             $managerConfigs[$autoMappedManager]['auto_mapping'] = false;
  365.         }
  366.         return $managerConfigs;
  367.     }
  368.     /**
  369.      * Prefixes the relative dependency injection container path with the object manager prefix.
  370.      *
  371.      * @example $name is 'entity_manager' then the result would be 'doctrine.orm.entity_manager'
  372.      */
  373.     abstract protected function getObjectManagerElementName(string $name): string;
  374.     /**
  375.      * Noun that describes the mapped objects such as Entity or Document.
  376.      *
  377.      * Will be used for autodetection of persistent objects directory.
  378.      */
  379.     abstract protected function getMappingObjectDefaultName(): string;
  380.     /**
  381.      * Relative path from the bundle root to the directory where mapping files reside.
  382.      */
  383.     abstract protected function getMappingResourceConfigDirectory(string $bundleDir null): string;
  384.     /**
  385.      * Extension used by the mapping files.
  386.      */
  387.     abstract protected function getMappingResourceExtension(): string;
  388.     /**
  389.      * The class name used by the various mapping drivers.
  390.      */
  391.     abstract protected function getMetadataDriverClass(string $driverType): string;
  392.     /**
  393.      * Search for a manager that is declared as 'auto_mapping' = true.
  394.      *
  395.      * @throws \LogicException
  396.      */
  397.     private function validateAutoMapping(array $managerConfigs): ?string
  398.     {
  399.         $autoMappedManager null;
  400.         foreach ($managerConfigs as $name => $manager) {
  401.             if (!$manager['auto_mapping']) {
  402.                 continue;
  403.             }
  404.             if (null !== $autoMappedManager) {
  405.                 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));
  406.             }
  407.             $autoMappedManager $name;
  408.         }
  409.         return $autoMappedManager;
  410.     }
  411. }