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

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