vendor/symfony/dependency-injection/Loader/FileLoader.php line 121

  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\Component\DependencyInjection\Loader;
  11. use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException;
  12. use Symfony\Component\Config\Exception\LoaderLoadException;
  13. use Symfony\Component\Config\FileLocatorInterface;
  14. use Symfony\Component\Config\Loader\FileLoader as BaseFileLoader;
  15. use Symfony\Component\Config\Loader\Loader;
  16. use Symfony\Component\Config\Resource\GlobResource;
  17. use Symfony\Component\DependencyInjection\Attribute\When;
  18. use Symfony\Component\DependencyInjection\ChildDefinition;
  19. use Symfony\Component\DependencyInjection\Compiler\RegisterAutoconfigureAttributesPass;
  20. use Symfony\Component\DependencyInjection\ContainerBuilder;
  21. use Symfony\Component\DependencyInjection\Definition;
  22. use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
  23. /**
  24.  * FileLoader is the abstract class used by all built-in loaders that are file based.
  25.  *
  26.  * @author Fabien Potencier <fabien@symfony.com>
  27.  */
  28. abstract class FileLoader extends BaseFileLoader
  29. {
  30.     public const ANONYMOUS_ID_REGEXP '/^\.\d+_[^~]*+~[._a-zA-Z\d]{7}$/';
  31.     protected $container;
  32.     protected $isLoadingInstanceof false;
  33.     protected $instanceof = [];
  34.     protected $interfaces = [];
  35.     protected $singlyImplemented = [];
  36.     protected $autoRegisterAliasesForSinglyImplementedInterfaces true;
  37.     public function __construct(ContainerBuilder $containerFileLocatorInterface $locatorstring $env null)
  38.     {
  39.         $this->container $container;
  40.         parent::__construct($locator$env);
  41.     }
  42.     /**
  43.      * @param bool|string $ignoreErrors Whether errors should be ignored; pass "not_found" to ignore only when the loaded resource is not found
  44.      */
  45.     public function import(mixed $resourcestring $type nullbool|string $ignoreErrors falsestring $sourceResource null$exclude null): mixed
  46.     {
  47.         $args \func_get_args();
  48.         if ($ignoreNotFound 'not_found' === $ignoreErrors) {
  49.             $args[2] = false;
  50.         } elseif (!\is_bool($ignoreErrors)) {
  51.             throw new \TypeError(sprintf('Invalid argument $ignoreErrors provided to "%s::import()": boolean or "not_found" expected, "%s" given.', static::class, get_debug_type($ignoreErrors)));
  52.         }
  53.         try {
  54.             return parent::import(...$args);
  55.         } catch (LoaderLoadException $e) {
  56.             if (!$ignoreNotFound || !($prev $e->getPrevious()) instanceof FileLocatorFileNotFoundException) {
  57.                 throw $e;
  58.             }
  59.             foreach ($prev->getTrace() as $frame) {
  60.                 if ('import' === ($frame['function'] ?? null) && is_a($frame['class'] ?? ''Loader::class, true)) {
  61.                     break;
  62.                 }
  63.             }
  64.             if (__FILE__ !== $frame['file']) {
  65.                 throw $e;
  66.             }
  67.         }
  68.         return null;
  69.     }
  70.     /**
  71.      * Registers a set of classes as services using PSR-4 for discovery.
  72.      *
  73.      * @param Definition           $prototype A definition to use as template
  74.      * @param string               $namespace The namespace prefix of classes in the scanned directory
  75.      * @param string               $resource  The directory to look for classes, glob-patterns allowed
  76.      * @param string|string[]|null $exclude   A globbed path of files to exclude or an array of globbed paths of files to exclude
  77.      * @param string|null          $source    The path to the file that defines the auto-discovery rule
  78.      */
  79.     public function registerClasses(Definition $prototypestring $namespacestring $resourcestring|array $exclude null/* , string $source = null */)
  80.     {
  81.         if (!str_ends_with($namespace'\\')) {
  82.             throw new InvalidArgumentException(sprintf('Namespace prefix must end with a "\\": "%s".'$namespace));
  83.         }
  84.         if (!preg_match('/^(?:[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+\\\\)++$/'$namespace)) {
  85.             throw new InvalidArgumentException(sprintf('Namespace is not a valid PSR-4 prefix: "%s".'$namespace));
  86.         }
  87.         // This can happen with YAML files
  88.         if (\is_array($exclude) && \in_array(null$excludetrue)) {
  89.             throw new InvalidArgumentException('The exclude list must not contain a "null" value.');
  90.         }
  91.         // This can happen with XML files
  92.         if (\is_array($exclude) && \in_array(''$excludetrue)) {
  93.             throw new InvalidArgumentException('The exclude list must not contain an empty value.');
  94.         }
  95.         $source \func_num_args() > func_get_arg(4) : null;
  96.         $autoconfigureAttributes = new RegisterAutoconfigureAttributesPass();
  97.         $autoconfigureAttributes $autoconfigureAttributes->accept($prototype) ? $autoconfigureAttributes null;
  98.         $classes $this->findClasses($namespace$resource, (array) $exclude$autoconfigureAttributes$source);
  99.         // prepare for deep cloning
  100.         $serializedPrototype serialize($prototype);
  101.         foreach ($classes as $class => $errorMessage) {
  102.             if (null === $errorMessage && $autoconfigureAttributes && $this->env) {
  103.                 $r $this->container->getReflectionClass($class);
  104.                 $attribute null;
  105.                 foreach ($r->getAttributes(When::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
  106.                     if ($this->env === $attribute->newInstance()->env) {
  107.                         $attribute null;
  108.                         break;
  109.                     }
  110.                 }
  111.                 if (null !== $attribute) {
  112.                     continue;
  113.                 }
  114.             }
  115.             if (interface_exists($classfalse)) {
  116.                 $this->interfaces[] = $class;
  117.             } else {
  118.                 $this->setDefinition($class$definition unserialize($serializedPrototype));
  119.                 if (null !== $errorMessage) {
  120.                     $definition->addError($errorMessage);
  121.                     continue;
  122.                 }
  123.                 $definition->setClass($class);
  124.                 foreach (class_implements($classfalse) as $interface) {
  125.                     $this->singlyImplemented[$interface] = ($this->singlyImplemented[$interface] ?? $class) !== $class false $class;
  126.                 }
  127.             }
  128.         }
  129.         if ($this->autoRegisterAliasesForSinglyImplementedInterfaces) {
  130.             $this->registerAliasesForSinglyImplementedInterfaces();
  131.         }
  132.     }
  133.     public function registerAliasesForSinglyImplementedInterfaces()
  134.     {
  135.         foreach ($this->interfaces as $interface) {
  136.             if (!empty($this->singlyImplemented[$interface]) && !$this->container->has($interface)) {
  137.                 $this->container->setAlias($interface$this->singlyImplemented[$interface]);
  138.             }
  139.         }
  140.         $this->interfaces $this->singlyImplemented = [];
  141.     }
  142.     /**
  143.      * Registers a definition in the container with its instanceof-conditionals.
  144.      */
  145.     protected function setDefinition(string $idDefinition $definition)
  146.     {
  147.         $this->container->removeBindings($id);
  148.         if ($this->isLoadingInstanceof) {
  149.             if (!$definition instanceof ChildDefinition) {
  150.                 throw new InvalidArgumentException(sprintf('Invalid type definition "%s": ChildDefinition expected, "%s" given.'$idget_debug_type($definition)));
  151.             }
  152.             $this->instanceof[$id] = $definition;
  153.         } else {
  154.             $this->container->setDefinition($id$definition->setInstanceofConditionals($this->instanceof));
  155.         }
  156.     }
  157.     private function findClasses(string $namespacestring $pattern, array $excludePatterns, ?RegisterAutoconfigureAttributesPass $autoconfigureAttributes, ?string $source): array
  158.     {
  159.         $parameterBag $this->container->getParameterBag();
  160.         $excludePaths = [];
  161.         $excludePrefix null;
  162.         $excludePatterns $parameterBag->unescapeValue($parameterBag->resolveValue($excludePatterns));
  163.         foreach ($excludePatterns as $excludePattern) {
  164.             foreach ($this->glob($excludePatterntrue$resourcetruetrue) as $path => $info) {
  165.                 $excludePrefix ??= $resource->getPrefix();
  166.                 // normalize Windows slashes and remove trailing slashes
  167.                 $excludePaths[rtrim(str_replace('\\''/'$path), '/')] = true;
  168.             }
  169.         }
  170.         $pattern $parameterBag->unescapeValue($parameterBag->resolveValue($pattern));
  171.         $classes = [];
  172.         $prefixLen null;
  173.         foreach ($this->glob($patterntrue$resourcefalsefalse$excludePaths) as $path => $info) {
  174.             if (null === $prefixLen) {
  175.                 $prefixLen \strlen($resource->getPrefix());
  176.                 if ($excludePrefix && !str_starts_with($excludePrefix$resource->getPrefix())) {
  177.                     throw new InvalidArgumentException(sprintf('Invalid "exclude" pattern when importing classes for "%s": make sure your "exclude" pattern (%s) is a subset of the "resource" pattern (%s).'$namespace$excludePattern$pattern));
  178.                 }
  179.             }
  180.             if (isset($excludePaths[str_replace('\\''/'$path)])) {
  181.                 continue;
  182.             }
  183.             if (!str_ends_with($path'.php') || !$info->isReadable()) {
  184.                 continue;
  185.             }
  186.             $class $namespace.ltrim(str_replace('/''\\'substr($path$prefixLen, -4)), '\\');
  187.             if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+$/'$class)) {
  188.                 continue;
  189.             }
  190.             try {
  191.                 $r $this->container->getReflectionClass($class);
  192.             } catch (\ReflectionException $e) {
  193.                 $classes[$class] = $e->getMessage();
  194.                 continue;
  195.             }
  196.             // check to make sure the expected class exists
  197.             if (!$r) {
  198.                 throw new InvalidArgumentException(sprintf('Expected to find class "%s" in file "%s" while importing services from resource "%s", but it was not found! Check the namespace prefix used with the resource.'$class$path$pattern));
  199.             }
  200.             if ($r->isInstantiable() || $r->isInterface()) {
  201.                 $classes[$class] = null;
  202.             }
  203.             if ($autoconfigureAttributes && !$r->isInstantiable()) {
  204.                 $autoconfigureAttributes->processClass($this->container$r);
  205.             }
  206.         }
  207.         // track only for new & removed files
  208.         if ($resource instanceof GlobResource) {
  209.             $this->container->addResource($resource);
  210.         } else {
  211.             foreach ($resource as $path) {
  212.                 $this->container->fileExists($pathfalse);
  213.             }
  214.         }
  215.         if (null !== $prefixLen) {
  216.             $attributes null !== $source ? ['source' => sprintf('in "%s/%s"'basename(\dirname($source)), basename($source))] : [];
  217.             foreach ($excludePaths as $path => $_) {
  218.                 $class $namespace.ltrim(str_replace('/''\\'substr($path$prefixLenstr_ends_with($path'.php') ? -null)), '\\');
  219.                 if (!$this->container->has($class)) {
  220.                     $this->container->register($class$class)
  221.                         ->setAbstract(true)
  222.                         ->addTag('container.excluded'$attributes);
  223.                 }
  224.             }
  225.         }
  226.         return $classes;
  227.     }
  228. }