upgraded dependencies

This commit is contained in:
RafficMohammed
2023-01-08 01:59:16 +05:30
parent 51056e3aad
commit f9ae387337
6895 changed files with 133617 additions and 178680 deletions

View File

@@ -18,7 +18,6 @@ use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Routing\Annotation\Route as RouteAnnotation;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RouteCompiler;
/**
* AnnotationClassLoader loads routing information from a PHP class and its methods.
@@ -52,34 +51,50 @@ use Symfony\Component\Routing\RouteCompiler;
* {
* }
* }
*
* On PHP 8, the annotation class can be used as an attribute as well:
* #[Route('/Blog')]
* class Blog
* {
* #[Route('/', name: 'blog_index')]
* public function index()
* {
* }
* #[Route('/{id}', name: 'blog_post', requirements: ["id" => '\d+'])]
* public function show()
* {
* }
* }
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Alexander M. Turek <me@derrabus.de>
*/
abstract class AnnotationClassLoader implements LoaderInterface
{
protected $reader;
protected $env;
/**
* @var string
*/
protected $routeAnnotationClass = 'Symfony\\Component\\Routing\\Annotation\\Route';
protected $routeAnnotationClass = RouteAnnotation::class;
/**
* @var int
*/
protected $defaultRouteIndex = 0;
public function __construct(Reader $reader)
public function __construct(Reader $reader = null, string $env = null)
{
$this->reader = $reader;
$this->env = $env;
}
/**
* Sets the annotation class to read route properties from.
*
* @param string $class A fully-qualified class name
*/
public function setRouteAnnotationClass($class)
public function setRouteAnnotationClass(string $class)
{
$this->routeAnnotationClass = $class;
}
@@ -87,14 +102,13 @@ abstract class AnnotationClassLoader implements LoaderInterface
/**
* Loads from annotations from a class.
*
* @param string $class A class name
* @param string|null $type The resource type
* @param string $class A class name
*
* @return RouteCollection A RouteCollection instance
* @return RouteCollection
*
* @throws \InvalidArgumentException When route can't be parsed
*/
public function load($class, $type = null)
public function load($class, string $type = null)
{
if (!class_exists($class)) {
throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class));
@@ -110,21 +124,21 @@ abstract class AnnotationClassLoader implements LoaderInterface
$collection = new RouteCollection();
$collection->addResource(new FileResource($class->getFileName()));
if ($globals['env'] && $this->env !== $globals['env']) {
return $collection;
}
foreach ($class->getMethods() as $method) {
$this->defaultRouteIndex = 0;
foreach ($this->reader->getMethodAnnotations($method) as $annot) {
if ($annot instanceof $this->routeAnnotationClass) {
$this->addRoute($collection, $annot, $globals, $class, $method);
}
foreach ($this->getAnnotations($method) as $annot) {
$this->addRoute($collection, $annot, $globals, $class, $method);
}
}
if (0 === $collection->count() && $class->hasMethod('__invoke')) {
$globals = $this->resetGlobals();
foreach ($this->reader->getClassAnnotations($class) as $annot) {
if ($annot instanceof $this->routeAnnotationClass) {
$this->addRoute($collection, $annot, $globals, $class, $class->getMethod('__invoke'));
}
foreach ($this->getAnnotations($class) as $annot) {
$this->addRoute($collection, $annot, $globals, $class, $class->getMethod('__invoke'));
}
}
@@ -132,11 +146,14 @@ abstract class AnnotationClassLoader implements LoaderInterface
}
/**
* @param RouteAnnotation $annot or an object that exposes a similar interface
* @param array $globals
* @param RouteAnnotation $annot or an object that exposes a similar interface
*/
protected function addRoute(RouteCollection $collection, $annot, $globals, \ReflectionClass $class, \ReflectionMethod $method)
protected function addRoute(RouteCollection $collection, object $annot, array $globals, \ReflectionClass $class, \ReflectionMethod $method)
{
if ($annot->getEnv() && $annot->getEnv() !== $this->env) {
return;
}
$name = $annot->getName();
if (null === $name) {
$name = $this->getDefaultRouteName($class, $method);
@@ -147,7 +164,7 @@ abstract class AnnotationClassLoader implements LoaderInterface
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);
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::%s()"?', $placeholder, $requirement, $name, $class->getName(), $method->getName()));
}
}
@@ -162,10 +179,8 @@ abstract class AnnotationClassLoader implements LoaderInterface
$host = $globals['host'];
}
$condition = $annot->getCondition();
if (null === $condition) {
$condition = $globals['condition'];
}
$condition = $annot->getCondition() ?? $globals['condition'];
$priority = $annot->getPriority() ?? $globals['priority'];
$path = $annot->getLocalizedPaths() ?: $annot->getPath();
$prefix = $globals['localized_paths'] ?: $globals['path'];
@@ -212,11 +227,11 @@ abstract class AnnotationClassLoader implements LoaderInterface
$this->configureRoute($route, $class, $method, $annot);
if (0 !== $locale) {
$route->setDefault('_locale', $locale);
$route->setRequirement('_locale', preg_quote($locale, RouteCompiler::REGEX_DELIMITER));
$route->setRequirement('_locale', preg_quote($locale));
$route->setDefault('_canonical_route', $name);
$collection->add($name.'.'.$locale, $route);
$collection->add($name.'.'.$locale, $route, $priority);
} else {
$collection->add($name, $route);
$collection->add($name, $route, $priority);
}
}
}
@@ -224,7 +239,7 @@ abstract class AnnotationClassLoader implements LoaderInterface
/**
* {@inheritdoc}
*/
public function supports($resource, $type = null)
public function supports($resource, string $type = null)
{
return \is_string($resource) && preg_match('/^(?:\\\\?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)+$/', $resource) && (!$type || 'annotation' === $type);
}
@@ -264,7 +279,15 @@ abstract class AnnotationClassLoader implements LoaderInterface
{
$globals = $this->resetGlobals();
if ($annot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass)) {
$annot = null;
if (\PHP_VERSION_ID >= 80000 && ($attribute = $class->getAttributes($this->routeAnnotationClass, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null)) {
$annot = $attribute->newInstance();
}
if (!$annot && $this->reader) {
$annot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass);
}
if ($annot) {
if (null !== $annot->getName()) {
$globals['name'] = $annot->getName();
}
@@ -303,9 +326,12 @@ abstract class AnnotationClassLoader implements LoaderInterface
$globals['condition'] = $annot->getCondition();
}
$globals['priority'] = $annot->getPriority() ?? 0;
$globals['env'] = $annot->getEnv();
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);
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" in "%s"?', $placeholder, $requirement, $class->getName()));
}
}
}
@@ -313,7 +339,7 @@ abstract class AnnotationClassLoader implements LoaderInterface
return $globals;
}
private function resetGlobals()
private function resetGlobals(): array
{
return [
'path' => null,
@@ -326,13 +352,43 @@ abstract class AnnotationClassLoader implements LoaderInterface
'host' => '',
'condition' => '',
'name' => '',
'priority' => 0,
'env' => null,
];
}
protected function createRoute($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition)
protected function createRoute(string $path, array $defaults, array $requirements, array $options, ?string $host, array $schemes, array $methods, ?string $condition)
{
return new Route($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
}
abstract protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, $annot);
abstract protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annot);
/**
* @param \ReflectionClass|\ReflectionMethod $reflection
*
* @return iterable<int, RouteAnnotation>
*/
private function getAnnotations(object $reflection): iterable
{
if (\PHP_VERSION_ID >= 80000) {
foreach ($reflection->getAttributes($this->routeAnnotationClass, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
yield $attribute->newInstance();
}
}
if (!$this->reader) {
return;
}
$anntotations = $reflection instanceof \ReflectionClass
? $this->reader->getClassAnnotations($reflection)
: $this->reader->getMethodAnnotations($reflection);
foreach ($anntotations as $annotation) {
if ($annotation instanceof $this->routeAnnotationClass) {
yield $annotation;
}
}
}
}

