Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\Routing\Loader; Chris@0: Chris@17: use Symfony\Component\Config\Loader\FileLoader; Chris@17: use Symfony\Component\Config\Resource\FileResource; Chris@17: use Symfony\Component\Routing\Route; Chris@0: use Symfony\Component\Routing\RouteCollection; Chris@0: use Symfony\Component\Yaml\Exception\ParseException; Chris@0: use Symfony\Component\Yaml\Parser as YamlParser; Chris@0: Chris@0: /** Chris@0: * YamlFileLoader loads Yaml routing files. Chris@0: * Chris@0: * @author Fabien Potencier Chris@0: * @author Tobias Schultze Chris@0: */ Chris@0: class YamlFileLoader extends FileLoader Chris@0: { Chris@17: private static $availableKeys = [ Chris@14: 'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', 'controller', Chris@17: ]; Chris@0: private $yamlParser; Chris@0: Chris@0: /** Chris@0: * Loads a Yaml file. Chris@0: * Chris@0: * @param string $file A Yaml file path Chris@0: * @param string|null $type The resource type Chris@0: * Chris@0: * @return RouteCollection A RouteCollection instance Chris@0: * Chris@0: * @throws \InvalidArgumentException When a route can't be parsed because YAML is invalid Chris@0: */ Chris@0: public function load($file, $type = null) Chris@0: { Chris@0: $path = $this->locator->locate($file); Chris@0: Chris@0: if (!stream_is_local($path)) { Chris@0: throw new \InvalidArgumentException(sprintf('This is not a local file "%s".', $path)); Chris@0: } Chris@0: Chris@0: if (!file_exists($path)) { Chris@0: throw new \InvalidArgumentException(sprintf('File "%s" not found.', $path)); Chris@0: } Chris@0: Chris@0: if (null === $this->yamlParser) { Chris@0: $this->yamlParser = new YamlParser(); Chris@0: } Chris@0: Chris@14: $prevErrorHandler = set_error_handler(function ($level, $message, $script, $line) use ($file, &$prevErrorHandler) { Chris@14: $message = E_USER_DEPRECATED === $level ? preg_replace('/ on line \d+/', ' in "'.$file.'"$0', $message) : $message; Chris@14: Chris@14: return $prevErrorHandler ? $prevErrorHandler($level, $message, $script, $line) : false; Chris@14: }); Chris@14: Chris@0: try { Chris@14: $parsedConfig = $this->yamlParser->parseFile($path); Chris@0: } catch (ParseException $e) { Chris@0: throw new \InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML.', $path), 0, $e); Chris@14: } finally { Chris@14: restore_error_handler(); Chris@0: } Chris@0: Chris@0: $collection = new RouteCollection(); Chris@0: $collection->addResource(new FileResource($path)); Chris@0: Chris@0: // empty file Chris@0: if (null === $parsedConfig) { Chris@0: return $collection; Chris@0: } Chris@0: Chris@0: // not an array Chris@17: if (!\is_array($parsedConfig)) { Chris@0: throw new \InvalidArgumentException(sprintf('The file "%s" must contain a YAML array.', $path)); Chris@0: } Chris@0: Chris@0: foreach ($parsedConfig as $name => $config) { Chris@0: $this->validate($config, $name, $path); Chris@0: Chris@0: if (isset($config['resource'])) { Chris@0: $this->parseImport($collection, $config, $path, $file); Chris@0: } else { Chris@0: $this->parseRoute($collection, $name, $config, $path); Chris@0: } Chris@0: } Chris@0: Chris@0: return $collection; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function supports($resource, $type = null) Chris@0: { Chris@17: return \is_string($resource) && \in_array(pathinfo($resource, PATHINFO_EXTENSION), ['yml', 'yaml'], true) && (!$type || 'yaml' === $type); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Parses a route and adds it to the RouteCollection. Chris@0: * Chris@0: * @param RouteCollection $collection A RouteCollection instance Chris@0: * @param string $name Route name Chris@0: * @param array $config Route definition Chris@0: * @param string $path Full path of the YAML file being processed Chris@0: */ Chris@0: protected function parseRoute(RouteCollection $collection, $name, array $config, $path) Chris@0: { Chris@17: $defaults = isset($config['defaults']) ? $config['defaults'] : []; Chris@17: $requirements = isset($config['requirements']) ? $config['requirements'] : []; Chris@17: $options = isset($config['options']) ? $config['options'] : []; Chris@0: $host = isset($config['host']) ? $config['host'] : ''; Chris@17: $schemes = isset($config['schemes']) ? $config['schemes'] : []; Chris@17: $methods = isset($config['methods']) ? $config['methods'] : []; Chris@0: $condition = isset($config['condition']) ? $config['condition'] : null; Chris@0: Chris@14: if (isset($config['controller'])) { Chris@14: $defaults['_controller'] = $config['controller']; Chris@14: } Chris@14: Chris@0: $route = new Route($config['path'], $defaults, $requirements, $options, $host, $schemes, $methods, $condition); Chris@0: Chris@0: $collection->add($name, $route); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Parses an import and adds the routes in the resource to the RouteCollection. Chris@0: * Chris@0: * @param RouteCollection $collection A RouteCollection instance Chris@0: * @param array $config Route definition Chris@0: * @param string $path Full path of the YAML file being processed Chris@0: * @param string $file Loaded file name Chris@0: */ Chris@0: protected function parseImport(RouteCollection $collection, array $config, $path, $file) Chris@0: { Chris@0: $type = isset($config['type']) ? $config['type'] : null; Chris@0: $prefix = isset($config['prefix']) ? $config['prefix'] : ''; Chris@17: $defaults = isset($config['defaults']) ? $config['defaults'] : []; Chris@17: $requirements = isset($config['requirements']) ? $config['requirements'] : []; Chris@17: $options = isset($config['options']) ? $config['options'] : []; Chris@0: $host = isset($config['host']) ? $config['host'] : null; Chris@0: $condition = isset($config['condition']) ? $config['condition'] : null; Chris@0: $schemes = isset($config['schemes']) ? $config['schemes'] : null; Chris@0: $methods = isset($config['methods']) ? $config['methods'] : null; Chris@0: Chris@14: if (isset($config['controller'])) { Chris@14: $defaults['_controller'] = $config['controller']; Chris@14: } Chris@14: Chris@17: $this->setCurrentDir(\dirname($path)); Chris@0: Chris@14: $imported = $this->import($config['resource'], $type, false, $file); Chris@14: Chris@17: if (!\is_array($imported)) { Chris@17: $imported = [$imported]; Chris@0: } Chris@14: Chris@14: foreach ($imported as $subCollection) { Chris@14: /* @var $subCollection RouteCollection */ Chris@14: $subCollection->addPrefix($prefix); Chris@14: if (null !== $host) { Chris@14: $subCollection->setHost($host); Chris@14: } Chris@14: if (null !== $condition) { Chris@14: $subCollection->setCondition($condition); Chris@14: } Chris@14: if (null !== $schemes) { Chris@14: $subCollection->setSchemes($schemes); Chris@14: } Chris@14: if (null !== $methods) { Chris@14: $subCollection->setMethods($methods); Chris@14: } Chris@14: $subCollection->addDefaults($defaults); Chris@14: $subCollection->addRequirements($requirements); Chris@14: $subCollection->addOptions($options); Chris@14: Chris@14: $collection->addCollection($subCollection); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Validates the route configuration. Chris@0: * Chris@0: * @param array $config A resource config Chris@0: * @param string $name The config key Chris@0: * @param string $path The loaded file path Chris@0: * Chris@0: * @throws \InvalidArgumentException If one of the provided config keys is not supported, Chris@0: * something is missing or the combination is nonsense Chris@0: */ Chris@0: protected function validate($config, $name, $path) Chris@0: { Chris@17: if (!\is_array($config)) { Chris@0: throw new \InvalidArgumentException(sprintf('The definition of "%s" in "%s" must be a YAML array.', $name, $path)); Chris@0: } Chris@0: if ($extraKeys = array_diff(array_keys($config), self::$availableKeys)) { Chris@17: throw new \InvalidArgumentException(sprintf('The routing file "%s" contains unsupported keys for "%s": "%s". Expected one of: "%s".', $path, $name, implode('", "', $extraKeys), implode('", "', self::$availableKeys))); Chris@0: } Chris@0: if (isset($config['resource']) && isset($config['path'])) { Chris@17: 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)); Chris@0: } Chris@0: if (!isset($config['resource']) && isset($config['type'])) { Chris@17: 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)); Chris@0: } Chris@0: if (!isset($config['resource']) && !isset($config['path'])) { Chris@17: throw new \InvalidArgumentException(sprintf('You must define a "path" for the route "%s" in file "%s".', $name, $path)); Chris@0: } Chris@14: if (isset($config['controller']) && isset($config['defaults']['_controller'])) { Chris@14: throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "controller" key and the defaults key "_controller" for "%s".', $path, $name)); Chris@14: } Chris@0: } Chris@0: }