updated backend

This commit is contained in:
Torsten 2020-01-17 10:58:01 +01:00
parent 1fea0f4fe7
commit 11521c2339
3459 changed files with 72735 additions and 168535 deletions

View file

@ -15,6 +15,7 @@ use Doctrine\Common\Annotations\Reader;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\Config\Loader\LoaderResolverInterface;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Routing\Annotation\Route as RouteAnnotation;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
@ -32,7 +33,6 @@ use Symfony\Component\Routing\RouteCollection;
* recognizes several parameters: requirements, options, defaults, schemes,
* methods, host, and name. The name parameter is mandatory.
* Here is an example of how you should be able to use it:
*
* /**
* * @Route("/Blog")
* * /
@ -44,7 +44,6 @@ use Symfony\Component\Routing\RouteCollection;
* public function index()
* {
* }
*
* /**
* * @Route("/{id}", name="blog_post", requirements = {"id" = "\d+"})
* * /
@ -120,11 +119,9 @@ abstract class AnnotationClassLoader implements LoaderInterface
}
if (0 === $collection->count() && $class->hasMethod('__invoke')) {
$globals = $this->resetGlobals();
foreach ($this->reader->getClassAnnotations($class) as $annot) {
if ($annot instanceof $this->routeAnnotationClass) {
$globals['path'] = '';
$globals['name'] = '';
$this->addRoute($collection, $annot, $globals, $class, $class->getMethod('__invoke'));
}
}
@ -133,6 +130,10 @@ abstract class AnnotationClassLoader implements LoaderInterface
return $collection;
}
/**
* @param RouteAnnotation $annot or an object that exposes a similar interface
* @param array $globals
*/
protected function addRoute(RouteCollection $collection, $annot, $globals, \ReflectionClass $class, \ReflectionMethod $method)
{
$name = $annot->getName();
@ -141,13 +142,16 @@ abstract class AnnotationClassLoader implements LoaderInterface
}
$name = $globals['name'].$name;
$defaults = array_replace($globals['defaults'], $annot->getDefaults());
foreach ($method->getParameters() as $param) {
if (false !== strpos($globals['path'].$annot->getPath(), sprintf('{%s}', $param->getName())) && !isset($defaults[$param->getName()]) && $param->isDefaultValueAvailable()) {
$defaults[$param->getName()] = $param->getDefaultValue();
$requirements = $annot->getRequirements();
foreach ($requirements as $placeholder => $requirement) {
if (\is_int($placeholder)) {
@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::%s()"?', $placeholder, $requirement, $name, $class->getName(), $method->getName()), E_USER_DEPRECATED);
}
}
$requirements = array_replace($globals['requirements'], $annot->getRequirements());
$defaults = array_replace($globals['defaults'], $annot->getDefaults());
$requirements = array_replace($globals['requirements'], $requirements);
$options = array_replace($globals['options'], $annot->getOptions());
$schemes = array_merge($globals['schemes'], $annot->getSchemes());
$methods = array_merge($globals['methods'], $annot->getMethods());
@ -162,11 +166,57 @@ abstract class AnnotationClassLoader implements LoaderInterface
$condition = $globals['condition'];
}
$route = $this->createRoute($globals['path'].$annot->getPath(), $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
$path = $annot->getLocalizedPaths() ?: $annot->getPath();
$prefix = $globals['localized_paths'] ?: $globals['path'];
$paths = [];
$this->configureRoute($route, $class, $method, $annot);
if (\is_array($path)) {
if (!\is_array($prefix)) {
foreach ($path as $locale => $localePath) {
$paths[$locale] = $prefix.$localePath;
}
} elseif ($missing = array_diff_key($prefix, $path)) {
throw new \LogicException(sprintf('Route to "%s" is missing paths for locale(s) "%s".', $class->name.'::'.$method->name, implode('", "', array_keys($missing))));
} else {
foreach ($path as $locale => $localePath) {
if (!isset($prefix[$locale])) {
throw new \LogicException(sprintf('Route to "%s" with locale "%s" is missing a corresponding prefix in class "%s".', $method->name, $locale, $class->name));
}
$collection->add($name, $route);
$paths[$locale] = $prefix[$locale].$localePath;
}
}
} elseif (\is_array($prefix)) {
foreach ($prefix as $locale => $localePrefix) {
$paths[$locale] = $localePrefix.$path;
}
} else {
$paths[] = $prefix.$path;
}
foreach ($method->getParameters() as $param) {
if (isset($defaults[$param->name]) || !$param->isDefaultValueAvailable()) {
continue;
}
foreach ($paths as $locale => $path) {
if (preg_match(sprintf('/\{%s(?:<.*?>)?\}/', preg_quote($param->name)), $path)) {
$defaults[$param->name] = $param->getDefaultValue();
break;
}
}
}
foreach ($paths as $locale => $path) {
$route = $this->createRoute($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
$this->configureRoute($route, $class, $method, $annot);
if (0 !== $locale) {
$route->setDefault('_locale', $locale);
$route->setDefault('_canonical_route', $name);
$collection->add($name.'.'.$locale, $route);
} else {
$collection->add($name, $route);
}
}
}
/**
@ -194,14 +244,12 @@ abstract class AnnotationClassLoader implements LoaderInterface
/**
* Gets the default route name for a class method.
*
* @param \ReflectionClass $class
* @param \ReflectionMethod $method
*
* @return string
*/
protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMethod $method)
{
$name = strtolower(str_replace('\\', '_', $class->name).'_'.$method->name);
$name = str_replace('\\', '_', $class->name).'_'.$method->name;
$name = \function_exists('mb_strtolower') && preg_match('//u', $name) ? mb_strtolower($name, 'UTF-8') : strtolower($name);
if ($this->defaultRouteIndex > 0) {
$name .= '_'.$this->defaultRouteIndex;
}
@ -212,17 +260,7 @@ abstract class AnnotationClassLoader implements LoaderInterface
protected function getGlobals(\ReflectionClass $class)
{
$globals = array(
'path' => '',
'requirements' => array(),
'options' => array(),
'defaults' => array(),
'schemes' => array(),
'methods' => array(),
'host' => '',
'condition' => '',
'name' => '',
);
$globals = $this->resetGlobals();
if ($annot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass)) {
if (null !== $annot->getName()) {
@ -233,6 +271,8 @@ abstract class AnnotationClassLoader implements LoaderInterface
$globals['path'] = $annot->getPath();
}
$globals['localized_paths'] = $annot->getLocalizedPaths();
if (null !== $annot->getRequirements()) {
$globals['requirements'] = $annot->getRequirements();
}
@ -260,11 +300,33 @@ abstract class AnnotationClassLoader implements LoaderInterface
if (null !== $annot->getCondition()) {
$globals['condition'] = $annot->getCondition();
}
foreach ($globals['requirements'] as $placeholder => $requirement) {
if (\is_int($placeholder)) {
@trigger_error(sprintf('A placeholder name must be a string (%d given). Did you forget to specify the placeholder key for the requirement "%s" in "%s"?', $placeholder, $requirement, $class->getName()), E_USER_DEPRECATED);
}
}
}
return $globals;
}
private function resetGlobals()
{
return [
'path' => null,
'localized_paths' => [],
'requirements' => [],
'options' => [],
'defaults' => [],
'schemes' => [],
'methods' => [],
'host' => '',
'condition' => '',
'name' => '',
];
}
protected function createRoute($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition)
{
return new Route($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition);

View file

@ -32,7 +32,7 @@ class AnnotationFileLoader extends FileLoader
public function __construct(FileLocatorInterface $locator, AnnotationClassLoader $loader)
{
if (!\function_exists('token_get_all')) {
throw new \RuntimeException('The Tokenizer extension is required for the routing annotation loaders.');
throw new \LogicException('The Tokenizer extension is required for the routing annotation loaders.');
}
parent::__construct($locator);
@ -46,7 +46,7 @@ class AnnotationFileLoader extends FileLoader
* @param string $file A PHP file path
* @param string|null $type The resource type
*
* @return RouteCollection A RouteCollection instance
* @return RouteCollection|null A RouteCollection instance
*
* @throws \InvalidArgumentException When the file does not exist or its routes cannot be parsed
*/
@ -56,13 +56,16 @@ class AnnotationFileLoader extends FileLoader
$collection = new RouteCollection();
if ($class = $this->findClass($path)) {
$refl = new \ReflectionClass($class);
if ($refl->isAbstract()) {
return null;
}
$collection->addResource(new FileResource($path));
$collection->addCollection($this->loader->load($class, $type));
}
if (\PHP_VERSION_ID >= 70000) {
// PHP 7 memory manager will not release after token_get_all(), see https://bugs.php.net/70098
gc_mem_caches();
}
gc_mem_caches();
return $collection;
}
@ -105,7 +108,7 @@ class AnnotationFileLoader extends FileLoader
if (true === $namespace && T_STRING === $token[0]) {
$namespace = $token[1];
while (isset($tokens[++$i][1]) && \in_array($tokens[$i][0], array(T_NS_SEPARATOR, T_STRING))) {
while (isset($tokens[++$i][1]) && \in_array($tokens[$i][0], [T_NS_SEPARATOR, T_STRING])) {
$namespace .= $tokens[$i][1];
}
$token = $tokens[$i];
@ -122,7 +125,7 @@ class AnnotationFileLoader extends FileLoader
if (T_DOUBLE_COLON === $tokens[$j][0] || T_NEW === $tokens[$j][0]) {
$skipClassToken = true;
break;
} elseif (!\in_array($tokens[$j][0], array(T_WHITESPACE, T_DOC_COMMENT, T_COMMENT))) {
} elseif (!\in_array($tokens[$j][0], [T_WHITESPACE, T_DOC_COMMENT, T_COMMENT])) {
break;
}
}

View file

@ -12,14 +12,17 @@
namespace Symfony\Component\Routing\Loader\DependencyInjection;
use Psr\Container\ContainerInterface;
use Symfony\Component\Routing\Loader\ContainerLoader;
use Symfony\Component\Routing\Loader\ObjectRouteLoader;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', ServiceRouterLoader::class, ContainerLoader::class), E_USER_DEPRECATED);
/**
* A route loader that executes a service to load the routes.
*
* This depends on the DependencyInjection component.
*
* @author Ryan Weaver <ryan@knpuniversity.com>
*
* @deprecated since Symfony 4.4, use Symfony\Component\Routing\Loader\ContainerLoader instead.
*/
class ServiceRouterLoader extends ObjectRouteLoader
{

View file

@ -11,16 +11,16 @@
namespace Symfony\Component\Routing\Loader;
use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Routing\RouteCollection;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', ObjectRouteLoader::class, ObjectLoader::class), E_USER_DEPRECATED);
/**
* A route loader that calls a method on an object to load the routes.
*
* @author Ryan Weaver <ryan@knpuniversity.com>
*
* @deprecated since Symfony 4.4, use ObjectLoader instead.
*/
abstract class ObjectRouteLoader extends Loader
abstract class ObjectRouteLoader extends ObjectLoader
{
/**
* Returns the object that the method will be called on to load routes.
@ -34,48 +34,6 @@ abstract class ObjectRouteLoader extends Loader
*/
abstract protected function getServiceObject($id);
/**
* Calls the service that will load the routes.
*
* @param mixed $resource Some value that will resolve to a callable
* @param string|null $type The resource type
*
* @return RouteCollection
*/
public function load($resource, $type = null)
{
$parts = explode(':', $resource);
if (2 != \count($parts)) {
throw new \InvalidArgumentException(sprintf('Invalid resource "%s" passed to the "service" route loader: use the format "service_name:methodName"', $resource));
}
$serviceString = $parts[0];
$method = $parts[1];
$loaderObject = $this->getServiceObject($serviceString);
if (!\is_object($loaderObject)) {
throw new \LogicException(sprintf('%s:getServiceObject() must return an object: %s returned', \get_class($this), \gettype($loaderObject)));
}
if (!method_exists($loaderObject, $method)) {
throw new \BadMethodCallException(sprintf('Method "%s" not found on "%s" when importing routing resource "%s"', $method, \get_class($loaderObject), $resource));
}
$routeCollection = \call_user_func(array($loaderObject, $method), $this);
if (!$routeCollection instanceof RouteCollection) {
$type = \is_object($routeCollection) ? \get_class($routeCollection) : \gettype($routeCollection);
throw new \LogicException(sprintf('The %s::%s method must return a RouteCollection: %s returned', \get_class($loaderObject), $method, $type));
}
// make the service file tracked so that if it changes, the cache rebuilds
$this->addClassResource(new \ReflectionClass($loaderObject), $routeCollection);
return $routeCollection;
}
/**
* {@inheritdoc}
*/
@ -84,12 +42,11 @@ abstract class ObjectRouteLoader extends Loader
return 'service' === $type;
}
private function addClassResource(\ReflectionClass $class, RouteCollection $collection)
/**
* {@inheritdoc}
*/
protected function getObject(string $id)
{
do {
if (is_file($class->getFileName())) {
$collection->addResource(new FileResource($class->getFileName()));
}
} while ($class = $class->getParentClass());
return $this->getServiceObject($id);
}
}

View file

@ -40,15 +40,15 @@ class PhpFileLoader extends FileLoader
// the closure forbids access to the private scope in the included file
$loader = $this;
$load = \Closure::bind(function ($file) use ($loader) {
$load = \Closure::bind(static function ($file) use ($loader) {
return include $file;
}, null, ProtectedPhpFileLoader::class);
$result = $load($path);
if ($result instanceof \Closure) {
if (\is_object($result) && \is_callable($result)) {
$collection = new RouteCollection();
$result(new RoutingConfigurator($collection, $this, $path, $file), $this);
$result(new RoutingConfigurator($collection, $this, $path, $file));
} else {
$collection = $result;
}

View file

@ -63,10 +63,9 @@ class XmlFileLoader extends FileLoader
/**
* Parses a node from a loaded XML file.
*
* @param RouteCollection $collection Collection to associate with the node
* @param \DOMElement $node Element to parse
* @param string $path Full path of the XML file being processed
* @param string $file Loaded file name
* @param \DOMElement $node Element to parse
* @param string $path Full path of the XML file being processed
* @param string $file Loaded file name
*
* @throws \InvalidArgumentException When the XML is invalid
*/
@ -99,34 +98,49 @@ class XmlFileLoader extends FileLoader
/**
* Parses a route and adds it to the RouteCollection.
*
* @param RouteCollection $collection RouteCollection instance
* @param \DOMElement $node Element to parse that represents a Route
* @param string $path Full path of the XML file being processed
* @param \DOMElement $node Element to parse that represents a Route
* @param string $path Full path of the XML file being processed
*
* @throws \InvalidArgumentException When the XML is invalid
*/
protected function parseRoute(RouteCollection $collection, \DOMElement $node, $path)
{
if ('' === ($id = $node->getAttribute('id')) || !$node->hasAttribute('path')) {
throw new \InvalidArgumentException(sprintf('The <route> element in file "%s" must have an "id" and a "path" attribute.', $path));
if ('' === $id = $node->getAttribute('id')) {
throw new \InvalidArgumentException(sprintf('The <route> element in file "%s" must have an "id" attribute.', $path));
}
$schemes = preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY);
$methods = preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY);
list($defaults, $requirements, $options, $condition) = $this->parseConfigs($node, $path);
list($defaults, $requirements, $options, $condition, $paths) = $this->parseConfigs($node, $path);
$route = new Route($node->getAttribute('path'), $defaults, $requirements, $options, $node->getAttribute('host'), $schemes, $methods, $condition);
$collection->add($id, $route);
if (!$paths && '' === $node->getAttribute('path')) {
throw new \InvalidArgumentException(sprintf('The <route> element in file "%s" must have a "path" attribute or <path> child nodes.', $path));
}
if ($paths && '' !== $node->getAttribute('path')) {
throw new \InvalidArgumentException(sprintf('The <route> element in file "%s" must not have both a "path" attribute and <path> child nodes.', $path));
}
if (!$paths) {
$route = new Route($node->getAttribute('path'), $defaults, $requirements, $options, $node->getAttribute('host'), $schemes, $methods, $condition);
$collection->add($id, $route);
} else {
foreach ($paths as $locale => $p) {
$defaults['_locale'] = $locale;
$defaults['_canonical_route'] = $id;
$route = new Route($p, $defaults, $requirements, $options, $node->getAttribute('host'), $schemes, $methods, $condition);
$collection->add($id.'.'.$locale, $route);
}
}
}
/**
* Parses an import and adds the routes in the resource to the RouteCollection.
*
* @param RouteCollection $collection RouteCollection instance
* @param \DOMElement $node Element to parse that represents a Route
* @param string $path Full path of the XML file being processed
* @param string $file Loaded file name
* @param \DOMElement $node Element to parse that represents a Route
* @param string $path Full path of the XML file being processed
* @param string $file Loaded file name
*
* @throws \InvalidArgumentException When the XML is invalid
*/
@ -141,20 +155,72 @@ class XmlFileLoader extends FileLoader
$host = $node->hasAttribute('host') ? $node->getAttribute('host') : null;
$schemes = $node->hasAttribute('schemes') ? preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY) : null;
$methods = $node->hasAttribute('methods') ? preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY) : null;
$trailingSlashOnRoot = $node->hasAttribute('trailing-slash-on-root') ? XmlUtils::phpize($node->getAttribute('trailing-slash-on-root')) : true;
list($defaults, $requirements, $options, $condition) = $this->parseConfigs($node, $path);
list($defaults, $requirements, $options, $condition, /* $paths */, $prefixes) = $this->parseConfigs($node, $path);
if ('' !== $prefix && $prefixes) {
throw new \InvalidArgumentException(sprintf('The <route> element in file "%s" must not have both a "prefix" attribute and <prefix> child nodes.', $path));
}
$exclude = [];
foreach ($node->childNodes as $child) {
if ($child instanceof \DOMElement && $child->localName === $exclude && self::NAMESPACE_URI === $child->namespaceURI) {
$exclude[] = $child->nodeValue;
}
}
if ($node->hasAttribute('exclude')) {
if ($exclude) {
throw new \InvalidArgumentException('You cannot use both the attribute "exclude" and <exclude> tags at the same time.');
}
$exclude = [$node->getAttribute('exclude')];
}
$this->setCurrentDir(\dirname($path));
$imported = $this->import($resource, ('' !== $type ? $type : null), false, $file);
/** @var RouteCollection[] $imported */
$imported = $this->import($resource, ('' !== $type ? $type : null), false, $file, $exclude) ?: [];
if (!\is_array($imported)) {
$imported = array($imported);
$imported = [$imported];
}
foreach ($imported as $subCollection) {
/* @var $subCollection RouteCollection */
$subCollection->addPrefix($prefix);
if ('' !== $prefix || !$prefixes) {
$subCollection->addPrefix($prefix);
if (!$trailingSlashOnRoot) {
$rootPath = (new Route(trim(trim($prefix), '/').'/'))->getPath();
foreach ($subCollection->all() as $route) {
if ($route->getPath() === $rootPath) {
$route->setPath(rtrim($rootPath, '/'));
}
}
}
} else {
foreach ($prefixes as $locale => $localePrefix) {
$prefixes[$locale] = trim(trim($localePrefix), '/');
}
foreach ($subCollection->all() as $name => $route) {
if (null === $locale = $route->getDefault('_locale')) {
$subCollection->remove($name);
foreach ($prefixes as $locale => $localePrefix) {
$localizedRoute = clone $route;
$localizedRoute->setPath($localePrefix.(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath()));
$localizedRoute->setDefault('_locale', $locale);
$localizedRoute->setDefault('_canonical_route', $name);
$subCollection->add($name.'.'.$locale, $localizedRoute);
}
} elseif (!isset($prefixes[$locale])) {
throw new \InvalidArgumentException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix when imported in "%s".', $name, $locale, $path));
} else {
$route->setPath($prefixes[$locale].(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath()));
$subCollection->add($name, $route);
}
}
}
if (null !== $host) {
$subCollection->setHost($host);
}
@ -171,6 +237,10 @@ class XmlFileLoader extends FileLoader
$subCollection->addRequirements($requirements);
$subCollection->addOptions($options);
if ($namePrefix = $node->getAttribute('name-prefix')) {
$subCollection->addNamePrefix($namePrefix);
}
$collection->addCollection($subCollection);
}
}
@ -194,26 +264,30 @@ class XmlFileLoader extends FileLoader
/**
* Parses the config elements (default, requirement, option).
*
* @param \DOMElement $node Element to parse that contains the configs
* @param string $path Full path of the XML file being processed
*
* @return array An array with the defaults as first item, requirements as second and options as third
*
* @throws \InvalidArgumentException When the XML is invalid
*/
private function parseConfigs(\DOMElement $node, $path)
private function parseConfigs(\DOMElement $node, string $path): array
{
$defaults = array();
$requirements = array();
$options = array();
$defaults = [];
$requirements = [];
$options = [];
$condition = null;
$prefixes = [];
$paths = [];
/** @var \DOMElement $n */
foreach ($node->getElementsByTagNameNS(self::NAMESPACE_URI, '*') as $n) {
if ($node !== $n->parentNode) {
continue;
}
switch ($n->localName) {
case 'path':
$paths[$n->getAttribute('locale')] = trim($n->textContent);
break;
case 'prefix':
$prefixes[$n->getAttribute('locale')] = trim($n->textContent);
break;
case 'default':
if ($this->isElementValueNull($n)) {
$defaults[$n->getAttribute('key')] = null;
@ -226,7 +300,7 @@ class XmlFileLoader extends FileLoader
$requirements[$n->getAttribute('key')] = trim($n->textContent);
break;
case 'option':
$options[$n->getAttribute('key')] = trim($n->textContent);
$options[$n->getAttribute('key')] = XmlUtils::phpize(trim($n->textContent));
break;
case 'condition':
$condition = trim($n->textContent);
@ -245,22 +319,28 @@ class XmlFileLoader extends FileLoader
$defaults['_controller'] = $controller;
}
if ($node->hasAttribute('locale')) {
$defaults['_locale'] = $node->getAttribute('locale');
}
if ($node->hasAttribute('format')) {
$defaults['_format'] = $node->getAttribute('format');
}
if ($node->hasAttribute('utf8')) {
$options['utf8'] = XmlUtils::phpize($node->getAttribute('utf8'));
}
return array($defaults, $requirements, $options, $condition);
return [$defaults, $requirements, $options, $condition, $paths, $prefixes];
}
/**
* Parses the "default" elements.
*
* @param \DOMElement $element The "default" element to parse
* @param string $path Full path of the XML file being processed
*
* @return array|bool|float|int|string|null The parsed value of the "default" element
*/
private function parseDefaultsConfig(\DOMElement $element, $path)
private function parseDefaultsConfig(\DOMElement $element, string $path)
{
if ($this->isElementValueNull($element)) {
return;
return null;
}
// Check for existing element nodes in the default element. There can
@ -287,17 +367,14 @@ class XmlFileLoader extends FileLoader
/**
* Recursively parses the value of a "default" element.
*
* @param \DOMElement $node The node value
* @param string $path Full path of the XML file being processed
*
* @return array|bool|float|int|string The parsed value
*
* @throws \InvalidArgumentException when the XML is invalid
*/
private function parseDefaultNode(\DOMElement $node, $path)
private function parseDefaultNode(\DOMElement $node, string $path)
{
if ($this->isElementValueNull($node)) {
return;
return null;
}
switch ($node->localName) {
@ -310,7 +387,7 @@ class XmlFileLoader extends FileLoader
case 'string':
return trim($node->nodeValue);
case 'list':
$list = array();
$list = [];
foreach ($node->childNodes as $element) {
if (!$element instanceof \DOMElement) {
@ -326,7 +403,7 @@ class XmlFileLoader extends FileLoader
return $list;
case 'map':
$map = array();
$map = [];
foreach ($node->childNodes as $element) {
if (!$element instanceof \DOMElement) {
@ -346,7 +423,7 @@ class XmlFileLoader extends FileLoader
}
}
private function isElementValueNull(\DOMElement $element)
private function isElementValueNull(\DOMElement $element): bool
{
$namespaceUri = 'http://www.w3.org/2001/XMLSchema-instance';

View file

@ -17,6 +17,7 @@ use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Parser as YamlParser;
use Symfony\Component\Yaml\Yaml;
/**
* YamlFileLoader loads Yaml routing files.
@ -26,9 +27,9 @@ use Symfony\Component\Yaml\Parser as YamlParser;
*/
class YamlFileLoader extends FileLoader
{
private static $availableKeys = array(
'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', 'controller',
);
private static $availableKeys = [
'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', 'controller', 'name_prefix', 'trailing_slash_on_root', 'locale', 'format', 'utf8', 'exclude',
];
private $yamlParser;
/**
@ -57,18 +58,10 @@ class YamlFileLoader extends FileLoader
$this->yamlParser = new YamlParser();
}
$prevErrorHandler = set_error_handler(function ($level, $message, $script, $line) use ($file, &$prevErrorHandler) {
$message = E_USER_DEPRECATED === $level ? preg_replace('/ on line \d+/', ' in "'.$file.'"$0', $message) : $message;
return $prevErrorHandler ? $prevErrorHandler($level, $message, $script, $line) : false;
});
try {
$parsedConfig = $this->yamlParser->parseFile($path);
$parsedConfig = $this->yamlParser->parseFile($path, Yaml::PARSE_CONSTANT);
} catch (ParseException $e) {
throw new \InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML.', $path), 0, $e);
} finally {
restore_error_handler();
}
$collection = new RouteCollection();
@ -102,71 +95,138 @@ class YamlFileLoader extends FileLoader
*/
public function supports($resource, $type = null)
{
return \is_string($resource) && \in_array(pathinfo($resource, PATHINFO_EXTENSION), array('yml', 'yaml'), true) && (!$type || 'yaml' === $type);
return \is_string($resource) && \in_array(pathinfo($resource, PATHINFO_EXTENSION), ['yml', 'yaml'], true) && (!$type || 'yaml' === $type);
}
/**
* Parses a route and adds it to the RouteCollection.
*
* @param RouteCollection $collection A RouteCollection instance
* @param string $name Route name
* @param array $config Route definition
* @param string $path Full path of the YAML file being processed
* @param string $name Route name
* @param array $config Route definition
* @param string $path Full path of the YAML file being processed
*/
protected function parseRoute(RouteCollection $collection, $name, array $config, $path)
{
$defaults = isset($config['defaults']) ? $config['defaults'] : array();
$requirements = isset($config['requirements']) ? $config['requirements'] : array();
$options = isset($config['options']) ? $config['options'] : array();
$defaults = isset($config['defaults']) ? $config['defaults'] : [];
$requirements = isset($config['requirements']) ? $config['requirements'] : [];
$options = isset($config['options']) ? $config['options'] : [];
$host = isset($config['host']) ? $config['host'] : '';
$schemes = isset($config['schemes']) ? $config['schemes'] : array();
$methods = isset($config['methods']) ? $config['methods'] : array();
$schemes = isset($config['schemes']) ? $config['schemes'] : [];
$methods = isset($config['methods']) ? $config['methods'] : [];
$condition = isset($config['condition']) ? $config['condition'] : null;
foreach ($requirements as $placeholder => $requirement) {
if (\is_int($placeholder)) {
@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);
}
}
if (isset($config['controller'])) {
$defaults['_controller'] = $config['controller'];
}
if (isset($config['locale'])) {
$defaults['_locale'] = $config['locale'];
}
if (isset($config['format'])) {
$defaults['_format'] = $config['format'];
}
if (isset($config['utf8'])) {
$options['utf8'] = $config['utf8'];
}
$route = new Route($config['path'], $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
if (\is_array($config['path'])) {
$route = new Route('', $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
$collection->add($name, $route);
foreach ($config['path'] as $locale => $path) {
$localizedRoute = clone $route;
$localizedRoute->setDefault('_locale', $locale);
$localizedRoute->setDefault('_canonical_route', $name);
$localizedRoute->setPath($path);
$collection->add($name.'.'.$locale, $localizedRoute);
}
} else {
$route = new Route($config['path'], $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
$collection->add($name, $route);
}
}
/**
* Parses an import and adds the routes in the resource to the RouteCollection.
*
* @param RouteCollection $collection A RouteCollection instance
* @param array $config Route definition
* @param string $path Full path of the YAML file being processed
* @param string $file Loaded file name
* @param array $config Route definition
* @param string $path Full path of the YAML file being processed
* @param string $file Loaded file name
*/
protected function parseImport(RouteCollection $collection, array $config, $path, $file)
{
$type = isset($config['type']) ? $config['type'] : null;
$prefix = isset($config['prefix']) ? $config['prefix'] : '';
$defaults = isset($config['defaults']) ? $config['defaults'] : array();
$requirements = isset($config['requirements']) ? $config['requirements'] : array();
$options = isset($config['options']) ? $config['options'] : array();
$defaults = isset($config['defaults']) ? $config['defaults'] : [];
$requirements = isset($config['requirements']) ? $config['requirements'] : [];
$options = isset($config['options']) ? $config['options'] : [];
$host = isset($config['host']) ? $config['host'] : null;
$condition = isset($config['condition']) ? $config['condition'] : null;
$schemes = isset($config['schemes']) ? $config['schemes'] : null;
$methods = isset($config['methods']) ? $config['methods'] : null;
$trailingSlashOnRoot = $config['trailing_slash_on_root'] ?? true;
$exclude = $config['exclude'] ?? null;
if (isset($config['controller'])) {
$defaults['_controller'] = $config['controller'];
}
if (isset($config['locale'])) {
$defaults['_locale'] = $config['locale'];
}
if (isset($config['format'])) {
$defaults['_format'] = $config['format'];
}
if (isset($config['utf8'])) {
$options['utf8'] = $config['utf8'];
}
$this->setCurrentDir(\dirname($path));
$imported = $this->import($config['resource'], $type, false, $file);
$imported = $this->import($config['resource'], $type, false, $file, $exclude) ?: [];
if (!\is_array($imported)) {
$imported = array($imported);
$imported = [$imported];
}
foreach ($imported as $subCollection) {
/* @var $subCollection RouteCollection */
$subCollection->addPrefix($prefix);
if (!\is_array($prefix)) {
$subCollection->addPrefix($prefix);
if (!$trailingSlashOnRoot) {
$rootPath = (new Route(trim(trim($prefix), '/').'/'))->getPath();
foreach ($subCollection->all() as $route) {
if ($route->getPath() === $rootPath) {
$route->setPath(rtrim($rootPath, '/'));
}
}
}
} else {
foreach ($prefix as $locale => $localePrefix) {
$prefix[$locale] = trim(trim($localePrefix), '/');
}
foreach ($subCollection->all() as $name => $route) {
if (null === $locale = $route->getDefault('_locale')) {
$subCollection->remove($name);
foreach ($prefix as $locale => $localePrefix) {
$localizedRoute = clone $route;
$localizedRoute->setDefault('_locale', $locale);
$localizedRoute->setDefault('_canonical_route', $name);
$localizedRoute->setPath($localePrefix.(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath()));
$subCollection->add($name.'.'.$locale, $localizedRoute);
}
} elseif (!isset($prefix[$locale])) {
throw new \InvalidArgumentException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix when imported in "%s".', $name, $locale, $file));
} else {
$route->setPath($prefix[$locale].(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath()));
$subCollection->add($name, $route);
}
}
}
if (null !== $host) {
$subCollection->setHost($host);
}
@ -183,6 +243,10 @@ class YamlFileLoader extends FileLoader
$subCollection->addRequirements($requirements);
$subCollection->addOptions($options);
if (isset($config['name_prefix'])) {
$subCollection->addNamePrefix($config['name_prefix']);
}
$collection->addCollection($subCollection);
}
}
@ -203,28 +267,16 @@ class YamlFileLoader extends FileLoader
throw new \InvalidArgumentException(sprintf('The definition of "%s" in "%s" must be a YAML array.', $name, $path));
}
if ($extraKeys = array_diff(array_keys($config), self::$availableKeys)) {
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)
));
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)));
}
if (isset($config['resource']) && isset($config['path'])) {
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
));
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));
}
if (!isset($config['resource']) && isset($config['type'])) {
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
));
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));
}
if (!isset($config['resource']) && !isset($config['path'])) {
throw new \InvalidArgumentException(sprintf(
'You must define a "path" for the route "%s" in file "%s".',
$name, $path
));
throw new \InvalidArgumentException(sprintf('You must define a "path" for the route "%s" in file "%s".', $name, $path));
}
if (isset($config['controller']) && isset($config['defaults']['_controller'])) {
throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "controller" key and the defaults key "_controller" for "%s".', $path, $name));

View file

@ -24,6 +24,14 @@
</xsd:choice>
</xsd:complexType>
<xsd:complexType name="localized-path">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="locale" type="xsd:string" use="required" />
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<xsd:group name="configs">
<xsd:choice>
<xsd:element name="default" nillable="true" type="default" />
@ -34,26 +42,40 @@
</xsd:group>
<xsd:complexType name="route">
<xsd:group ref="configs" minOccurs="0" maxOccurs="unbounded" />
<xsd:sequence>
<xsd:group ref="configs" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="path" type="localized-path" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="id" type="xsd:string" use="required" />
<xsd:attribute name="path" type="xsd:string" use="required" />
<xsd:attribute name="path" type="xsd:string" />
<xsd:attribute name="host" type="xsd:string" />
<xsd:attribute name="schemes" type="xsd:string" />
<xsd:attribute name="methods" type="xsd:string" />
<xsd:attribute name="controller" type="xsd:string" />
<xsd:attribute name="locale" type="xsd:string" />
<xsd:attribute name="format" type="xsd:string" />
<xsd:attribute name="utf8" type="xsd:boolean" />
</xsd:complexType>
<xsd:complexType name="import">
<xsd:group ref="configs" minOccurs="0" maxOccurs="unbounded" />
<xsd:sequence maxOccurs="unbounded" minOccurs="0">
<xsd:group ref="configs" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="prefix" type="localized-path" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="exclude" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="resource" type="xsd:string" use="required" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="exclude" type="xsd:string" />
<xsd:attribute name="prefix" type="xsd:string" />
<xsd:attribute name="name-prefix" type="xsd:string" />
<xsd:attribute name="host" type="xsd:string" />
<xsd:attribute name="schemes" type="xsd:string" />
<xsd:attribute name="methods" type="xsd:string" />
<xsd:attribute name="controller" type="xsd:string" />
<xsd:attribute name="locale" type="xsd:string" />
<xsd:attribute name="format" type="xsd:string" />
<xsd:attribute name="trailing-slash-on-root" type="xsd:boolean" />
<xsd:attribute name="utf8" type="xsd:boolean" />
</xsd:complexType>
<xsd:complexType name="default" mixed="true">