View File

@@ -28,11 +28,11 @@ class AnnotationDirectoryLoader extends AnnotationFileLoader
* @param string $path A directory path
* @param string|null $type The resource type
*
* @return RouteCollection A RouteCollection instance
* @return RouteCollection
*
* @throws \InvalidArgumentException When the directory does not exist or its routes cannot be parsed
*/
public function load($path, $type = null)
public function load($path, string $type = null)
{
if (!is_dir($dir = $this->locator->locate($path))) {
return parent::supports($path, $type) ? parent::load($path, $type) : new RouteCollection();
@@ -74,7 +74,7 @@ class AnnotationDirectoryLoader extends AnnotationFileLoader
/**
* {@inheritdoc}
*/
public function supports($resource, $type = null)
public function supports($resource, string $type = null)
{
if ('annotation' === $type) {
return true;

View File

@@ -43,11 +43,11 @@ class AnnotationFileLoader extends FileLoader
* @param string $file A PHP file path
* @param string|null $type The resource type
*
* @return RouteCollection|null A RouteCollection instance
* @return RouteCollection|null
*
* @throws \InvalidArgumentException When the file does not exist or its routes cannot be parsed
*/
public function load($file, $type = null)
public function load($file, string $type = null)
{
$path = $this->locator->locate($file);
@@ -70,7 +70,7 @@ class AnnotationFileLoader extends FileLoader
/**
* {@inheritdoc}
*/
public function supports($resource, $type = null)
public function supports($resource, string $type = null)
{
return \is_string($resource) && 'php' === pathinfo($resource, \PATHINFO_EXTENSION) && (!$type || 'annotation' === $type);
}
@@ -78,11 +78,9 @@ class AnnotationFileLoader extends FileLoader
/**
* Returns the full class name for the first class in the file.
*
* @param string $file A PHP file path
*
* @return string|false Full class name if found, false otherwise
* @return string|false
*/
protected function findClass($file)
protected function findClass(string $file)
{
$class = false;
$namespace = false;

View File

@@ -29,17 +29,17 @@ class ClosureLoader extends Loader
* @param \Closure $closure A Closure
* @param string|null $type The resource type
*
* @return RouteCollection A RouteCollection instance
* @return RouteCollection
*/
public function load($closure, $type = null)
public function load($closure, string $type = null)
{
return $closure();
return $closure($this->env);
}
/**
* {@inheritdoc}
*/
public function supports($resource, $type = null)
public function supports($resource, string $type = null)
{
return $resource instanceof \Closure && (!$type || 'closure' === $type);
}

View File

@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Routing\Loader\Configurator;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\Routing\Alias;
class AliasConfigurator
{
private $alias;
public function __construct(Alias $alias)
{
$this->alias = $alias;
}
/**
* Whether this alias is deprecated, that means it should not be called anymore.
*
* @param string $package The name of the composer package that is triggering the deprecation
* @param string $version The version of the package that introduced the deprecation
* @param string $message The deprecation message to use
*
* @return $this
*
* @throws InvalidArgumentException when the message template is invalid
*/
public function deprecate(string $package, string $version, string $message): self
{
$this->alias->setDeprecated($package, $version, $message);
return $this;
}
}

View File

@@ -20,11 +20,13 @@ use Symfony\Component\Routing\RouteCollection;
class CollectionConfigurator
{
use Traits\AddTrait;
use Traits\HostTrait;
use Traits\RouteTrait;
private $parent;
private $parentConfigurator;
private $parentPrefixes;
private $host;
public function __construct(RouteCollection $parent, string $name, self $parentConfigurator = null, array $parentPrefixes = null)
{
@@ -54,6 +56,9 @@ class CollectionConfigurator
if (null === $this->prefixes) {
$this->collection->addPrefix($this->route->getPath());
}
if (null !== $this->host) {
$this->addHost($this->collection, $this->host);
}
$this->parent->addCollection($this->collection);
}
@@ -99,6 +104,20 @@ class CollectionConfigurator
return $this;
}
/**
* Sets the host to use for all child routes.
*
* @param string|array $host the host, or the localized hosts
*
* @return $this
*/
final public function host($host): self
{
$this->host = $host;
return $this;
}
private function createRoute(string $path): Route
{
return (clone $this->route)->setPath($path);

View File

@@ -11,15 +11,15 @@
namespace Symfony\Component\Routing\Loader\Configurator;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RouteCompiler;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ImportConfigurator
{
use Traits\HostTrait;
use Traits\PrefixTrait;
use Traits\RouteTrait;
private $parent;
@@ -57,39 +57,7 @@ class ImportConfigurator
*/
final public function prefix($prefix, bool $trailingSlashOnRoot = true): self
{
if (!\is_array($prefix)) {
$this->route->addPrefix($prefix);
if (!$trailingSlashOnRoot) {
$rootPath = (new Route(trim(trim($prefix), '/').'/'))->getPath();
foreach ($this->route->all() as $route) {
if ($route->getPath() === $rootPath) {
$route->setPath(rtrim($rootPath, '/'));
}
}
}
} else {
foreach ($prefix as $locale => $localePrefix) {
$prefix[$locale] = trim(trim($localePrefix), '/');
}
foreach ($this->route->all() as $name => $route) {
if (null === $locale = $route->getDefault('_locale')) {
$this->route->remove($name);
foreach ($prefix as $locale => $localePrefix) {
$localizedRoute = clone $route;
$localizedRoute->setDefault('_locale', $locale);
$localizedRoute->setRequirement('_locale', preg_quote($locale, RouteCompiler::REGEX_DELIMITER));
$localizedRoute->setDefault('_canonical_route', $name);
$localizedRoute->setPath($localePrefix.(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath()));
$this->route->add($name.'.'.$locale, $localizedRoute);
}
} elseif (!isset($prefix[$locale])) {
throw new \InvalidArgumentException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix in its parent collection.', $name, $locale));
} else {
$route->setPath($prefix[$locale].(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath()));
$this->route->add($name, $route);
}
}
}
$this->addPrefix($this->route, $prefix, $trailingSlashOnRoot);
return $this;
}
@@ -105,4 +73,18 @@ class ImportConfigurator
return $this;
}
/**
* Sets the host to use for all child routes.
*
* @param string|array $host the host, or the localized hosts
*
* @return $this
*/
final public function host($host): self
{
$this->addHost($this->route, $host);
return $this;
}
}

View File

@@ -19,11 +19,12 @@ use Symfony\Component\Routing\RouteCollection;
class RouteConfigurator
{
use Traits\AddTrait;
use Traits\HostTrait;
use Traits\RouteTrait;
private $parentConfigurator;
protected $parentConfigurator;
public function __construct(RouteCollection $collection, $route, string $name = '', CollectionConfigurator $parentConfigurator = null, array $prefixes = null)
public function __construct(RouteCollection $collection, RouteCollection $route, string $name = '', CollectionConfigurator $parentConfigurator = null, array $prefixes = null)
{
$this->collection = $collection;
$this->route = $route;
@@ -31,4 +32,18 @@ class RouteConfigurator
$this->parentConfigurator = $parentConfigurator; // for GC control
$this->prefixes = $prefixes;
}
/**
* Sets the host to use for all child routes.
*
* @param string|array $host the host, or the localized hosts
*
* @return $this
*/
final public function host($host): self
{
$this->addHost($this->route, $host);
return $this;
}
}

View File

@@ -24,13 +24,15 @@ class RoutingConfigurator
private $loader;
private $path;
private $file;
private $env;
public function __construct(RouteCollection $collection, PhpFileLoader $loader, string $path, string $file)
public function __construct(RouteCollection $collection, PhpFileLoader $loader, string $path, string $file, string $env = null)
{
$this->collection = $collection;
$this->loader = $loader;
$this->path = $path;
$this->file = $file;
$this->env = $env;
}
/**
@@ -57,4 +59,23 @@ class RoutingConfigurator
{
return new CollectionConfigurator($this->collection, $name);
}
/**
* Get the current environment to be able to write conditional configuration.
*/
final public function env(): ?string
{
return $this->env;
}
/**
* @return static
*/
final public function withPath(string $path): self
{
$clone = clone $this;
$clone->path = $clone->file = $path;
return $clone;
}
}

View File

@@ -11,68 +11,41 @@
namespace Symfony\Component\Routing\Loader\Configurator\Traits;
use Symfony\Component\Routing\Loader\Configurator\AliasConfigurator;
use Symfony\Component\Routing\Loader\Configurator\CollectionConfigurator;
use Symfony\Component\Routing\Loader\Configurator\RouteConfigurator;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RouteCompiler;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
trait AddTrait
{
use LocalizedRouteTrait;
/**
* @var RouteCollection
*/
private $collection;
private $name = '';
private $prefixes;
protected $collection;
protected $name = '';
protected $prefixes;
/**
* Adds a route.
*
* @param string|array $path the path, or the localized paths of the route
*/
final public function add(string $name, $path): RouteConfigurator
public function add(string $name, $path): RouteConfigurator
{
$paths = [];
$parentConfigurator = $this instanceof CollectionConfigurator ? $this : ($this instanceof RouteConfigurator ? $this->parentConfigurator : null);
$route = $this->createLocalizedRoute($this->collection, $name, $path, $this->name, $this->prefixes);
if (\is_array($path)) {
if (null === $this->prefixes) {
$paths = $path;
} elseif ($missing = array_diff_key($this->prefixes, $path)) {
throw new \LogicException(sprintf('Route "%s" is missing routes for locale(s) "%s".', $name, implode('", "', array_keys($missing))));
} else {
foreach ($path as $locale => $localePath) {
if (!isset($this->prefixes[$locale])) {
throw new \LogicException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix in its parent collection.', $name, $locale));
}
return new RouteConfigurator($this->collection, $route, $this->name, $parentConfigurator, $this->prefixes);
}
$paths[$locale] = $this->prefixes[$locale].$localePath;
}
}
} elseif (null !== $this->prefixes) {
foreach ($this->prefixes as $locale => $prefix) {
$paths[$locale] = $prefix.$path;
}
} else {
$this->collection->add($this->name.$name, $route = $this->createRoute($path));
return new RouteConfigurator($this->collection, $route, $this->name, $parentConfigurator, $this->prefixes);
}
$routes = new RouteCollection();
foreach ($paths as $locale => $path) {
$routes->add($name.'.'.$locale, $route = $this->createRoute($path));
$this->collection->add($this->name.$name.'.'.$locale, $route);
$route->setDefault('_locale', $locale);
$route->setRequirement('_locale', preg_quote($locale, RouteCompiler::REGEX_DELIMITER));
$route->setDefault('_canonical_route', $this->name.$name);
}
return new RouteConfigurator($this->collection, $routes, $this->name, $parentConfigurator, $this->prefixes);
public function alias(string $name, string $alias): AliasConfigurator
{
return new AliasConfigurator($this->collection->addAlias($name, $alias));
}
/**
@@ -80,13 +53,8 @@ trait AddTrait
*
* @param string|array $path the path, or the localized paths of the route
*/
final public function __invoke(string $name, $path): RouteConfigurator
public function __invoke(string $name, $path): RouteConfigurator
{
return $this->add($name, $path);
}
private function createRoute(string $path): Route
{
return new Route($path);
}
}

View File

@@ -0,0 +1,49 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Routing\Loader\Configurator\Traits;
use Symfony\Component\Routing\RouteCollection;
/**
* @internal
*/
trait HostTrait
{
final protected function addHost(RouteCollection $routes, $hosts)
{
if (!$hosts || !\is_array($hosts)) {
$routes->setHost($hosts ?: '');
return;
}
foreach ($routes->all() as $name => $route) {
if (null === $locale = $route->getDefault('_locale')) {
$routes->remove($name);
foreach ($hosts as $locale => $host) {
$localizedRoute = clone $route;
$localizedRoute->setDefault('_locale', $locale);
$localizedRoute->setRequirement('_locale', preg_quote($locale));
$localizedRoute->setDefault('_canonical_route', $name);
$localizedRoute->setHost($host);
$routes->add($name.'.'.$locale, $localizedRoute);
}
} elseif (!isset($hosts[$locale])) {
throw new \InvalidArgumentException(sprintf('Route "%s" with locale "%s" is missing a corresponding host in its parent collection.', $name, $locale));
} else {
$route->setHost($hosts[$locale]);
$route->setRequirement('_locale', preg_quote($locale));
$routes->add($name, $route);
}
}
}
}

View File

@@ -0,0 +1,76 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Routing\Loader\Configurator\Traits;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* @internal
*
* @author Nicolas Grekas <p@tchwork.com>
* @author Jules Pietri <jules@heahprod.com>
*/
trait LocalizedRouteTrait
{
/**
* Creates one or many routes.
*
* @param string|array $path the path, or the localized paths of the route
*/
final protected function createLocalizedRoute(RouteCollection $collection, string $name, $path, string $namePrefix = '', array $prefixes = null): RouteCollection
{
$paths = [];
$routes = new RouteCollection();
if (\is_array($path)) {
if (null === $prefixes) {
$paths = $path;
} elseif ($missing = array_diff_key($prefixes, $path)) {
throw new \LogicException(sprintf('Route "%s" is missing routes for locale(s) "%s".', $name, implode('", "', array_keys($missing))));
} else {
foreach ($path as $locale => $localePath) {
if (!isset($prefixes[$locale])) {
throw new \LogicException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix in its parent collection.', $name, $locale));
}
$paths[$locale] = $prefixes[$locale].$localePath;
}
}
} elseif (null !== $prefixes) {
foreach ($prefixes as $locale => $prefix) {
$paths[$locale] = $prefix.$path;
}
} else {
$routes->add($namePrefix.$name, $route = $this->createRoute($path));
$collection->add($namePrefix.$name, $route);
return $routes;
}
foreach ($paths as $locale => $path) {
$routes->add($name.'.'.$locale, $route = $this->createRoute($path));
$collection->add($namePrefix.$name.'.'.$locale, $route);
$route->setDefault('_locale', $locale);
$route->setRequirement('_locale', preg_quote($locale));
$route->setDefault('_canonical_route', $namePrefix.$name);
}
return $routes;
}
private function createRoute(string $path): Route
{
return new Route($path);
}
}

View File

@@ -0,0 +1,62 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Routing\Loader\Configurator\Traits;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* @internal
*
* @author Nicolas Grekas <p@tchwork.com>
*/
trait PrefixTrait
{
final protected function addPrefix(RouteCollection $routes, $prefix, bool $trailingSlashOnRoot)
{
if (\is_array($prefix)) {
foreach ($prefix as $locale => $localePrefix) {
$prefix[$locale] = trim(trim($localePrefix), '/');
}
foreach ($routes->all() as $name => $route) {
if (null === $locale = $route->getDefault('_locale')) {
$routes->remove($name);
foreach ($prefix as $locale => $localePrefix) {
$localizedRoute = clone $route;
$localizedRoute->setDefault('_locale', $locale);
$localizedRoute->setRequirement('_locale', preg_quote($locale));
$localizedRoute->setDefault('_canonical_route', $name);
$localizedRoute->setPath($localePrefix.(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath()));
$routes->add($name.'.'.$locale, $localizedRoute);
}
} elseif (!isset($prefix[$locale])) {
throw new \InvalidArgumentException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix in its parent collection.', $name, $locale));
} else {
$route->setPath($prefix[$locale].(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath()));
$routes->add($name, $route);
}
}
return;
}
$routes->addPrefix($prefix);
if (!$trailingSlashOnRoot) {
$rootPath = (new Route(trim(trim($prefix), '/').'/'))->getPath();
foreach ($routes->all() as $route) {
if ($route->getPath() === $rootPath) {
$route->setPath(rtrim($rootPath, '/'));
}
}
}
}
}

View File

@@ -19,7 +19,7 @@ trait RouteTrait
/**
* @var RouteCollection|Route
*/
private $route;
protected $route;
/**
* Adds defaults.
@@ -160,4 +160,16 @@ trait RouteTrait
return $this;
}
/**
* Adds the "_stateless" entry to defaults.
*
* @return $this
*/
final public function stateless(bool $stateless = true): self
{
$this->route->addDefaults(['_stateless' => $stateless]);
return $this;
}
}

View File

@@ -22,15 +22,16 @@ class ContainerLoader extends ObjectLoader
{
private $container;
public function __construct(ContainerInterface $container)
public function __construct(ContainerInterface $container, string $env = null)
{
$this->container = $container;
parent::__construct($env);
}
/**
* {@inheritdoc}
*/
public function supports($resource, $type = null)
public function supports($resource, string $type = null)
{
return 'service' === $type && \is_string($resource);
}

View File

@@ -1,43 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
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.
*
* @author Ryan Weaver <ryan@knpuniversity.com>
*
* @deprecated since Symfony 4.4, use Symfony\Component\Routing\Loader\ContainerLoader instead.
*/
class ServiceRouterLoader extends ObjectRouteLoader
{
/**
* @var ContainerInterface
*/
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
protected function getServiceObject($id)
{
return $this->container->get($id);
}
}

View File

@@ -20,7 +20,7 @@ class DirectoryLoader extends FileLoader
/**
* {@inheritdoc}
*/
public function load($file, $type = null)
public function load($file, string $type = null)
{
$path = $this->locator->locate($file);
@@ -49,7 +49,7 @@ class DirectoryLoader extends FileLoader
/**
* {@inheritdoc}
*/
public function supports($resource, $type = null)
public function supports($resource, string $type = null)
{
// only when type is forced to directory, not to conflict with AnnotationLoader

View File

@@ -24,7 +24,7 @@ class GlobFileLoader extends FileLoader
/**
* {@inheritdoc}
*/
public function load($resource, $type = null)
public function load($resource, string $type = null)
{
$collection = new RouteCollection();
@@ -40,7 +40,7 @@ class GlobFileLoader extends FileLoader
/**
* {@inheritdoc}
*/
public function supports($resource, $type = null)
public function supports($resource, string $type = null)
{
return 'glob' === $type;
}

View File

@@ -40,36 +40,31 @@ abstract class ObjectLoader extends Loader
*
* @return RouteCollection
*/
public function load($resource, $type = null)
public function load($resource, string $type = null)
{
if (!preg_match('/^[^\:]+(?:::?(?:[^\:]+))?$/', $resource)) {
if (!preg_match('/^[^\:]+(?:::(?:[^\:]+))?$/', $resource)) {
throw new \InvalidArgumentException(sprintf('Invalid resource "%s" passed to the %s route loader: use the format "object_id::method" or "object_id" if your object class has an "__invoke" method.', $resource, \is_string($type) ? '"'.$type.'"' : 'object'));
}
if (1 === substr_count($resource, ':')) {
$resource = str_replace(':', '::', $resource);
@trigger_error(sprintf('Referencing object route loaders with a single colon is deprecated since Symfony 4.1. Use %s instead.', $resource), \E_USER_DEPRECATED);
}
$parts = explode('::', $resource);
$method = $parts[1] ?? '__invoke';
$loaderObject = $this->getObject($parts[0]);
if (!\is_object($loaderObject)) {
throw new \TypeError(sprintf('"%s:getObject()" must return an object: "%s" returned.', static::class, \gettype($loaderObject)));
throw new \TypeError(sprintf('"%s:getObject()" must return an object: "%s" returned.', static::class, get_debug_type($loaderObject)));
}
if (!\is_callable([$loaderObject, $method])) {
throw new \BadMethodCallException(sprintf('Method "%s" not found on "%s" when importing routing resource "%s".', $method, \get_class($loaderObject), $resource));
throw new \BadMethodCallException(sprintf('Method "%s" not found on "%s" when importing routing resource "%s".', $method, get_debug_type($loaderObject), $resource));
}
$routeCollection = $loaderObject->$method($this);
$routeCollection = $loaderObject->$method($this, $this->env);
if (!$routeCollection instanceof RouteCollection) {
$type = \is_object($routeCollection) ? \get_class($routeCollection) : \gettype($routeCollection);
$type = get_debug_type($routeCollection);
throw new \LogicException(sprintf('The "%s::%s()" method must return a RouteCollection: "%s" returned.', \get_class($loaderObject), $method, $type));
throw new \LogicException(sprintf('The "%s::%s()" method must return a RouteCollection: "%s" returned.', get_debug_type($loaderObject), $method, $type));
}
// make the object file tracked so that if it changes, the cache rebuilds

View File

@@ -1,52 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Routing\Loader;
@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 ObjectLoader
{
/**
* Returns the object that the method will be called on to load routes.
*
* For example, if your application uses a service container,
* the $id may be a service id.
*
* @param string $id
*
* @return object
*/
abstract protected function getServiceObject($id);
/**
* {@inheritdoc}
*/
public function supports($resource, $type = null)
{
return 'service' === $type;
}
/**
* {@inheritdoc}
*/
protected function getObject(string $id)
{
return $this->getServiceObject($id);
}
}

View File

@@ -22,6 +22,8 @@ use Symfony\Component\Routing\RouteCollection;
* The file must return a RouteCollection instance.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Nicolas grekas <p@tchwork.com>
* @author Jules Pietri <jules@heahprod.com>
*/
class PhpFileLoader extends FileLoader
{
@@ -31,9 +33,9 @@ class PhpFileLoader extends FileLoader
* @param string $file A PHP file path
* @param string|null $type The resource type
*
* @return RouteCollection A RouteCollection instance
* @return RouteCollection
*/
public function load($file, $type = null)
public function load($file, string $type = null)
{
$path = $this->locator->locate($file);
$this->setCurrentDir(\dirname($path));
@@ -47,8 +49,7 @@ class PhpFileLoader extends FileLoader
$result = $load($path);
if (\is_object($result) && \is_callable($result)) {
$collection = new RouteCollection();
$result(new RoutingConfigurator($collection, $this, $path, $file));
$collection = $this->callConfigurator($result, $path, $file);
} else {
$collection = $result;
}
@@ -61,10 +62,19 @@ class PhpFileLoader extends FileLoader
/**
* {@inheritdoc}
*/
public function supports($resource, $type = null)
public function supports($resource, string $type = null)
{
return \is_string($resource) && 'php' === pathinfo($resource, \PATHINFO_EXTENSION) && (!$type || 'php' === $type);
}
protected function callConfigurator(callable $result, string $path, string $file): RouteCollection
{
$collection = new RouteCollection();
$result(new RoutingConfigurator($collection, $this, $path, $file, $this->env));
return $collection;
}
}
/**

View File

@@ -14,9 +14,10 @@ namespace Symfony\Component\Routing\Loader;
use Symfony\Component\Config\Loader\FileLoader;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Config\Util\XmlUtils;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\Loader\Configurator\Traits\HostTrait;
use Symfony\Component\Routing\Loader\Configurator\Traits\LocalizedRouteTrait;
use Symfony\Component\Routing\Loader\Configurator\Traits\PrefixTrait;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RouteCompiler;
/**
* XmlFileLoader loads XML routing files.
@@ -26,6 +27,10 @@ use Symfony\Component\Routing\RouteCompiler;
*/
class XmlFileLoader extends FileLoader
{
use HostTrait;
use LocalizedRouteTrait;
use PrefixTrait;
public const NAMESPACE_URI = 'http://symfony.com/schema/routing';
public const SCHEME_PATH = '/schema/routing/routing-1.0.xsd';
@@ -35,12 +40,12 @@ class XmlFileLoader extends FileLoader
* @param string $file An XML file path
* @param string|null $type The resource type
*
* @return RouteCollection A RouteCollection instance
* @return RouteCollection
*
* @throws \InvalidArgumentException when the file cannot be loaded or when the XML cannot be
* parsed because it does not validate against the scheme
*/
public function load($file, $type = null)
public function load($file, string $type = null)
{
$path = $this->locator->locate($file);
@@ -64,13 +69,9 @@ class XmlFileLoader extends FileLoader
/**
* Parses a node from a loaded XML file.
*
* @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
*/
protected function parseNode(RouteCollection $collection, \DOMElement $node, $path, $file)
protected function parseNode(RouteCollection $collection, \DOMElement $node, string $path, string $file)
{
if (self::NAMESPACE_URI !== $node->namespaceURI) {
return;
@@ -83,6 +84,16 @@ class XmlFileLoader extends FileLoader
case 'import':
$this->parseImport($collection, $node, $path, $file);
break;
case 'when':
if (!$this->env || $node->getAttribute('env') !== $this->env) {
break;
}
foreach ($node->childNodes as $node) {
if ($node instanceof \DOMElement) {
$this->parseNode($collection, $node, $path, $file);
}
}
break;
default:
throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "route" or "import".', $node->localName, $path));
}
@@ -91,7 +102,7 @@ class XmlFileLoader extends FileLoader
/**
* {@inheritdoc}
*/
public function supports($resource, $type = null)
public function supports($resource, string $type = null)
{
return \is_string($resource) && 'xml' === pathinfo($resource, \PATHINFO_EXTENSION) && (!$type || 'xml' === $type);
}
@@ -99,21 +110,28 @@ class XmlFileLoader extends FileLoader
/**
* Parses a route and adds it to the RouteCollection.
*
* @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)
protected function parseRoute(RouteCollection $collection, \DOMElement $node, string $path)
{
if ('' === $id = $node->getAttribute('id')) {
throw new \InvalidArgumentException(sprintf('The <route> element in file "%s" must have an "id" attribute.', $path));
}
if ('' !== $alias = $node->getAttribute('alias')) {
$alias = $collection->addAlias($id, $alias);
if ($deprecationInfo = $this->parseDeprecation($node, $path)) {
$alias->setDeprecated($deprecationInfo['package'], $deprecationInfo['version'], $deprecationInfo['message']);
}
return;
}
$schemes = preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, \PREG_SPLIT_NO_EMPTY);
$methods = preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, \PREG_SPLIT_NO_EMPTY);
[$defaults, $requirements, $options, $condition, $paths] = $this->parseConfigs($node, $path);
[$defaults, $requirements, $options, $condition, $paths, /* $prefixes */, $hosts] = $this->parseConfigs($node, $path);
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));
@@ -123,30 +141,25 @@ class XmlFileLoader extends FileLoader
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;
$requirements['_locale'] = preg_quote($locale, RouteCompiler::REGEX_DELIMITER);
$route = new Route($p, $defaults, $requirements, $options, $node->getAttribute('host'), $schemes, $methods, $condition);
$collection->add($id.'.'.$locale, $route);
}
$routes = $this->createLocalizedRoute($collection, $id, $paths ?: $node->getAttribute('path'));
$routes->addDefaults($defaults);
$routes->addRequirements($requirements);
$routes->addOptions($options);
$routes->setSchemes($schemes);
$routes->setMethods($methods);
$routes->setCondition($condition);
if (null !== $hosts) {
$this->addHost($routes, $hosts);
}
}
/**
* Parses an import and adds the routes in the resource to the RouteCollection.
*
* @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
*/
protected function parseImport(RouteCollection $collection, \DOMElement $node, $path, $file)
protected function parseImport(RouteCollection $collection, \DOMElement $node, string $path, string $file)
{
if ('' === $resource = $node->getAttribute('resource')) {
throw new \InvalidArgumentException(sprintf('The <import> element in file "%s" must have a "resource" attribute.', $path));
@@ -154,12 +167,12 @@ class XmlFileLoader extends FileLoader
$type = $node->getAttribute('type');
$prefix = $node->getAttribute('prefix');
$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;
$namePrefix = $node->getAttribute('name-prefix') ?: null;
[$defaults, $requirements, $options, $condition, /* $paths */, $prefixes] = $this->parseConfigs($node, $path);
[$defaults, $requirements, $options, $condition, /* $paths */, $prefixes, $hosts] = $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));
@@ -189,44 +202,12 @@ class XmlFileLoader extends FileLoader
}
foreach ($imported as $subCollection) {
/* @var $subCollection RouteCollection */
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->setRequirement('_locale', preg_quote($locale, RouteCompiler::REGEX_DELIMITER));
$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);
}
}
$this->addPrefix($subCollection, $prefixes ?: $prefix, $trailingSlashOnRoot);
if (null !== $hosts) {
$this->addHost($subCollection, $hosts);
}
if (null !== $host) {
$subCollection->setHost($host);
}
if (null !== $condition) {
$subCollection->setCondition($condition);
}
@@ -236,30 +217,25 @@ class XmlFileLoader extends FileLoader
if (null !== $methods) {
$subCollection->setMethods($methods);
}
if (null !== $namePrefix) {
$subCollection->addNamePrefix($namePrefix);
}
$subCollection->addDefaults($defaults);
$subCollection->addRequirements($requirements);
$subCollection->addOptions($options);
if ($namePrefix = $node->getAttribute('name-prefix')) {
$subCollection->addNamePrefix($namePrefix);
}
$collection->addCollection($subCollection);
}
}
/**
* Loads an XML file.
*
* @param string $file An XML file path
*
* @return \DOMDocument
*
* @throws \InvalidArgumentException When loading of XML file fails because of syntax errors
* or when the XML structure is not as expected by the scheme -
* see validate()
*/
protected function loadFile($file)
protected function loadFile(string $file)
{
return XmlUtils::loadFile($file, __DIR__.static::SCHEME_PATH);
}
@@ -277,6 +253,7 @@ class XmlFileLoader extends FileLoader
$condition = null;
$prefixes = [];
$paths = [];
$hosts = [];
/** @var \DOMElement $n */
foreach ($node->getElementsByTagNameNS(self::NAMESPACE_URI, '*') as $n) {
@@ -288,6 +265,9 @@ class XmlFileLoader extends FileLoader
case 'path':
$paths[$n->getAttribute('locale')] = trim($n->textContent);
break;
case 'host':
$hosts[$n->getAttribute('locale')] = trim($n->textContent);
break;
case 'prefix':
$prefixes[$n->getAttribute('locale')] = trim($n->textContent);
break;
@@ -331,14 +311,27 @@ class XmlFileLoader extends FileLoader
if ($node->hasAttribute('utf8')) {
$options['utf8'] = XmlUtils::phpize($node->getAttribute('utf8'));
}
if ($stateless = $node->getAttribute('stateless')) {
if (isset($defaults['_stateless'])) {
$name = $node->hasAttribute('id') ? sprintf('"%s".', $node->getAttribute('id')) : sprintf('the "%s" tag.', $node->tagName);
return [$defaults, $requirements, $options, $condition, $paths, $prefixes];
throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "stateless" attribute and the defaults key "_stateless" for ', $path).$name);
}
$defaults['_stateless'] = XmlUtils::phpize($stateless);
}
if (!$hosts) {
$hosts = $node->hasAttribute('host') ? $node->getAttribute('host') : null;
}
return [$defaults, $requirements, $options, $condition, $paths, $prefixes, $hosts];
}
/**
* Parses the "default" elements.
*
* @return array|bool|float|int|string|null The parsed value of the "default" element
* @return array|bool|float|int|string|null
*/
private function parseDefaultsConfig(\DOMElement $element, string $path)
{
@@ -370,7 +363,7 @@ class XmlFileLoader extends FileLoader
/**
* Recursively parses the value of a "default" element.
*
* @return array|bool|float|int|string|null The parsed value
* @return array|bool|float|int|string|null
*
* @throws \InvalidArgumentException when the XML is invalid
*/
@@ -436,4 +429,41 @@ class XmlFileLoader extends FileLoader
return 'true' === $element->getAttributeNS($namespaceUri, 'nil') || '1' === $element->getAttributeNS($namespaceUri, 'nil');
}
/**
* Parses the deprecation elements.
*
* @throws \InvalidArgumentException When the XML is invalid
*/
private function parseDeprecation(\DOMElement $node, string $path): array
{
$deprecatedNode = null;
foreach ($node->childNodes as $child) {
if (!$child instanceof \DOMElement || self::NAMESPACE_URI !== $child->namespaceURI) {
continue;
}
if ('deprecated' !== $child->localName) {
throw new \InvalidArgumentException(sprintf('Invalid child element "%s" defined for alias "%s" in "%s".', $child->localName, $node->getAttribute('id'), $path));
}
$deprecatedNode = $child;
}
if (null === $deprecatedNode) {
return [];
}
if (!$deprecatedNode->hasAttribute('package')) {
throw new \InvalidArgumentException(sprintf('The <deprecated> element in file "%s" must have a "package" attribute.', $path));
}
if (!$deprecatedNode->hasAttribute('version')) {
throw new \InvalidArgumentException(sprintf('The <deprecated> element in file "%s" must have a "version" attribute.', $path));
}
return [
'package' => $deprecatedNode->getAttribute('package'),
'version' => $deprecatedNode->getAttribute('version'),
'message' => trim($deprecatedNode->nodeValue),
];
}
}

View File

@@ -13,9 +13,10 @@ namespace Symfony\Component\Routing\Loader;
use Symfony\Component\Config\Loader\FileLoader;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\Loader\Configurator\Traits\HostTrait;
use Symfony\Component\Routing\Loader\Configurator\Traits\LocalizedRouteTrait;
use Symfony\Component\Routing\Loader\Configurator\Traits\PrefixTrait;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RouteCompiler;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Parser as YamlParser;
use Symfony\Component\Yaml\Yaml;
@@ -28,8 +29,12 @@ use Symfony\Component\Yaml\Yaml;
*/
class YamlFileLoader extends FileLoader
{
use HostTrait;
use LocalizedRouteTrait;
use PrefixTrait;
private const AVAILABLE_KEYS = [
'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', 'controller', 'name_prefix', 'trailing_slash_on_root', 'locale', 'format', 'utf8', 'exclude',
'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', 'controller', 'name_prefix', 'trailing_slash_on_root', 'locale', 'format', 'utf8', 'exclude', 'stateless',
];
private $yamlParser;
@@ -39,11 +44,11 @@ class YamlFileLoader extends FileLoader
* @param string $file A Yaml file path
* @param string|null $type The resource type
*
* @return RouteCollection A RouteCollection instance
* @return RouteCollection
*
* @throws \InvalidArgumentException When a route can't be parsed because YAML is invalid
*/
public function load($file, $type = null)
public function load($file, string $type = null)
{
$path = $this->locator->locate($file);
@@ -79,6 +84,24 @@ class YamlFileLoader extends FileLoader
}
foreach ($parsedConfig as $name => $config) {
if (0 === strpos($name, 'when@')) {
if (!$this->env || 'when@'.$this->env !== $name) {
continue;
}
foreach ($config as $name => $config) {
$this->validate($config, $name.'" when "@'.$this->env, $path);
if (isset($config['resource'])) {
$this->parseImport($collection, $config, $path, $file);
} else {
$this->parseRoute($collection, $name, $config, $path);
}
}
continue;
}
$this->validate($config, $name, $path);
if (isset($config['resource'])) {
@@ -94,31 +117,37 @@ class YamlFileLoader extends FileLoader
/**
* {@inheritdoc}
*/
public function supports($resource, $type = null)
public function supports($resource, string $type = null)
{
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 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)
protected function parseRoute(RouteCollection $collection, string $name, array $config, string $path)
{
if (isset($config['alias'])) {
$alias = $collection->addAlias($name, $config['alias']);
$deprecation = $config['deprecated'] ?? null;
if (null !== $deprecation) {
$alias->setDeprecated(
$deprecation['package'],
$deprecation['version'],
$deprecation['message'] ?? ''
);
}
return;
}
$defaults = $config['defaults'] ?? [];
$requirements = $config['requirements'] ?? [];
$options = $config['options'] ?? [];
$host = $config['host'] ?? '';
$schemes = $config['schemes'] ?? [];
$methods = $config['methods'] ?? [];
$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);
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));
}
}
@@ -134,32 +163,27 @@ class YamlFileLoader extends FileLoader
if (isset($config['utf8'])) {
$options['utf8'] = $config['utf8'];
}
if (isset($config['stateless'])) {
$defaults['_stateless'] = $config['stateless'];
}
if (\is_array($config['path'])) {
$route = new Route('', $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
$routes = $this->createLocalizedRoute($collection, $name, $config['path']);
$routes->addDefaults($defaults);
$routes->addRequirements($requirements);
$routes->addOptions($options);
$routes->setSchemes($config['schemes'] ?? []);
$routes->setMethods($config['methods'] ?? []);
$routes->setCondition($config['condition'] ?? null);
foreach ($config['path'] as $locale => $path) {
$localizedRoute = clone $route;
$localizedRoute->setDefault('_locale', $locale);
$localizedRoute->setRequirement('_locale', preg_quote($locale, RouteCompiler::REGEX_DELIMITER));
$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);
if (isset($config['host'])) {
$this->addHost($routes, $config['host']);
}
}
/**
* Parses an import and adds the routes in the resource to the RouteCollection.
*
* @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)
protected function parseImport(RouteCollection $collection, array $config, string $path, string $file)
{
$type = $config['type'] ?? null;
$prefix = $config['prefix'] ?? '';
@@ -171,6 +195,7 @@ class YamlFileLoader extends FileLoader
$schemes = $config['schemes'] ?? null;
$methods = $config['methods'] ?? null;
$trailingSlashOnRoot = $config['trailing_slash_on_root'] ?? true;
$namePrefix = $config['name_prefix'] ?? null;
$exclude = $config['exclude'] ?? null;
if (isset($config['controller'])) {
@@ -185,9 +210,13 @@ class YamlFileLoader extends FileLoader
if (isset($config['utf8'])) {
$options['utf8'] = $config['utf8'];
}
if (isset($config['stateless'])) {
$defaults['_stateless'] = $config['stateless'];
}
$this->setCurrentDir(\dirname($path));
/** @var RouteCollection[] $imported */
$imported = $this->import($config['resource'], $type, false, $file, $exclude) ?: [];
if (!\is_array($imported)) {
@@ -195,43 +224,10 @@ class YamlFileLoader extends FileLoader
}
foreach ($imported as $subCollection) {
/* @var $subCollection RouteCollection */
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->setRequirement('_locale', preg_quote($locale, RouteCompiler::REGEX_DELIMITER));
$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);
}
}
}
$this->addPrefix($subCollection, $prefix, $trailingSlashOnRoot);
if (null !== $host) {
$subCollection->setHost($host);
$this->addHost($subCollection, $host);
}
if (null !== $condition) {
$subCollection->setCondition($condition);
@@ -242,14 +238,13 @@ class YamlFileLoader extends FileLoader
if (null !== $methods) {
$subCollection->setMethods($methods);
}
if (null !== $namePrefix) {
$subCollection->addNamePrefix($namePrefix);
}
$subCollection->addDefaults($defaults);
$subCollection->addRequirements($requirements);
$subCollection->addOptions($options);
if (isset($config['name_prefix'])) {
$subCollection->addNamePrefix($config['name_prefix']);
}
$collection->addCollection($subCollection);
}
}
@@ -264,11 +259,16 @@ class YamlFileLoader extends FileLoader
* @throws \InvalidArgumentException If one of the provided config keys is not supported,
* something is missing or the combination is nonsense
*/
protected function validate($config, $name, $path)
protected function validate($config, string $name, string $path)
{
if (!\is_array($config)) {
throw new \InvalidArgumentException(sprintf('The definition of "%s" in "%s" must be a YAML array.', $name, $path));
}
if (isset($config['alias'])) {
$this->validateAlias($config, $name, $path);
return;
}
if ($extraKeys = array_diff(array_keys($config), self::AVAILABLE_KEYS)) {
throw new \InvalidArgumentException(sprintf('The routing file "%s" contains unsupported keys for "%s": "%s". Expected one of: "%s".', $path, $name, implode('", "', $extraKeys), implode('", "', self::AVAILABLE_KEYS)));
}
@@ -284,5 +284,31 @@ class YamlFileLoader extends FileLoader
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));
}
if (isset($config['stateless']) && isset($config['defaults']['_stateless'])) {
throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "stateless" key and the defaults key "_stateless" for "%s".', $path, $name));
}
}
/**
* @throws \InvalidArgumentException If one of the provided config keys is not supported,
* something is missing or the combination is nonsense
*/
private function validateAlias(array $config, string $name, string $path): void
{
foreach ($config as $key => $value) {
if (!\in_array($key, ['alias', 'deprecated'], true)) {
throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify other keys than "alias" and "deprecated" for "%s".', $path, $name));
}
if ('deprecated' === $key) {
if (!isset($value['package'])) {
throw new \InvalidArgumentException(sprintf('The routing file "%s" must specify the attribute "package" of the "deprecated" option for "%s".', $path, $name));
}
if (!isset($value['version'])) {
throw new \InvalidArgumentException(sprintf('The routing file "%s" must specify the attribute "version" of the "deprecated" option for "%s".', $path, $name));
}
}
}
}
}

