vendor/symfony/routing/Loader/YamlFileLoader.php line 108

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\Component\Routing\Loader;
  11. use Symfony\Component\Config\Loader\FileLoader;
  12. use Symfony\Component\Config\Resource\FileResource;
  13. use Symfony\Component\Routing\Loader\Configurator\Traits\HostTrait;
  14. use Symfony\Component\Routing\Loader\Configurator\Traits\LocalizedRouteTrait;
  15. use Symfony\Component\Routing\Loader\Configurator\Traits\PrefixTrait;
  16. use Symfony\Component\Routing\RouteCollection;
  17. use Symfony\Component\Yaml\Exception\ParseException;
  18. use Symfony\Component\Yaml\Parser as YamlParser;
  19. use Symfony\Component\Yaml\Yaml;
  20. /**
  21.  * YamlFileLoader loads Yaml routing files.
  22.  *
  23.  * @author Fabien Potencier <fabien@symfony.com>
  24.  * @author Tobias Schultze <http://tobion.de>
  25.  */
  26. class YamlFileLoader extends FileLoader
  27. {
  28.     use HostTrait;
  29.     use LocalizedRouteTrait;
  30.     use PrefixTrait;
  31.     private const AVAILABLE_KEYS = [
  32.         'resource''type''prefix''path''host''schemes''methods''defaults''requirements''options''condition''controller''name_prefix''trailing_slash_on_root''locale''format''utf8''exclude''stateless',
  33.     ];
  34.     private $yamlParser;
  35.     /**
  36.      * Loads a Yaml file.
  37.      *
  38.      * @param string      $file A Yaml file path
  39.      * @param string|null $type The resource type
  40.      *
  41.      * @return RouteCollection
  42.      *
  43.      * @throws \InvalidArgumentException When a route can't be parsed because YAML is invalid
  44.      */
  45.     public function load($file, ?string $type null)
  46.     {
  47.         $path $this->locator->locate($file);
  48.         if (!stream_is_local($path)) {
  49.             throw new \InvalidArgumentException(sprintf('This is not a local file "%s".'$path));
  50.         }
  51.         if (!file_exists($path)) {
  52.             throw new \InvalidArgumentException(sprintf('File "%s" not found.'$path));
  53.         }
  54.         if (null === $this->yamlParser) {
  55.             $this->yamlParser = new YamlParser();
  56.         }
  57.         try {
  58.             $parsedConfig $this->yamlParser->parseFile($pathYaml::PARSE_CONSTANT);
  59.         } catch (ParseException $e) {
  60.             throw new \InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML: '$path).$e->getMessage(), 0$e);
  61.         }
  62.         $collection = new RouteCollection();
  63.         $collection->addResource(new FileResource($path));
  64.         // empty file
  65.         if (null === $parsedConfig) {
  66.             return $collection;
  67.         }
  68.         // not an array
  69.         if (!\is_array($parsedConfig)) {
  70.             throw new \InvalidArgumentException(sprintf('The file "%s" must contain a YAML array.'$path));
  71.         }
  72.         foreach ($parsedConfig as $name => $config) {
  73.             if (=== strpos($name'when@')) {
  74.                 if (!$this->env || 'when@'.$this->env !== $name) {
  75.                     continue;
  76.                 }
  77.                 foreach ($config as $name => $config) {
  78.                     $this->validate($config$name.'" when "@'.$this->env$path);
  79.                     if (isset($config['resource'])) {
  80.                         $this->parseImport($collection$config$path$file);
  81.                     } else {
  82.                         $this->parseRoute($collection$name$config$path);
  83.                     }
  84.                 }
  85.                 continue;
  86.             }
  87.             $this->validate($config$name$path);
  88.             if (isset($config['resource'])) {
  89.                 $this->parseImport($collection$config$path$file);
  90.             } else {
  91.                 $this->parseRoute($collection$name$config$path);
  92.             }
  93.         }
  94.         return $collection;
  95.     }
  96.     /**
  97.      * {@inheritdoc}
  98.      */
  99.     public function supports($resource, ?string $type null)
  100.     {
  101.         return \is_string($resource) && \in_array(pathinfo($resource\PATHINFO_EXTENSION), ['yml''yaml'], true) && (!$type || 'yaml' === $type);
  102.     }
  103.     /**
  104.      * Parses a route and adds it to the RouteCollection.
  105.      */
  106.     protected function parseRoute(RouteCollection $collectionstring $name, array $configstring $path)
  107.     {
  108.         if (isset($config['alias'])) {
  109.             $alias $collection->addAlias($name$config['alias']);
  110.             $deprecation $config['deprecated'] ?? null;
  111.             if (null !== $deprecation) {
  112.                 $alias->setDeprecated(
  113.                     $deprecation['package'],
  114.                     $deprecation['version'],
  115.                     $deprecation['message'] ?? ''
  116.                 );
  117.             }
  118.             return;
  119.         }
  120.         $defaults $config['defaults'] ?? [];
  121.         $requirements $config['requirements'] ?? [];
  122.         $options $config['options'] ?? [];
  123.         foreach ($requirements as $placeholder => $requirement) {
  124.             if (\is_int($placeholder)) {
  125.                 throw new \InvalidArgumentException(sprintf('A placeholder name must be a string (%d given). Did you forget to specify the placeholder key for the requirement "%s" of route "%s" in "%s"?'$placeholder$requirement$name$path));
  126.             }
  127.         }
  128.         if (isset($config['controller'])) {
  129.             $defaults['_controller'] = $config['controller'];
  130.         }
  131.         if (isset($config['locale'])) {
  132.             $defaults['_locale'] = $config['locale'];
  133.         }
  134.         if (isset($config['format'])) {
  135.             $defaults['_format'] = $config['format'];
  136.         }
  137.         if (isset($config['utf8'])) {
  138.             $options['utf8'] = $config['utf8'];
  139.         }
  140.         if (isset($config['stateless'])) {
  141.             $defaults['_stateless'] = $config['stateless'];
  142.         }
  143.         $routes $this->createLocalizedRoute($collection$name$config['path']);
  144.         $routes->addDefaults($defaults);
  145.         $routes->addRequirements($requirements);
  146.         $routes->addOptions($options);
  147.         $routes->setSchemes($config['schemes'] ?? []);
  148.         $routes->setMethods($config['methods'] ?? []);
  149.         $routes->setCondition($config['condition'] ?? null);
  150.         if (isset($config['host'])) {
  151.             $this->addHost($routes$config['host']);
  152.         }
  153.     }
  154.     /**
  155.      * Parses an import and adds the routes in the resource to the RouteCollection.
  156.      */
  157.     protected function parseImport(RouteCollection $collection, array $configstring $pathstring $file)
  158.     {
  159.         $type $config['type'] ?? null;
  160.         $prefix $config['prefix'] ?? '';
  161.         $defaults $config['defaults'] ?? [];
  162.         $requirements $config['requirements'] ?? [];
  163.         $options $config['options'] ?? [];
  164.         $host $config['host'] ?? null;
  165.         $condition $config['condition'] ?? null;
  166.         $schemes $config['schemes'] ?? null;
  167.         $methods $config['methods'] ?? null;
  168.         $trailingSlashOnRoot $config['trailing_slash_on_root'] ?? true;
  169.         $namePrefix $config['name_prefix'] ?? null;
  170.         $exclude $config['exclude'] ?? null;
  171.         if (isset($config['controller'])) {
  172.             $defaults['_controller'] = $config['controller'];
  173.         }
  174.         if (isset($config['locale'])) {
  175.             $defaults['_locale'] = $config['locale'];
  176.         }
  177.         if (isset($config['format'])) {
  178.             $defaults['_format'] = $config['format'];
  179.         }
  180.         if (isset($config['utf8'])) {
  181.             $options['utf8'] = $config['utf8'];
  182.         }
  183.         if (isset($config['stateless'])) {
  184.             $defaults['_stateless'] = $config['stateless'];
  185.         }
  186.         $this->setCurrentDir(\dirname($path));
  187.         /** @var RouteCollection[] $imported */
  188.         $imported $this->import($config['resource'], $typefalse$file$exclude) ?: [];
  189.         if (!\is_array($imported)) {
  190.             $imported = [$imported];
  191.         }
  192.         foreach ($imported as $subCollection) {
  193.             $this->addPrefix($subCollection$prefix$trailingSlashOnRoot);
  194.             if (null !== $host) {
  195.                 $this->addHost($subCollection$host);
  196.             }
  197.             if (null !== $condition) {
  198.                 $subCollection->setCondition($condition);
  199.             }
  200.             if (null !== $schemes) {
  201.                 $subCollection->setSchemes($schemes);
  202.             }
  203.             if (null !== $methods) {
  204.                 $subCollection->setMethods($methods);
  205.             }
  206.             if (null !== $namePrefix) {
  207.                 $subCollection->addNamePrefix($namePrefix);
  208.             }
  209.             $subCollection->addDefaults($defaults);
  210.             $subCollection->addRequirements($requirements);
  211.             $subCollection->addOptions($options);
  212.             $collection->addCollection($subCollection);
  213.         }
  214.     }
  215.     /**
  216.      * Validates the route configuration.
  217.      *
  218.      * @param array  $config A resource config
  219.      * @param string $name   The config key
  220.      * @param string $path   The loaded file path
  221.      *
  222.      * @throws \InvalidArgumentException If one of the provided config keys is not supported,
  223.      *                                   something is missing or the combination is nonsense
  224.      */
  225.     protected function validate($configstring $namestring $path)
  226.     {
  227.         if (!\is_array($config)) {
  228.             throw new \InvalidArgumentException(sprintf('The definition of "%s" in "%s" must be a YAML array.'$name$path));
  229.         }
  230.         if (isset($config['alias'])) {
  231.             $this->validateAlias($config$name$path);
  232.             return;
  233.         }
  234.         if ($extraKeys array_diff(array_keys($config), self::AVAILABLE_KEYS)) {
  235.             throw new \InvalidArgumentException(sprintf('The routing file "%s" contains unsupported keys for "%s": "%s". Expected one of: "%s".'$path$nameimplode('", "'$extraKeys), implode('", "'self::AVAILABLE_KEYS)));
  236.         }
  237.         if (isset($config['resource']) && isset($config['path'])) {
  238.             throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "resource" key and the "path" key for "%s". Choose between an import and a route definition.'$path$name));
  239.         }
  240.         if (!isset($config['resource']) && isset($config['type'])) {
  241.             throw new \InvalidArgumentException(sprintf('The "type" key for the route definition "%s" in "%s" is unsupported. It is only available for imports in combination with the "resource" key.'$name$path));
  242.         }
  243.         if (!isset($config['resource']) && !isset($config['path'])) {
  244.             throw new \InvalidArgumentException(sprintf('You must define a "path" for the route "%s" in file "%s".'$name$path));
  245.         }
  246.         if (isset($config['controller']) && isset($config['defaults']['_controller'])) {
  247.             throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "controller" key and the defaults key "_controller" for "%s".'$path$name));
  248.         }
  249.         if (isset($config['stateless']) && isset($config['defaults']['_stateless'])) {
  250.             throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "stateless" key and the defaults key "_stateless" for "%s".'$path$name));
  251.         }
  252.     }
  253.     /**
  254.      * @throws \InvalidArgumentException If one of the provided config keys is not supported,
  255.      *                                   something is missing or the combination is nonsense
  256.      */
  257.     private function validateAlias(array $configstring $namestring $path): void
  258.     {
  259.         foreach ($config as $key => $value) {
  260.             if (!\in_array($key, ['alias''deprecated'], true)) {
  261.                 throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify other keys than "alias" and "deprecated" for "%s".'$path$name));
  262.             }
  263.             if ('deprecated' === $key) {
  264.                 if (!isset($value['package'])) {
  265.                     throw new \InvalidArgumentException(sprintf('The routing file "%s" must specify the attribute "package" of the "deprecated" option for "%s".'$path$name));
  266.                 }
  267.                 if (!isset($value['version'])) {
  268.                     throw new \InvalidArgumentException(sprintf('The routing file "%s" must specify the attribute "version" of the "deprecated" option for "%s".'$path$name));
  269.                 }
  270.             }
  271.         }
  272.     }
  273. }