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.                 foreach (class_implements($classfalse) as $interface) {
  124.                     $this->singlyImplemented[$interface] = ($this->singlyImplemented[$interface] ?? $class) !== $class false $class;
  125.                 }
  126.             }
  127.         }
  128.         if ($this->autoRegisterAliasesForSinglyImplementedInterfaces) {
  129.             $this->registerAliasesForSinglyImplementedInterfaces();
  130.         }
  131.     }
  132.     public function registerAliasesForSinglyImplementedInterfaces()
  133.     {
  134.         foreach ($this->interfaces as $interface) {
  135.             if (!empty($this->singlyImplemented[$interface]) && !$this->container->has($interface)) {
  136.                 $this->container->setAlias($interface$this->singlyImplemented[$interface]);
  137.             }
  138.         }
  139.         $this->interfaces $this->singlyImplemented = [];
  140.     }
  141.     /**
  142.      * Registers a definition in the container with its instanceof-conditionals.
  143.      */
  144.     protected function setDefinition(string $idDefinition $definition)
  145.     {
  146.         $this->container->removeBindings($id);
  147.         if ($this->isLoadingInstanceof) {
  148.             if (!$definition instanceof ChildDefinition) {
  149.                 throw new InvalidArgumentException(sprintf('Invalid type definition "%s": ChildDefinition expected, "%s" given.'$idget_debug_type($definition)));
  150.             }
  151.             $this->instanceof[$id] = $definition;
  152.         } else {
  153.             $this->container->setDefinition($id$definition->setInstanceofConditionals($this->instanceof));
  154.         }
  155.     }
  156.     private function findClasses(string $namespacestring $pattern, array $excludePatterns, ?RegisterAutoconfigureAttributesPass $autoconfigureAttributes, ?string $source): array
  157.     {
  158.         $parameterBag $this->container->getParameterBag();
  159.         $excludePaths = [];
  160.         $excludePrefix null;
  161.         $excludePatterns $parameterBag->unescapeValue($parameterBag->resolveValue($excludePatterns));
  162.         foreach ($excludePatterns as $excludePattern) {
  163.             foreach ($this->glob($excludePatterntrue$resourcetruetrue) as $path => $info) {
  164.                 $excludePrefix ??= $resource->getPrefix();
  165.                 // normalize Windows slashes and remove trailing slashes
  166.                 $excludePaths[rtrim(str_replace('\\''/'$path), '/')] = true;
  167.             }
  168.         }
  169.         $pattern $parameterBag->unescapeValue($parameterBag->resolveValue($pattern));
  170.         $classes = [];
  171.         $prefixLen null;
  172.         foreach ($this->glob($patterntrue$resourcefalsefalse$excludePaths) as $path => $info) {
  173.             if (null === $prefixLen) {
  174.                 $prefixLen \strlen($resource->getPrefix());
  175.                 if ($excludePrefix && !str_starts_with($excludePrefix$resource->getPrefix())) {
  176.                     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));
  177.                 }
  178.             }
  179.             if (isset($excludePaths[str_replace('\\''/'$path)])) {
  180.                 continue;
  181.             }
  182.             if (!str_ends_with($path'.php') || !$info->isReadable()) {
  183.                 continue;
  184.             }
  185.             $class $namespace.ltrim(str_replace('/''\\'substr($path$prefixLen, -4)), '\\');
  186.             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)) {
  187.                 continue;
  188.             }
  189.             try {
  190.                 $r $this->container->getReflectionClass($class);
  191.             } catch (\ReflectionException $e) {
  192.                 $classes[$class] = $e->getMessage();
  193.                 continue;
  194.             }
  195.             // check to make sure the expected class exists
  196.             if (!$r) {
  197.                 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));
  198.             }
  199.             if ($r->isInstantiable() || $r->isInterface()) {
  200.                 $classes[$class] = null;
  201.             }
  202.             if ($autoconfigureAttributes && !$r->isInstantiable()) {
  203.                 $autoconfigureAttributes->processClass($this->container$r);
  204.             }
  205.         }
  206.         // track only for new & removed files
  207.         if ($resource instanceof GlobResource) {
  208.             $this->container->addResource($resource);
  209.         } else {
  210.             foreach ($resource as $path) {
  211.                 $this->container->fileExists($pathfalse);
  212.             }
  213.         }
  214.         if (null !== $prefixLen) {
  215.             $attributes null !== $source ? ['source' => sprintf('in "%s/%s"'basename(\dirname($source)), basename($source))] : [];
  216.             foreach ($excludePaths as $path => $_) {
  217.                 $class $namespace.ltrim(str_replace('/''\\'substr($path$prefixLenstr_ends_with($path'.php') ? -null)), '\\');
  218.                 if (!$this->container->has($class)) {
  219.                     $this->container->register($class)
  220.                         ->setAbstract(true)
  221.                         ->addTag('container.excluded'$attributes);
  222.                 }
  223.             }
  224.         }
  225.         return $classes;
  226.     }
  227. }