View File

@@ -21,9 +21,18 @@
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="import" type="import" />
<xsd:element name="route" type="route" />
<xsd:element name="when" type="when" />
</xsd:choice>
</xsd:complexType>
<xsd:complexType name="when">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="import" type="import" />
<xsd:element name="route" type="route" />
</xsd:choice>
<xsd:attribute name="env" type="xsd:string" use="required" />
</xsd:complexType>
<xsd:complexType name="localized-path">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
@@ -45,6 +54,8 @@
<xsd:sequence>
<xsd:group ref="configs" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="path" type="localized-path" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="host" type="localized-path" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="deprecated" type="deprecated" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
<xsd:attribute name="id" type="xsd:string" use="required" />
<xsd:attribute name="path" type="xsd:string" />
@@ -55,6 +66,8 @@
<xsd:attribute name="locale" type="xsd:string" />
<xsd:attribute name="format" type="xsd:string" />
<xsd:attribute name="utf8" type="xsd:boolean" />
<xsd:attribute name="stateless" type="xsd:boolean" />
<xsd:attribute name="alias" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="import">
@@ -62,6 +75,7 @@
<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:element name="host" type="localized-path" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="resource" type="xsd:string" use="required" />
<xsd:attribute name="type" type="xsd:string" />
@@ -76,6 +90,7 @@
<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:attribute name="stateless" type="xsd:boolean" />
</xsd:complexType>
<xsd:complexType name="default" mixed="true">
@@ -167,4 +182,13 @@
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="deprecated">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="package" type="xsd:string" use="required" />
<xsd:attribute name="version" type="xsd:string" use="required" />
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
</xsd:schema>