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

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