updated-packages

This commit is contained in:
RafficMohammed
2023-01-08 00:13:22 +05:30
parent 3ff7df7487
commit da241bacb6
12659 changed files with 563377 additions and 510538 deletions

View File

@@ -1,3 +0,0 @@
vendor/
composer.lock
phpunit.xml

View File

@@ -22,14 +22,14 @@ namespace Symfony\Component\Routing\Annotation;
class Route
{
private $path;
private $localizedPaths = array();
private $localizedPaths = [];
private $name;
private $requirements = array();
private $options = array();
private $defaults = array();
private $requirements = [];
private $options = [];
private $defaults = [];
private $host;
private $methods = array();
private $schemes = array();
private $methods = [];
private $schemes = [];
private $condition;
/**
@@ -40,7 +40,7 @@ class Route
public function __construct(array $data)
{
if (isset($data['localized_paths'])) {
throw new \BadMethodCallException(sprintf('Unknown property "localized_paths" on annotation "%s".', \get_class($this)));
throw new \BadMethodCallException(sprintf('Unknown property "localized_paths" on annotation "%s".', static::class));
}
if (isset($data['value'])) {
@@ -53,10 +53,25 @@ class Route
unset($data['path']);
}
if (isset($data['locale'])) {
$data['defaults']['_locale'] = $data['locale'];
unset($data['locale']);
}
if (isset($data['format'])) {
$data['defaults']['_format'] = $data['format'];
unset($data['format']);
}
if (isset($data['utf8'])) {
$data['options']['utf8'] = filter_var($data['utf8'], \FILTER_VALIDATE_BOOLEAN) ?: false;
unset($data['utf8']);
}
foreach ($data as $key => $value) {
$method = 'set'.str_replace('_', '', $key);
if (!method_exists($this, $method)) {
throw new \BadMethodCallException(sprintf('Unknown property "%s" on annotation "%s".', $key, \get_class($this)));
throw new \BadMethodCallException(sprintf('Unknown property "%s" on annotation "%s".', $key, static::class));
}
$this->$method($value);
}
@@ -134,7 +149,7 @@ class Route
public function setSchemes($schemes)
{
$this->schemes = \is_array($schemes) ? $schemes : array($schemes);
$this->schemes = \is_array($schemes) ? $schemes : [$schemes];
}
public function getSchemes()
@@ -144,7 +159,7 @@ class Route
public function setMethods($methods)
{
$this->methods = \is_array($methods) ? $methods : array($methods);
$this->methods = \is_array($methods) ? $methods : [$methods];
}
public function getMethods()

View File

@@ -1,6 +1,26 @@
CHANGELOG
=========
4.4.0
-----
* Deprecated `ServiceRouterLoader` in favor of `ContainerLoader`.
* Deprecated `ObjectRouteLoader` in favor of `ObjectLoader`.
* Added a way to exclude patterns of resources from being imported by the `import()` method
4.3.0
-----
* added `CompiledUrlMatcher` and `CompiledUrlMatcherDumper`
* added `CompiledUrlGenerator` and `CompiledUrlGeneratorDumper`
* deprecated `PhpGeneratorDumper` and `PhpMatcherDumper`
* deprecated `generator_base_class`, `generator_cache_class`, `matcher_base_class` and `matcher_cache_class` router options
* `Serializable` implementing methods for `Route` and `CompiledRoute` are marked as `@internal` and `@final`.
Instead of overwriting them, use `__serialize` and `__unserialize` as extension points which are forward compatible
with the new serialization methods in PHP 7.4.
* exposed `utf8` Route option, defaults "locale" and "format" in configuration loaders and configurators
* added support for invokable service route loaders
4.2.0
-----
@@ -24,15 +44,15 @@ CHANGELOG
3.3.0
-----
* [DEPRECATION] Class parameters have been deprecated and will be removed in 4.0.
* router.options.generator_class
* router.options.generator_base_class
* router.options.generator_dumper_class
* router.options.matcher_class
* router.options.matcher_base_class
* router.options.matcher_dumper_class
* router.options.matcher.cache_class
* router.options.generator.cache_class
* [DEPRECATION] Class parameters have been deprecated and will be removed in 4.0.
* router.options.generator_class
* router.options.generator_base_class
* router.options.generator_dumper_class
* router.options.matcher_class
* router.options.matcher_base_class
* router.options.matcher_dumper_class
* router.options.matcher.cache_class
* router.options.generator.cache_class
3.2.0
-----
@@ -52,7 +72,7 @@ CHANGELOG
Before:
```php
$router->generate('blog_show', array('slug' => 'my-blog-post'), true);
$router->generate('blog_show', ['slug' => 'my-blog-post'], true);
```
After:
@@ -60,7 +80,7 @@ CHANGELOG
```php
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
$router->generate('blog_show', array('slug' => 'my-blog-post'), UrlGeneratorInterface::ABSOLUTE_URL);
$router->generate('blog_show', ['slug' => 'my-blog-post'], UrlGeneratorInterface::ABSOLUTE_URL);
```
2.5.0
@@ -68,7 +88,7 @@ CHANGELOG
* [DEPRECATION] The `ApacheMatcherDumper` and `ApacheUrlMatcher` were deprecated and
will be removed in Symfony 3.0, since the performance gains were minimal and
it's hard to replicate the behaviour of PHP implementation.
it's hard to replicate the behavior of PHP implementation.
2.3.0
-----
@@ -125,7 +145,7 @@ CHANGELOG
```php
$route = new Route();
$route->setPath('/article/{id}');
$route->setMethods(array('POST', 'PUT'));
$route->setMethods(['POST', 'PUT']);
$route->setSchemes('https');
```
@@ -180,10 +200,10 @@ CHANGELOG
used with a single parameter. The other params `$prefix`, `$default`, `$requirements` and `$options`
will still work, but have been deprecated. The `addPrefix` method should be used for this
use-case instead.
Before: `$parentCollection->addCollection($collection, '/prefix', array(...), array(...))`
Before: `$parentCollection->addCollection($collection, '/prefix', [...], [...])`
After:
```php
$collection->addPrefix('/prefix', array(...), array(...));
$collection->addPrefix('/prefix', [...], [...]);
$parentCollection->addCollection($collection);
```
* added support for the method default argument values when defining a @Route
@@ -208,7 +228,7 @@ CHANGELOG
(only relevant if you implemented your own RouteCompiler).
* Added possibility to generate relative paths and network paths in the UrlGenerator, e.g.
"../parent-file" and "//example.com/dir/file". The third parameter in
`UrlGeneratorInterface::generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH)`
`UrlGeneratorInterface::generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH)`
now accepts more values and you should use the constants defined in `UrlGeneratorInterface` for
claritiy. The old method calls with a Boolean parameter will continue to work because they
equal the signature using the constants.

View File

@@ -37,7 +37,7 @@ class CompiledRoute implements \Serializable
* @param array $hostVariables An array of host variables
* @param array $variables An array of variables (variables defined in the path and in the host patterns)
*/
public function __construct(string $staticPrefix, string $regex, array $tokens, array $pathVariables, string $hostRegex = null, array $hostTokens = array(), array $hostVariables = array(), array $variables = array())
public function __construct(string $staticPrefix, string $regex, array $tokens, array $pathVariables, string $hostRegex = null, array $hostTokens = [], array $hostVariables = [], array $variables = [])
{
$this->staticPrefix = $staticPrefix;
$this->regex = $regex;
@@ -49,12 +49,9 @@ class CompiledRoute implements \Serializable
$this->variables = $variables;
}
/**
* {@inheritdoc}
*/
public function serialize()
public function __serialize(): array
{
return serialize(array(
return [
'vars' => $this->variables,
'path_prefix' => $this->staticPrefix,
'path_regex' => $this->regex,
@@ -63,16 +60,22 @@ class CompiledRoute implements \Serializable
'host_regex' => $this->hostRegex,
'host_tokens' => $this->hostTokens,
'host_vars' => $this->hostVariables,
));
];
}
/**
* {@inheritdoc}
* @return string
*
* @internal since Symfony 4.3
* @final since Symfony 4.3
*/
public function unserialize($serialized)
public function serialize()
{
$data = unserialize($serialized, array('allowed_classes' => false));
return serialize($this->__serialize());
}
public function __unserialize(array $data): void
{
$this->variables = $data['vars'];
$this->staticPrefix = $data['path_prefix'];
$this->regex = $data['path_regex'];
@@ -83,6 +86,15 @@ class CompiledRoute implements \Serializable
$this->hostVariables = $data['host_vars'];
}
/**
* @internal since Symfony 4.3
* @final since Symfony 4.3
*/
public function unserialize($serialized)
{
$this->__unserialize(unserialize($serialized, ['allowed_classes' => false]));
}
/**
* Returns the static prefix.
*

View File

@@ -43,7 +43,7 @@ class RoutingResolverPass implements CompilerPassInterface
$definition = $container->getDefinition($this->resolverServiceId);
foreach ($this->findAndSortTaggedServices($this->loaderTag, $container) as $id) {
$definition->addMethodCall('addLoader', array(new Reference($id)));
$definition->addMethodCall('addLoader', [new Reference($id)]);
}
}
}

View File

@@ -20,9 +20,12 @@ namespace Symfony\Component\Routing\Exception;
*/
class MethodNotAllowedException extends \RuntimeException implements ExceptionInterface
{
protected $allowedMethods = array();
protected $allowedMethods = [];
public function __construct(array $allowedMethods, string $message = null, int $code = 0, \Exception $previous = null)
/**
* @param string[] $allowedMethods
*/
public function __construct(array $allowedMethods, ?string $message = '', int $code = 0, \Throwable $previous = null)
{
$this->allowedMethods = array_map('strtoupper', $allowedMethods);
@@ -32,7 +35,7 @@ class MethodNotAllowedException extends \RuntimeException implements ExceptionIn
/**
* Gets the allowed HTTP methods.
*
* @return array
* @return string[]
*/
public function getAllowedMethods()
{

View File

@@ -0,0 +1,65 @@
<?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\Generator;
use Psr\Log\LoggerInterface;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Symfony\Component\Routing\RequestContext;
/**
* Generates URLs based on rules dumped by CompiledUrlGeneratorDumper.
*/
class CompiledUrlGenerator extends UrlGenerator
{
private $compiledRoutes = [];
private $defaultLocale;
public function __construct(array $compiledRoutes, RequestContext $context, LoggerInterface $logger = null, string $defaultLocale = null)
{
$this->compiledRoutes = $compiledRoutes;
$this->context = $context;
$this->logger = $logger;
$this->defaultLocale = $defaultLocale;
}
public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH)
{
$locale = $parameters['_locale']
?? $this->context->getParameter('_locale')
?: $this->defaultLocale;
if (null !== $locale) {
do {
if (($this->compiledRoutes[$name.'.'.$locale][1]['_canonical_route'] ?? null) === $name) {
$name .= '.'.$locale;
break;
}
} while (false !== $locale = strstr($locale, '_', true));
}
if (!isset($this->compiledRoutes[$name])) {
throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name));
}
[$variables, $defaults, $requirements, $tokens, $hostTokens, $requiredSchemes] = $this->compiledRoutes[$name];
if (isset($defaults['_canonical_route']) && isset($defaults['_locale'])) {
if (!\in_array('_locale', $variables, true)) {
unset($parameters['_locale']);
} elseif (!isset($parameters['_locale'])) {
$parameters['_locale'] = $defaults['_locale'];
}
}
return $this->doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, $requiredSchemes);
}
}

View File

@@ -20,7 +20,7 @@ namespace Symfony\Component\Routing\Generator;
* The possible configurations and use-cases:
* - setStrictRequirements(true): Throw an exception for mismatching requirements. This
* is mostly useful in development environment.
* - setStrictRequirements(false): Don't throw an exception but return null as URL for
* - setStrictRequirements(false): Don't throw an exception but return an empty string as URL for
* mismatching requirements and log the problem. Useful when you cannot control all
* params because they come from third party libs but don't want to have a 404 in
* production environment. It should log the mismatch so one can review it.

View File

@@ -0,0 +1,73 @@
<?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\Generator\Dumper;
use Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherDumper;
/**
* CompiledUrlGeneratorDumper creates a PHP array to be used with CompiledUrlGenerator.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Tobias Schultze <http://tobion.de>
* @author Nicolas Grekas <p@tchwork.com>
*/
class CompiledUrlGeneratorDumper extends GeneratorDumper
{
public function getCompiledRoutes(): array
{
$compiledRoutes = [];
foreach ($this->getRoutes()->all() as $name => $route) {
$compiledRoute = $route->compile();
$compiledRoutes[$name] = [
$compiledRoute->getVariables(),
$route->getDefaults(),
$route->getRequirements(),
$compiledRoute->getTokens(),
$compiledRoute->getHostTokens(),
$route->getSchemes(),
];
}
return $compiledRoutes;
}
/**
* {@inheritdoc}
*/
public function dump(array $options = [])
{
return <<<EOF
<?php
// This file has been auto-generated by the Symfony Routing Component.
return [{$this->generateDeclaredRoutes()}
];
EOF;
}
/**
* Generates PHP code representing an array of defined routes
* together with the routes properties (e.g. requirements).
*/
private function generateDeclaredRoutes(): string
{
$routes = '';
foreach ($this->getCompiledRoutes() as $name => $properties) {
$routes .= sprintf("\n '%s' => %s,", $name, CompiledUrlMatcherDumper::export($properties));
}
return $routes;
}
}

View File

@@ -24,11 +24,9 @@ interface GeneratorDumperInterface
* Dumps a set of routes to a string representation of executable code
* that can then be used to generate a URL of such a route.
*
* @param array $options An array of options
*
* @return string Executable code
*/
public function dump(array $options = array());
public function dump(array $options = []);
/**
* Gets the routes to dump.

View File

@@ -11,13 +11,17 @@
namespace Symfony\Component\Routing\Generator\Dumper;
use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "CompiledUrlGeneratorDumper" instead.', PhpGeneratorDumper::class), \E_USER_DEPRECATED);
use Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherDumper;
/**
* PhpGeneratorDumper creates a PHP class able to generate URLs for a given set of routes.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Tobias Schultze <http://tobion.de>
*
* @deprecated since Symfony 4.3, use CompiledUrlGeneratorDumper instead.
*/
class PhpGeneratorDumper extends GeneratorDumper
{
@@ -33,12 +37,12 @@ class PhpGeneratorDumper extends GeneratorDumper
*
* @return string A PHP class representing the generator class
*/
public function dump(array $options = array())
public function dump(array $options = [])
{
$options = array_merge(array(
$options = array_merge([
'class' => 'ProjectUrlGenerator',
'base_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator',
), $options);
], $options);
return <<<EOF
<?php
@@ -75,16 +79,14 @@ EOF;
/**
* Generates PHP code representing an array of defined routes
* together with the routes properties (e.g. requirements).
*
* @return string PHP code
*/
private function generateDeclaredRoutes()
private function generateDeclaredRoutes(): string
{
$routes = "array(\n";
$routes = "[\n";
foreach ($this->getRoutes()->all() as $name => $route) {
$compiledRoute = $route->compile();
$properties = array();
$properties = [];
$properties[] = $compiledRoute->getVariables();
$properties[] = $route->getDefaults();
$properties[] = $route->getRequirements();
@@ -92,22 +94,20 @@ EOF;
$properties[] = $compiledRoute->getHostTokens();
$properties[] = $route->getSchemes();
$routes .= sprintf(" '%s' => %s,\n", $name, PhpMatcherDumper::export($properties));
$routes .= sprintf(" '%s' => %s,\n", $name, CompiledUrlMatcherDumper::export($properties));
}
$routes .= ' )';
$routes .= ' ]';
return $routes;
}
/**
* Generates PHP code representing the `generate` method that implements the UrlGeneratorInterface.
*
* @return string PHP code
*/
private function generateGenerateMethod()
private function generateGenerateMethod(): string
{
return <<<'EOF'
public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH)
public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH)
{
$locale = $parameters['_locale']
?? $this->context->getParameter('_locale')
@@ -116,7 +116,6 @@ EOF;
if (null !== $locale && null !== $name) {
do {
if ((self::$declaredRoutes[$name.'.'.$locale][1]['_canonical_route'] ?? null) === $name) {
unset($parameters['_locale']);
$name .= '.'.$locale;
break;
}
@@ -129,6 +128,14 @@ EOF;
list($variables, $defaults, $requirements, $tokens, $hostTokens, $requiredSchemes) = self::$declaredRoutes[$name];
if (isset($defaults['_canonical_route']) && isset($defaults['_locale'])) {
if (!\in_array('_locale', $variables, true)) {
unset($parameters['_locale']);
} elseif (!isset($parameters['_locale'])) {
$parameters['_locale'] = $defaults['_locale'];
}
}
return $this->doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, $requiredSchemes);
}
EOF;

View File

@@ -27,6 +27,20 @@ use Symfony\Component\Routing\RouteCollection;
*/
class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInterface
{
private const QUERY_FRAGMENT_DECODED = [
// RFC 3986 explicitly allows those in the query/fragment to reference other URIs unencoded
'%2F' => '/',
'%3F' => '?',
// reserved chars that have no special meaning for HTTP URIs in a query or fragment
// this excludes esp. "&", "=" and also "+" because PHP would treat it as a space (form-encoded)
'%40' => '@',
'%3A' => ':',
'%21' => '!',
'%3B' => ';',
'%2C' => ',',
'%2A' => '*',
];
protected $routes;
protected $context;
@@ -47,7 +61,7 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
* "?" and "#" (would be interpreted wrongly as query and fragment identifier),
* "'" and """ (are used as delimiters in HTML).
*/
protected $decodedChars = array(
protected $decodedChars = [
// the slash can be used to designate a hierarchical structure and we want allow using it with this meaning
// some webservers don't allow the slash in encoded form in the path for security reasons anyway
// see http://stackoverflow.com/questions/4069002/http-400-if-2f-part-of-get-url-in-jboss
@@ -65,7 +79,7 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
'%21' => '!',
'%2A' => '*',
'%7C' => '|',
);
];
public function __construct(RouteCollection $routes, RequestContext $context, LoggerInterface $logger = null, string $defaultLocale = null)
{
@@ -110,7 +124,7 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
/**
* {@inheritdoc}
*/
public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH)
public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH)
{
$route = null;
$locale = $parameters['_locale']
@@ -120,7 +134,6 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
if (null !== $locale) {
do {
if (null !== ($route = $this->routes->get($name.'.'.$locale)) && $route->getDefault('_canonical_route') === $name) {
unset($parameters['_locale']);
break;
}
} while (false !== $locale = strstr($locale, '_', true));
@@ -133,15 +146,28 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
// the Route has a cache of its own and is not recompiled as long as it does not get modified
$compiledRoute = $route->compile();
return $this->doGenerate($compiledRoute->getVariables(), $route->getDefaults(), $route->getRequirements(), $compiledRoute->getTokens(), $parameters, $name, $referenceType, $compiledRoute->getHostTokens(), $route->getSchemes());
$defaults = $route->getDefaults();
$variables = $compiledRoute->getVariables();
if (isset($defaults['_canonical_route']) && isset($defaults['_locale'])) {
if (!\in_array('_locale', $variables, true)) {
unset($parameters['_locale']);
} elseif (!isset($parameters['_locale'])) {
$parameters['_locale'] = $defaults['_locale'];
}
}
return $this->doGenerate($variables, $defaults, $route->getRequirements(), $compiledRoute->getTokens(), $parameters, $name, $referenceType, $compiledRoute->getHostTokens(), $route->getSchemes());
}
/**
* @throws MissingMandatoryParametersException When some parameters are missing that are mandatory for the route
* @throws InvalidParameterException When a parameter value for a placeholder is not correct because
* it does not match the requirement
*
* @return string
*/
protected function doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, array $requiredSchemes = array())
protected function doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, array $requiredSchemes = [])
{
$variables = array_flip($variables);
$mergedParams = array_replace($defaults, $this->context->getParameters(), $parameters);
@@ -156,21 +182,25 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
$message = 'Parameter "{parameter}" for route "{route}" must match "{expected}" ("{given}" given) to generate a corresponding URL.';
foreach ($tokens as $token) {
if ('variable' === $token[0]) {
if (!$optional || !array_key_exists($token[3], $defaults) || null !== $mergedParams[$token[3]] && (string) $mergedParams[$token[3]] !== (string) $defaults[$token[3]]) {
// check requirement
if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#'.(empty($token[4]) ? '' : 'u'), $mergedParams[$token[3]])) {
$varName = $token[3];
// variable is not important by default
$important = $token[5] ?? false;
if (!$optional || $important || !\array_key_exists($varName, $defaults) || (null !== $mergedParams[$varName] && (string) $mergedParams[$varName] !== (string) $defaults[$varName])) {
// check requirement (while ignoring look-around patterns)
if (null !== $this->strictRequirements && !preg_match('#^'.preg_replace('/\(\?(?:=|<=|!|<!)((?:[^()\\\\]+|\\\\.|\((?1)\))*)\)/', '', $token[2]).'$#i'.(empty($token[4]) ? '' : 'u'), $mergedParams[$token[3]] ?? '')) {
if ($this->strictRequirements) {
throw new InvalidParameterException(strtr($message, array('{parameter}' => $token[3], '{route}' => $name, '{expected}' => $token[2], '{given}' => $mergedParams[$token[3]])));
throw new InvalidParameterException(strtr($message, ['{parameter}' => $varName, '{route}' => $name, '{expected}' => $token[2], '{given}' => $mergedParams[$varName]]));
}
if ($this->logger) {
$this->logger->error($message, array('parameter' => $token[3], 'route' => $name, 'expected' => $token[2], 'given' => $mergedParams[$token[3]]));
$this->logger->error($message, ['parameter' => $varName, 'route' => $name, 'expected' => $token[2], 'given' => $mergedParams[$varName]]);
}
return;
return '';
}
$url = $token[1].$mergedParams[$token[3]].$url;
$url = $token[1].$mergedParams[$varName].$url;
$optional = false;
}
} else {
@@ -190,10 +220,10 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
// the path segments "." and ".." are interpreted as relative reference when resolving a URI; see http://tools.ietf.org/html/rfc3986#section-3.3
// so we need to encode them as they are not used for this purpose here
// otherwise we would generate a URI that, when followed by a user agent (e.g. browser), does not match this route
$url = strtr($url, array('/../' => '/%2E%2E/', '/./' => '/%2E/'));
if ('/..' === substr($url, -3)) {
$url = strtr($url, ['/../' => '/%2E%2E/', '/./' => '/%2E/']);
if (str_ends_with($url, '/..')) {
$url = substr($url, 0, -2).'%2E%2E';
} elseif ('/.' === substr($url, -2)) {
} elseif (str_ends_with($url, '/.')) {
$url = substr($url, 0, -1).'%2E';
}
@@ -212,16 +242,17 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
$routeHost = '';
foreach ($hostTokens as $token) {
if ('variable' === $token[0]) {
if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#i'.(empty($token[4]) ? '' : 'u'), $mergedParams[$token[3]])) {
// check requirement (while ignoring look-around patterns)
if (null !== $this->strictRequirements && !preg_match('#^'.preg_replace('/\(\?(?:=|<=|!|<!)((?:[^()\\\\]+|\\\\.|\((?1)\))*)\)/', '', $token[2]).'$#i'.(empty($token[4]) ? '' : 'u'), $mergedParams[$token[3]])) {
if ($this->strictRequirements) {
throw new InvalidParameterException(strtr($message, array('{parameter}' => $token[3], '{route}' => $name, '{expected}' => $token[2], '{given}' => $mergedParams[$token[3]])));
throw new InvalidParameterException(strtr($message, ['{parameter}' => $token[3], '{route}' => $name, '{expected}' => $token[2], '{given}' => $mergedParams[$token[3]]]));
}
if ($this->logger) {
$this->logger->error($message, array('parameter' => $token[3], 'route' => $name, 'expected' => $token[2], 'given' => $mergedParams[$token[3]]));
$this->logger->error($message, ['parameter' => $token[3], 'route' => $name, 'expected' => $token[2], 'given' => $mergedParams[$token[3]]]);
}
return;
return '';
}
$routeHost = $token[1].$mergedParams[$token[3]].$routeHost;
@@ -238,16 +269,18 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
}
}
if ((self::ABSOLUTE_URL === $referenceType || self::NETWORK_PATH === $referenceType) && !empty($host)) {
$port = '';
if ('http' === $scheme && 80 != $this->context->getHttpPort()) {
$port = ':'.$this->context->getHttpPort();
} elseif ('https' === $scheme && 443 != $this->context->getHttpsPort()) {
$port = ':'.$this->context->getHttpsPort();
}
if (self::ABSOLUTE_URL === $referenceType || self::NETWORK_PATH === $referenceType) {
if ('' !== $host || ('' !== $scheme && 'http' !== $scheme && 'https' !== $scheme)) {
$port = '';
if ('http' === $scheme && 80 !== $this->context->getHttpPort()) {
$port = ':'.$this->context->getHttpPort();
} elseif ('https' === $scheme && 443 !== $this->context->getHttpsPort()) {
$port = ':'.$this->context->getHttpsPort();
}
$schemeAuthority = self::NETWORK_PATH === $referenceType ? '//' : "$scheme://";
$schemeAuthority .= $host.$port;
$schemeAuthority = self::NETWORK_PATH === $referenceType || '' === $scheme ? '//' : "$scheme://";
$schemeAuthority .= $host.$port;
}
}
if (self::RELATIVE_PATH === $referenceType) {
@@ -269,14 +302,12 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
unset($extra['_fragment']);
}
if ($extra && $query = http_build_query($extra, '', '&', PHP_QUERY_RFC3986)) {
// "/" and "?" can be left decoded for better user experience, see
// http://tools.ietf.org/html/rfc3986#section-3.4
$url .= '?'.strtr($query, array('%2F' => '/'));
if ($extra && $query = http_build_query($extra, '', '&', \PHP_QUERY_RFC3986)) {
$url .= '?'.strtr($query, self::QUERY_FRAGMENT_DECODED);
}
if ('' !== $fragment) {
$url .= '#'.strtr(rawurlencode($fragment), array('%2F' => '/', '%3F' => '?'));
$url .= '#'.strtr(rawurlencode($fragment), self::QUERY_FRAGMENT_DECODED);
}
return $url;

View File

@@ -34,25 +34,25 @@ interface UrlGeneratorInterface extends RequestContextAwareInterface
/**
* Generates an absolute URL, e.g. "http://example.com/dir/file".
*/
const ABSOLUTE_URL = 0;
public const ABSOLUTE_URL = 0;
/**
* Generates an absolute path, e.g. "/dir/file".
*/
const ABSOLUTE_PATH = 1;
public const ABSOLUTE_PATH = 1;
/**
* Generates a relative path based on the current request path, e.g. "../parent-file".
*
* @see UrlGenerator::getRelativePath()
*/
const RELATIVE_PATH = 2;
public const RELATIVE_PATH = 2;
/**
* Generates a network path, e.g. "//example.com/dir/file".
* Such reference reuses the current scheme but specifies the host.
*/
const NETWORK_PATH = 3;
public const NETWORK_PATH = 3;
/**
* Generates a URL or path for a specific route based on the given parameters.
@@ -71,9 +71,9 @@ interface UrlGeneratorInterface extends RequestContextAwareInterface
*
* The special parameter _fragment will be used as the document fragment suffixed to the final URL.
*
* @param string $name The name of the route
* @param mixed $parameters An array of parameters
* @param int $referenceType The type of reference to be generated (one of the constants)
* @param string $name The name of the route
* @param mixed[] $parameters An array of parameters
* @param int $referenceType The type of reference to be generated (one of the constants)
*
* @return string The generated URL
*
@@ -82,5 +82,5 @@ interface UrlGeneratorInterface extends RequestContextAwareInterface
* @throws InvalidParameterException When a parameter value for a placeholder is not correct because
* it does not match the requirement
*/
public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH);
public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH);
}

View File

@@ -1,4 +1,4 @@
Copyright (c) 2004-2018 Fabien Potencier
Copyright (c) 2004-2022 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -15,13 +15,15 @@ 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;
use Symfony\Component\Routing\RouteCompiler;
/**
* AnnotationClassLoader loads routing information from a PHP class and its methods.
*
* You need to define an implementation for the getRouteDefaults() method. Most of the
* You need to define an implementation for the configureRoute() method. Most of the
* time, this method should define some PHP callable to be called for the route
* (a controller in MVC speak).
*
@@ -32,7 +34,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 +45,6 @@ use Symfony\Component\Routing\RouteCollection;
* public function index()
* {
* }
*
* /**
* * @Route("/{id}", name="blog_post", requirements = {"id" = "\d+"})
* * /
@@ -131,6 +131,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();
@@ -143,7 +147,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);
@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);
}
}
@@ -165,7 +169,7 @@ abstract class AnnotationClassLoader implements LoaderInterface
$path = $annot->getLocalizedPaths() ?: $annot->getPath();
$prefix = $globals['localized_paths'] ?: $globals['path'];
$paths = array();
$paths = [];
if (\is_array($path)) {
if (!\is_array($prefix)) {
@@ -196,7 +200,7 @@ abstract class AnnotationClassLoader implements LoaderInterface
continue;
}
foreach ($paths as $locale => $path) {
if (false !== strpos($path, sprintf('{%s}', $param->name))) {
if (preg_match(sprintf('/\{%s(?:<.*?>)?\}/', preg_quote($param->name)), $path)) {
$defaults[$param->name] = $param->getDefaultValue();
break;
}
@@ -208,6 +212,7 @@ 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->setDefault('_canonical_route', $name);
$collection->add($name.'.'.$locale, $route);
} else {
@@ -241,14 +246,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;
}
@@ -302,7 +305,7 @@ abstract class AnnotationClassLoader implements LoaderInterface
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);
@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);
}
}
}
@@ -312,18 +315,18 @@ abstract class AnnotationClassLoader implements LoaderInterface
private function resetGlobals()
{
return array(
return [
'path' => null,
'localized_paths' => array(),
'requirements' => array(),
'options' => array(),
'defaults' => array(),
'schemes' => array(),
'methods' => array(),
'localized_paths' => [],
'requirements' => [],
'options' => [],
'defaults' => [],
'schemes' => [],
'methods' => [],
'host' => '',
'condition' => '',
'name' => '',
);
];
}
protected function createRoute($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition)

View File

@@ -54,7 +54,7 @@ class AnnotationDirectoryLoader extends AnnotationFileLoader
});
foreach ($files as $file) {
if (!$file->isFile() || '.php' !== substr($file->getFilename(), -4)) {
if (!$file->isFile() || !str_ends_with($file->getFilename(), '.php')) {
continue;
}

View File

@@ -26,9 +26,6 @@ class AnnotationFileLoader extends FileLoader
{
protected $loader;
/**
* @throws \RuntimeException
*/
public function __construct(FileLocatorInterface $locator, AnnotationClassLoader $loader)
{
if (!\function_exists('token_get_all')) {
@@ -46,7 +43,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,11 +53,15 @@ 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));
}
// PHP 7 memory manager will not release after token_get_all(), see https://bugs.php.net/70098
gc_mem_caches();
return $collection;
@@ -71,7 +72,7 @@ class AnnotationFileLoader extends FileLoader
*/
public function supports($resource, $type = null)
{
return \is_string($resource) && 'php' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'annotation' === $type);
return \is_string($resource) && 'php' === pathinfo($resource, \PATHINFO_EXTENSION) && (!$type || 'annotation' === $type);
}
/**
@@ -87,41 +88,47 @@ class AnnotationFileLoader extends FileLoader
$namespace = false;
$tokens = token_get_all(file_get_contents($file));
if (1 === \count($tokens) && T_INLINE_HTML === $tokens[0][0]) {
if (1 === \count($tokens) && \T_INLINE_HTML === $tokens[0][0]) {
throw new \InvalidArgumentException(sprintf('The file "%s" does not contain PHP code. Did you forgot to add the "<?php" start tag at the beginning of the file?', $file));
}
$nsTokens = [\T_NS_SEPARATOR => true, \T_STRING => true];
if (\defined('T_NAME_QUALIFIED')) {
$nsTokens[\T_NAME_QUALIFIED] = true;
}
for ($i = 0; isset($tokens[$i]); ++$i) {
$token = $tokens[$i];
if (!isset($token[1])) {
continue;
}
if (true === $class && T_STRING === $token[0]) {
if (true === $class && \T_STRING === $token[0]) {
return $namespace.'\\'.$token[1];
}
if (true === $namespace && T_STRING === $token[0]) {
if (true === $namespace && isset($nsTokens[$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], $nsTokens[$tokens[$i][0]])) {
$namespace .= $tokens[$i][1];
}
$token = $tokens[$i];
}
if (T_CLASS === $token[0]) {
if (\T_CLASS === $token[0]) {
// Skip usage of ::class constant and anonymous classes
$skipClassToken = false;
for ($j = $i - 1; $j > 0; --$j) {
if (!isset($tokens[$j][1])) {
if ('(' === $tokens[$j] || ',' === $tokens[$j]) {
$skipClassToken = true;
}
break;
}
if (T_DOUBLE_COLON === $tokens[$j][0] || T_NEW === $tokens[$j][0]) {
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;
}
}
@@ -131,7 +138,7 @@ class AnnotationFileLoader extends FileLoader
}
}
if (T_NAMESPACE === $token[0]) {
if (\T_NAMESPACE === $token[0]) {
$namespace = true;
}
}

View File

@@ -36,6 +36,19 @@ class CollectionConfigurator
$this->parentPrefixes = $parentPrefixes;
}
/**
* @return array
*/
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct()
{
if (null === $this->prefixes) {
@@ -47,10 +60,8 @@ class CollectionConfigurator
/**
* Creates a sub-collection.
*
* @return self
*/
final public function collection($name = '')
final public function collection(string $name = ''): self
{
return new self($this->collection, $this->name.$name, $this, $this->prefixes);
}
@@ -62,7 +73,7 @@ class CollectionConfigurator
*
* @return $this
*/
final public function prefix($prefix)
final public function prefix($prefix): self
{
if (\is_array($prefix)) {
if (null === $this->parentPrefixes) {
@@ -88,7 +99,7 @@ class CollectionConfigurator
return $this;
}
private function createRoute($path): Route
private function createRoute(string $path): Route
{
return (clone $this->route)->setPath($path);
}

View File

@@ -13,6 +13,7 @@ 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>
@@ -29,6 +30,19 @@ class ImportConfigurator
$this->route = $route;
}
/**
* @return array
*/
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct()
{
$this->parent->addCollection($this->route);
@@ -41,7 +55,7 @@ class ImportConfigurator
*
* @return $this
*/
final public function prefix($prefix, bool $trailingSlashOnRoot = true)
final public function prefix($prefix, bool $trailingSlashOnRoot = true): self
{
if (!\is_array($prefix)) {
$this->route->addPrefix($prefix);
@@ -63,6 +77,7 @@ class ImportConfigurator
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);
@@ -84,7 +99,7 @@ class ImportConfigurator
*
* @return $this
*/
final public function namePrefix(string $namePrefix)
final public function namePrefix(string $namePrefix): self
{
$this->route->addNamePrefix($namePrefix);

View File

@@ -34,12 +34,13 @@ class RoutingConfigurator
}
/**
* @return ImportConfigurator
* @param string|string[]|null $exclude Glob patterns to exclude from the import
*/
final public function import($resource, $type = null, $ignoreErrors = false)
final public function import($resource, string $type = null, bool $ignoreErrors = false, $exclude = null): ImportConfigurator
{
$this->loader->setCurrentDir(\dirname($this->path));
$imported = $this->loader->import($resource, $type, $ignoreErrors, $this->file);
$imported = $this->loader->import($resource, $type, $ignoreErrors, $this->file, $exclude) ?: [];
if (!\is_array($imported)) {
return new ImportConfigurator($this->collection, $imported);
}
@@ -52,10 +53,7 @@ class RoutingConfigurator
return new ImportConfigurator($this->collection, $mergedCollection);
}
/**
* @return CollectionConfigurator
*/
final public function collection($name = '')
final public function collection(string $name = ''): CollectionConfigurator
{
return new CollectionConfigurator($this->collection, $name);
}

View File

@@ -15,6 +15,7 @@ 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;
trait AddTrait
{
@@ -34,7 +35,7 @@ trait AddTrait
*/
final public function add(string $name, $path): RouteConfigurator
{
$paths = array();
$paths = [];
$parentConfigurator = $this instanceof CollectionConfigurator ? $this : ($this instanceof RouteConfigurator ? $this->parentConfigurator : null);
if (\is_array($path)) {
@@ -67,6 +68,7 @@ trait AddTrait
$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);
}
@@ -83,7 +85,7 @@ trait AddTrait
return $this->add($name, $path);
}
private function createRoute($path): Route
private function createRoute(string $path): Route
{
return new Route($path);
}

View File

@@ -26,7 +26,7 @@ trait RouteTrait
*
* @return $this
*/
final public function defaults(array $defaults)
final public function defaults(array $defaults): self
{
$this->route->addDefaults($defaults);
@@ -38,7 +38,7 @@ trait RouteTrait
*
* @return $this
*/
final public function requirements(array $requirements)
final public function requirements(array $requirements): self
{
$this->route->addRequirements($requirements);
@@ -50,19 +50,31 @@ trait RouteTrait
*
* @return $this
*/
final public function options(array $options)
final public function options(array $options): self
{
$this->route->addOptions($options);
return $this;
}
/**
* Whether paths should accept utf8 encoding.
*
* @return $this
*/
final public function utf8(bool $utf8 = true): self
{
$this->route->addOptions(['utf8' => $utf8]);
return $this;
}
/**
* Sets the condition.
*
* @return $this
*/
final public function condition(string $condition)
final public function condition(string $condition): self
{
$this->route->setCondition($condition);
@@ -74,7 +86,7 @@ trait RouteTrait
*
* @return $this
*/
final public function host(string $pattern)
final public function host(string $pattern): self
{
$this->route->setHost($pattern);
@@ -89,7 +101,7 @@ trait RouteTrait
*
* @return $this
*/
final public function schemes(array $schemes)
final public function schemes(array $schemes): self
{
$this->route->setSchemes($schemes);
@@ -104,7 +116,7 @@ trait RouteTrait
*
* @return $this
*/
final public function methods(array $methods)
final public function methods(array $methods): self
{
$this->route->setMethods($methods);
@@ -114,13 +126,37 @@ trait RouteTrait
/**
* Adds the "_controller" entry to defaults.
*
* @param callable|string $controller a callable or parseable pseudo-callable
* @param callable|string|array $controller a callable or parseable pseudo-callable
*
* @return $this
*/
final public function controller($controller)
final public function controller($controller): self
{
$this->route->addDefaults(array('_controller' => $controller));
$this->route->addDefaults(['_controller' => $controller]);
return $this;
}
/**
* Adds the "_locale" entry to defaults.
*
* @return $this
*/
final public function locale(string $locale): self
{
$this->route->addDefaults(['_locale' => $locale]);
return $this;
}
/**
* Adds the "_format" entry to defaults.
*
* @return $this
*/
final public function format(string $format): self
{
$this->route->addDefaults(['_format' => $format]);
return $this;
}

View File

@@ -0,0 +1,45 @@
<?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;
use Psr\Container\ContainerInterface;
/**
* A route loader that executes a service from a PSR-11 container to load the routes.
*
* @author Ryan Weaver <ryan@knpuniversity.com>
*/
class ContainerLoader extends ObjectLoader
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* {@inheritdoc}
*/
public function supports($resource, $type = null)
{
return 'service' === $type && \is_string($resource);
}
/**
* {@inheritdoc}
*/
protected function getObject(string $id)
{
return $this->container->get($id);
}
}

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

@@ -0,0 +1,89 @@
<?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;
use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Routing\RouteCollection;
/**
* A route loader that calls a method on an object to load the routes.
*
* @author Ryan Weaver <ryan@knpuniversity.com>
*/
abstract class ObjectLoader extends Loader
{
/**
* 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.
*
* @return object
*/
abstract protected function getObject(string $id);
/**
* Calls the object method that will load the routes.
*
* @param string $resource object_id::method
* @param string|null $type The resource type
*
* @return RouteCollection
*/
public function load($resource, $type = null)
{
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)));
}
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));
}
$routeCollection = $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 object file tracked so that if it changes, the cache rebuilds
$this->addClassResource(new \ReflectionClass($loaderObject), $routeCollection);
return $routeCollection;
}
private function addClassResource(\ReflectionClass $class, RouteCollection $collection)
{
do {
if (is_file($class->getFileName())) {
$collection->addResource(new FileResource($class->getFileName()));
}
} while ($class = $class->getParentClass());
}
}

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,53 +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)
{
if (1 === substr_count($resource, ':')) {
$resource = str_replace(':', '::', $resource);
@trigger_error(sprintf('Referencing service route loaders with a single colon is deprecated since Symfony 4.1. Use %s instead.', $resource), E_USER_DEPRECATED);
}
$parts = explode('::', $resource);
if (2 != \count($parts)) {
throw new \InvalidArgumentException(sprintf('Invalid resource "%s" passed to the "service" route loader: use the format "service::method"', $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 (!\is_callable(array($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}
*/
@@ -89,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,7 +40,7 @@ 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);
@@ -48,7 +48,7 @@ class PhpFileLoader extends FileLoader
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;
}
@@ -63,7 +63,7 @@ class PhpFileLoader extends FileLoader
*/
public function supports($resource, $type = null)
{
return \is_string($resource) && 'php' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'php' === $type);
return \is_string($resource) && 'php' === pathinfo($resource, \PATHINFO_EXTENSION) && (!$type || 'php' === $type);
}
}

View File

@@ -16,6 +16,7 @@ use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Config\Util\XmlUtils;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RouteCompiler;
/**
* XmlFileLoader loads XML routing files.
@@ -25,8 +26,8 @@ use Symfony\Component\Routing\RouteCollection;
*/
class XmlFileLoader extends FileLoader
{
const NAMESPACE_URI = 'http://symfony.com/schema/routing';
const SCHEME_PATH = '/schema/routing/routing-1.0.xsd';
public const NAMESPACE_URI = 'http://symfony.com/schema/routing';
public const SCHEME_PATH = '/schema/routing/routing-1.0.xsd';
/**
* Loads an XML file.
@@ -63,10 +64,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
*/
@@ -93,15 +93,14 @@ class XmlFileLoader extends FileLoader
*/
public function supports($resource, $type = null)
{
return \is_string($resource) && 'xml' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'xml' === $type);
return \is_string($resource) && 'xml' === pathinfo($resource, \PATHINFO_EXTENSION) && (!$type || 'xml' === $type);
}
/**
* 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
*/
@@ -111,10 +110,10 @@ class XmlFileLoader extends FileLoader
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);
$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, $paths) = $this->parseConfigs($node, $path);
[$defaults, $requirements, $options, $condition, $paths] = $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));
@@ -131,6 +130,7 @@ class XmlFileLoader extends FileLoader
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);
}
@@ -140,10 +140,9 @@ class XmlFileLoader extends FileLoader
/**
* 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
*/
@@ -156,22 +155,37 @@ 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;
$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, /* $paths */, $prefixes) = $this->parseConfigs($node, $path);
[$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) {
@@ -197,6 +211,7 @@ class XmlFileLoader extends FileLoader
$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);
}
@@ -252,22 +267,18 @@ 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 = array();
$paths = array();
$prefixes = [];
$paths = [];
/** @var \DOMElement $n */
foreach ($node->getElementsByTagNameNS(self::NAMESPACE_URI, '*') as $n) {
if ($node !== $n->parentNode) {
continue;
@@ -292,7 +303,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);
@@ -304,29 +315,35 @@ class XmlFileLoader extends FileLoader
if ($controller = $node->getAttribute('controller')) {
if (isset($defaults['_controller'])) {
$name = $node->hasAttribute('id') ? sprintf('"%s"', $node->getAttribute('id')) : sprintf('the "%s" tag', $node->tagName);
$name = $node->hasAttribute('id') ? sprintf('"%s".', $node->getAttribute('id')) : sprintf('the "%s" tag.', $node->tagName);
throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "controller" attribute and the defaults key "_controller" for %s.', $path, $name));
throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "controller" attribute and the defaults key "_controller" for ', $path).$name);
}
$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, $paths, $prefixes);
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
@@ -353,17 +370,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
* @return array|bool|float|int|string|null 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) {
@@ -376,7 +390,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) {
@@ -392,7 +406,7 @@ class XmlFileLoader extends FileLoader
return $list;
case 'map':
$map = array();
$map = [];
foreach ($node->childNodes as $element) {
if (!$element instanceof \DOMElement) {
@@ -412,7 +426,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

@@ -15,6 +15,7 @@ use Symfony\Component\Config\Loader\FileLoader;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Routing\Route;
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;
@@ -27,9 +28,9 @@ use Symfony\Component\Yaml\Yaml;
*/
class YamlFileLoader extends FileLoader
{
private static $availableKeys = array(
'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', 'controller', 'name_prefix', 'trailing_slash_on_root',
);
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',
];
private $yamlParser;
/**
@@ -61,7 +62,7 @@ class YamlFileLoader extends FileLoader
try {
$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);
throw new \InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML: ', $path).$e->getMessage(), 0, $e);
}
$collection = new RouteCollection();
@@ -95,36 +96,44 @@ 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();
$host = isset($config['host']) ? $config['host'] : '';
$schemes = isset($config['schemes']) ? $config['schemes'] : array();
$methods = isset($config['methods']) ? $config['methods'] : array();
$condition = isset($config['condition']) ? $config['condition'] : null;
$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);
@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'];
}
if (\is_array($config['path'])) {
$route = new Route('', $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
@@ -132,6 +141,7 @@ class YamlFileLoader extends FileLoader
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);
@@ -145,34 +155,43 @@ class YamlFileLoader extends FileLoader
/**
* 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();
$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;
$type = $config['type'] ?? null;
$prefix = $config['prefix'] ?? '';
$defaults = $config['defaults'] ?? [];
$requirements = $config['requirements'] ?? [];
$options = $config['options'] ?? [];
$host = $config['host'] ?? null;
$condition = $config['condition'] ?? null;
$schemes = $config['schemes'] ?? null;
$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) {
@@ -197,6 +216,7 @@ class YamlFileLoader extends FileLoader
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);
@@ -249,8 +269,8 @@ class YamlFileLoader extends FileLoader
if (!\is_array($config)) {
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)));
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)));
}
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));

View File

@@ -52,22 +52,30 @@
<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: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">

View File

@@ -0,0 +1,31 @@
<?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\Matcher;
use Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherTrait;
use Symfony\Component\Routing\RequestContext;
/**
* Matches URLs based on rules dumped by CompiledUrlMatcherDumper.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class CompiledUrlMatcher extends UrlMatcher
{
use CompiledUrlMatcherTrait;
public function __construct(array $compiledRoutes, RequestContext $context)
{
$this->context = $context;
[$this->matchHost, $this->staticRoutes, $this->regexpList, $this->dynamicRoutes, $this->checkCondition] = $compiledRoutes;
}
}

View File

@@ -0,0 +1,501 @@
<?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\Matcher\Dumper;
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* CompiledUrlMatcherDumper creates PHP arrays to be used with CompiledUrlMatcher.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Tobias Schultze <http://tobion.de>
* @author Arnaud Le Blanc <arnaud.lb@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class CompiledUrlMatcherDumper extends MatcherDumper
{
private $expressionLanguage;
private $signalingException;
/**
* @var ExpressionFunctionProviderInterface[]
*/
private $expressionLanguageProviders = [];
/**
* {@inheritdoc}
*/
public function dump(array $options = [])
{
return <<<EOF
<?php
/**
* This file has been auto-generated
* by the Symfony Routing Component.
*/
return [
{$this->generateCompiledRoutes()}];
EOF;
}
public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider)
{
$this->expressionLanguageProviders[] = $provider;
}
/**
* Generates the arrays for CompiledUrlMatcher's constructor.
*/
public function getCompiledRoutes(bool $forDump = false): array
{
// Group hosts by same-suffix, re-order when possible
$matchHost = false;
$routes = new StaticPrefixCollection();
foreach ($this->getRoutes()->all() as $name => $route) {
if ($host = $route->getHost()) {
$matchHost = true;
$host = '/'.strtr(strrev($host), '}.{', '(/)');
}
$routes->addRoute($host ?: '/(.*)', [$name, $route]);
}
if ($matchHost) {
$compiledRoutes = [true];
$routes = $routes->populateCollection(new RouteCollection());
} else {
$compiledRoutes = [false];
$routes = $this->getRoutes();
}
[$staticRoutes, $dynamicRoutes] = $this->groupStaticRoutes($routes);
$conditions = [null];
$compiledRoutes[] = $this->compileStaticRoutes($staticRoutes, $conditions);
$chunkLimit = \count($dynamicRoutes);
while (true) {
try {
$this->signalingException = new \RuntimeException('Compilation failed: regular expression is too large');
$compiledRoutes = array_merge($compiledRoutes, $this->compileDynamicRoutes($dynamicRoutes, $matchHost, $chunkLimit, $conditions));
break;
} catch (\Exception $e) {
if (1 < $chunkLimit && $this->signalingException === $e) {
$chunkLimit = 1 + ($chunkLimit >> 1);
continue;
}
throw $e;
}
}
if ($forDump) {
$compiledRoutes[2] = $compiledRoutes[4];
}
unset($conditions[0]);
if ($conditions) {
foreach ($conditions as $expression => $condition) {
$conditions[$expression] = "case {$condition}: return {$expression};";
}
$checkConditionCode = <<<EOF
static function (\$condition, \$context, \$request) { // \$checkCondition
switch (\$condition) {
{$this->indent(implode("\n", $conditions), 3)}
}
}
EOF;
$compiledRoutes[4] = $forDump ? $checkConditionCode.",\n" : eval('return '.$checkConditionCode.';');
} else {
$compiledRoutes[4] = $forDump ? " null, // \$checkCondition\n" : null;
}
return $compiledRoutes;
}
private function generateCompiledRoutes(): string
{
[$matchHost, $staticRoutes, $regexpCode, $dynamicRoutes, $checkConditionCode] = $this->getCompiledRoutes(true);
$code = self::export($matchHost).', // $matchHost'."\n";
$code .= '[ // $staticRoutes'."\n";
foreach ($staticRoutes as $path => $routes) {
$code .= sprintf(" %s => [\n", self::export($path));
foreach ($routes as $route) {
$code .= sprintf(" [%s, %s, %s, %s, %s, %s, %s],\n", ...array_map([__CLASS__, 'export'], $route));
}
$code .= " ],\n";
}
$code .= "],\n";
$code .= sprintf("[ // \$regexpList%s\n],\n", $regexpCode);
$code .= '[ // $dynamicRoutes'."\n";
foreach ($dynamicRoutes as $path => $routes) {
$code .= sprintf(" %s => [\n", self::export($path));
foreach ($routes as $route) {
$code .= sprintf(" [%s, %s, %s, %s, %s, %s, %s],\n", ...array_map([__CLASS__, 'export'], $route));
}
$code .= " ],\n";
}
$code .= "],\n";
$code = preg_replace('/ => \[\n (\[.+?),\n \],/', ' => [$1],', $code);
return $this->indent($code, 1).$checkConditionCode;
}
/**
* Splits static routes from dynamic routes, so that they can be matched first, using a simple switch.
*/
private function groupStaticRoutes(RouteCollection $collection): array
{
$staticRoutes = $dynamicRegex = [];
$dynamicRoutes = new RouteCollection();
foreach ($collection->all() as $name => $route) {
$compiledRoute = $route->compile();
$staticPrefix = rtrim($compiledRoute->getStaticPrefix(), '/');
$hostRegex = $compiledRoute->getHostRegex();
$regex = $compiledRoute->getRegex();
if ($hasTrailingSlash = '/' !== $route->getPath()) {
$pos = strrpos($regex, '$');
$hasTrailingSlash = '/' === $regex[$pos - 1];
$regex = substr_replace($regex, '/?$', $pos - $hasTrailingSlash, 1 + $hasTrailingSlash);
}
if (!$compiledRoute->getPathVariables()) {
$host = !$compiledRoute->getHostVariables() ? $route->getHost() : '';
$url = $route->getPath();
if ($hasTrailingSlash) {
$url = substr($url, 0, -1);
}
foreach ($dynamicRegex as [$hostRx, $rx, $prefix]) {
if (('' === $prefix || str_starts_with($url, $prefix)) && (preg_match($rx, $url) || preg_match($rx, $url.'/')) && (!$host || !$hostRx || preg_match($hostRx, $host))) {
$dynamicRegex[] = [$hostRegex, $regex, $staticPrefix];
$dynamicRoutes->add($name, $route);
continue 2;
}
}
$staticRoutes[$url][$name] = [$route, $hasTrailingSlash];
} else {
$dynamicRegex[] = [$hostRegex, $regex, $staticPrefix];
$dynamicRoutes->add($name, $route);
}
}
return [$staticRoutes, $dynamicRoutes];
}
/**
* Compiles static routes in a switch statement.
*
* Condition-less paths are put in a static array in the switch's default, with generic matching logic.
* Paths that can match two or more routes, or have user-specified conditions are put in separate switch's cases.
*
* @throws \LogicException
*/
private function compileStaticRoutes(array $staticRoutes, array &$conditions): array
{
if (!$staticRoutes) {
return [];
}
$compiledRoutes = [];
foreach ($staticRoutes as $url => $routes) {
$compiledRoutes[$url] = [];
foreach ($routes as $name => [$route, $hasTrailingSlash]) {
$compiledRoutes[$url][] = $this->compileRoute($route, $name, (!$route->compile()->getHostVariables() ? $route->getHost() : $route->compile()->getHostRegex()) ?: null, $hasTrailingSlash, false, $conditions);
}
}
return $compiledRoutes;
}
/**
* Compiles a regular expression followed by a switch statement to match dynamic routes.
*
* The regular expression matches both the host and the pathinfo at the same time. For stellar performance,
* it is built as a tree of patterns, with re-ordering logic to group same-prefix routes together when possible.
*
* Patterns are named so that we know which one matched (https://pcre.org/current/doc/html/pcre2syntax.html#SEC23).
* This name is used to "switch" to the additional logic required to match the final route.
*
* Condition-less paths are put in a static array in the switch's default, with generic matching logic.
* Paths that can match two or more routes, or have user-specified conditions are put in separate switch's cases.
*
* Last but not least:
* - Because it is not possible to mix unicode/non-unicode patterns in a single regexp, several of them can be generated.
* - The same regexp can be used several times when the logic in the switch rejects the match. When this happens, the
* matching-but-failing subpattern is excluded by replacing its name by "(*F)", which forces a failure-to-match.
* To ease this backlisting operation, the name of subpatterns is also the string offset where the replacement should occur.
*/
private function compileDynamicRoutes(RouteCollection $collection, bool $matchHost, int $chunkLimit, array &$conditions): array
{
if (!$collection->all()) {
return [[], [], ''];
}
$regexpList = [];
$code = '';
$state = (object) [
'regexMark' => 0,
'regex' => [],
'routes' => [],
'mark' => 0,
'markTail' => 0,
'hostVars' => [],
'vars' => [],
];
$state->getVars = static function ($m) use ($state) {
if ('_route' === $m[1]) {
return '?:';
}
$state->vars[] = $m[1];
return '';
};
$chunkSize = 0;
$prev = null;
$perModifiers = [];
foreach ($collection->all() as $name => $route) {
preg_match('#[a-zA-Z]*$#', $route->compile()->getRegex(), $rx);
if ($chunkLimit < ++$chunkSize || $prev !== $rx[0] && $route->compile()->getPathVariables()) {
$chunkSize = 1;
$routes = new RouteCollection();
$perModifiers[] = [$rx[0], $routes];
$prev = $rx[0];
}
$routes->add($name, $route);
}
foreach ($perModifiers as [$modifiers, $routes]) {
$prev = false;
$perHost = [];
foreach ($routes->all() as $name => $route) {
$regex = $route->compile()->getHostRegex();
if ($prev !== $regex) {
$routes = new RouteCollection();
$perHost[] = [$regex, $routes];
$prev = $regex;
}
$routes->add($name, $route);
}
$prev = false;
$rx = '{^(?';
$code .= "\n {$state->mark} => ".self::export($rx);
$startingMark = $state->mark;
$state->mark += \strlen($rx);
$state->regex = $rx;
foreach ($perHost as [$hostRegex, $routes]) {
if ($matchHost) {
if ($hostRegex) {
preg_match('#^.\^(.*)\$.[a-zA-Z]*$#', $hostRegex, $rx);
$state->vars = [];
$hostRegex = '(?i:'.preg_replace_callback('#\?P<([^>]++)>#', $state->getVars, $rx[1]).')\.';
$state->hostVars = $state->vars;
} else {
$hostRegex = '(?:(?:[^./]*+\.)++)';
$state->hostVars = [];
}
$state->mark += \strlen($rx = ($prev ? ')' : '')."|{$hostRegex}(?");
$code .= "\n .".self::export($rx);
$state->regex .= $rx;
$prev = true;
}
$tree = new StaticPrefixCollection();
foreach ($routes->all() as $name => $route) {
preg_match('#^.\^(.*)\$.[a-zA-Z]*$#', $route->compile()->getRegex(), $rx);
$state->vars = [];
$regex = preg_replace_callback('#\?P<([^>]++)>#', $state->getVars, $rx[1]);
if ($hasTrailingSlash = '/' !== $regex && '/' === $regex[-1]) {
$regex = substr($regex, 0, -1);
}
$hasTrailingVar = (bool) preg_match('#\{\w+\}/?$#', $route->getPath());
$tree->addRoute($regex, [$name, $regex, $state->vars, $route, $hasTrailingSlash, $hasTrailingVar]);
}
$code .= $this->compileStaticPrefixCollection($tree, $state, 0, $conditions);
}
if ($matchHost) {
$code .= "\n .')'";
$state->regex .= ')';
}
$rx = ")/?$}{$modifiers}";
$code .= "\n .'{$rx}',";
$state->regex .= $rx;
$state->markTail = 0;
// if the regex is too large, throw a signaling exception to recompute with smaller chunk size
set_error_handler(function ($type, $message) { throw str_contains($message, $this->signalingException->getMessage()) ? $this->signalingException : new \ErrorException($message); });
try {
preg_match($state->regex, '');
} finally {
restore_error_handler();
}
$regexpList[$startingMark] = $state->regex;
}
$state->routes[$state->mark][] = [null, null, null, null, false, false, 0];
unset($state->getVars);
return [$regexpList, $state->routes, $code];
}
/**
* Compiles a regexp tree of subpatterns that matches nested same-prefix routes.
*
* @param \stdClass $state A simple state object that keeps track of the progress of the compilation,
* and gathers the generated switch's "case" and "default" statements
*/
private function compileStaticPrefixCollection(StaticPrefixCollection $tree, \stdClass $state, int $prefixLen, array &$conditions): string
{
$code = '';
$prevRegex = null;
$routes = $tree->getRoutes();
foreach ($routes as $i => $route) {
if ($route instanceof StaticPrefixCollection) {
$prevRegex = null;
$prefix = substr($route->getPrefix(), $prefixLen);
$state->mark += \strlen($rx = "|{$prefix}(?");
$code .= "\n .".self::export($rx);
$state->regex .= $rx;
$code .= $this->indent($this->compileStaticPrefixCollection($route, $state, $prefixLen + \strlen($prefix), $conditions));
$code .= "\n .')'";
$state->regex .= ')';
++$state->markTail;
continue;
}
[$name, $regex, $vars, $route, $hasTrailingSlash, $hasTrailingVar] = $route;
$compiledRoute = $route->compile();
$vars = array_merge($state->hostVars, $vars);
if ($compiledRoute->getRegex() === $prevRegex) {
$state->routes[$state->mark][] = $this->compileRoute($route, $name, $vars, $hasTrailingSlash, $hasTrailingVar, $conditions);
continue;
}
$state->mark += 3 + $state->markTail + \strlen($regex) - $prefixLen;
$state->markTail = 2 + \strlen($state->mark);
$rx = sprintf('|%s(*:%s)', substr($regex, $prefixLen), $state->mark);
$code .= "\n .".self::export($rx);
$state->regex .= $rx;
$prevRegex = $compiledRoute->getRegex();
$state->routes[$state->mark] = [$this->compileRoute($route, $name, $vars, $hasTrailingSlash, $hasTrailingVar, $conditions)];
}
return $code;
}
/**
* Compiles a single Route to PHP code used to match it against the path info.
*/
private function compileRoute(Route $route, string $name, $vars, bool $hasTrailingSlash, bool $hasTrailingVar, array &$conditions): array
{
$defaults = $route->getDefaults();
if (isset($defaults['_canonical_route'])) {
$name = $defaults['_canonical_route'];
unset($defaults['_canonical_route']);
}
if ($condition = $route->getCondition()) {
$condition = $this->getExpressionLanguage()->compile($condition, ['context', 'request']);
$condition = $conditions[$condition] ?? $conditions[$condition] = (str_contains($condition, '$request') ? 1 : -1) * \count($conditions);
} else {
$condition = null;
}
return [
['_route' => $name] + $defaults,
$vars,
array_flip($route->getMethods()) ?: null,
array_flip($route->getSchemes()) ?: null,
$hasTrailingSlash,
$hasTrailingVar,
$condition,
];
}
private function getExpressionLanguage(): ExpressionLanguage
{
if (null === $this->expressionLanguage) {
if (!class_exists(ExpressionLanguage::class)) {
throw new \LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
}
$this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders);
}
return $this->expressionLanguage;
}
private function indent(string $code, int $level = 1): string
{
return preg_replace('/^./m', str_repeat(' ', $level).'$0', $code);
}
/**
* @internal
*/
public static function export($value): string
{
if (null === $value) {
return 'null';
}
if (!\is_array($value)) {
if (\is_object($value)) {
throw new \InvalidArgumentException('Symfony\Component\Routing\Route cannot contain objects.');
}
return str_replace("\n", '\'."\n".\'', var_export($value, true));
}
if (!$value) {
return '[]';
}
$i = 0;
$export = '[';
foreach ($value as $k => $v) {
if ($i === $k) {
++$i;
} else {
$export .= self::export($k).' => ';
if (\is_int($k) && $i < $k) {
$i = 1 + $k;
}
}
$export .= self::export($v).', ';
}
return substr_replace($export, ']', -2);
}
}

View File

@@ -15,23 +15,26 @@ use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\NoConfigurationException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface;
use Symfony\Component\Routing\RequestContext;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*
* @property RequestContext $context
*/
trait PhpMatcherTrait
trait CompiledUrlMatcherTrait
{
private $matchHost = false;
private $staticRoutes = array();
private $regexpList = array();
private $dynamicRoutes = array();
private $staticRoutes = [];
private $regexpList = [];
private $dynamicRoutes = [];
private $checkCondition;
public function match($pathinfo)
public function match($pathinfo): array
{
$allow = $allowSchemes = array();
$allow = $allowSchemes = [];
if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) {
return $ret;
}
@@ -39,9 +42,9 @@ trait PhpMatcherTrait
throw new MethodNotAllowedException(array_keys($allow));
}
if (!$this instanceof RedirectableUrlMatcherInterface) {
throw new ResourceNotFoundException();
throw new ResourceNotFoundException(sprintf('No routes found for "%s".', $pathinfo));
}
if (!\in_array($this->context->getMethod(), array('HEAD', 'GET'), true)) {
if (!\in_array($this->context->getMethod(), ['HEAD', 'GET'], true)) {
// no-op
} elseif ($allowSchemes) {
redirect_scheme:
@@ -54,8 +57,8 @@ trait PhpMatcherTrait
} finally {
$this->context->setScheme($scheme);
}
} elseif ('/' !== $pathinfo) {
$pathinfo = '/' !== $pathinfo[-1] ? $pathinfo.'/' : substr($pathinfo, 0, -1);
} elseif ('/' !== $trimmedPathinfo = rtrim($pathinfo, '/') ?: '/') {
$pathinfo = $trimmedPathinfo === $pathinfo ? $pathinfo.'/' : $trimmedPathinfo;
if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) {
return $this->redirect($pathinfo, $ret['_route']) + $ret;
}
@@ -64,16 +67,16 @@ trait PhpMatcherTrait
}
}
throw new ResourceNotFoundException();
throw new ResourceNotFoundException(sprintf('No routes found for "%s".', $pathinfo));
}
private function doMatch(string $rawPathinfo, array &$allow = array(), array &$allowSchemes = array()): ?array
private function doMatch(string $pathinfo, array &$allow = [], array &$allowSchemes = []): array
{
$allow = $allowSchemes = array();
$pathinfo = rawurldecode($rawPathinfo) ?: '/';
$allow = $allowSchemes = [];
$pathinfo = rawurldecode($pathinfo) ?: '/';
$trimmedPathinfo = rtrim($pathinfo, '/') ?: '/';
$context = $this->context;
$requestMethod = $canonicalMethod = $context->getMethod();
$trimmedPathinfo = '/' !== $pathinfo && '/' === $pathinfo[-1] ? substr($pathinfo, 0, -1) : $pathinfo;
if ($this->matchHost) {
$host = strtolower($context->getHost());
@@ -82,20 +85,13 @@ trait PhpMatcherTrait
if ('HEAD' === $requestMethod) {
$canonicalMethod = 'GET';
}
$supportsRedirections = 'GET' === $canonicalMethod && $this instanceof RedirectableUrlMatcherInterface;
foreach ($this->staticRoutes[$trimmedPathinfo] ?? array() as list($ret, $requiredHost, $requiredMethods, $requiredSchemes, $hasTrailingSlash, $condition)) {
foreach ($this->staticRoutes[$trimmedPathinfo] ?? [] as [$ret, $requiredHost, $requiredMethods, $requiredSchemes, $hasTrailingSlash, , $condition]) {
if ($condition && !($this->checkCondition)($condition, $context, 0 < $condition ? $request ?? $request = $this->request ?: $this->createRequest($pathinfo) : null)) {
continue;
}
if ('/' === $pathinfo || $hasTrailingSlash === ('/' === $pathinfo[-1])) {
// no-op
} elseif ($this instanceof RedirectableUrlMatcherInterface) {
return null;
} else {
continue;
}
if ($requiredHost) {
if ('#' !== $requiredHost[0] ? $requiredHost !== $host : !preg_match($requiredHost, $host, $hostMatches)) {
continue;
@@ -106,13 +102,19 @@ trait PhpMatcherTrait
}
}
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
if ($hasRequiredScheme) {
$allow += $requiredMethods;
if ('/' !== $pathinfo && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) {
if ($supportsRedirections && (!$requiredMethods || isset($requiredMethods['GET']))) {
return $allow = $allowSchemes = [];
}
continue;
}
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($hasRequiredScheme && $requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
$allow += $requiredMethods;
continue;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
continue;
@@ -125,16 +127,30 @@ trait PhpMatcherTrait
foreach ($this->regexpList as $offset => $regex) {
while (preg_match($regex, $matchedPathinfo, $matches)) {
foreach ($this->dynamicRoutes[$m = (int) $matches['MARK']] as list($ret, $vars, $requiredMethods, $requiredSchemes, $hasTrailingSlash, $condition)) {
if ($condition && !($this->checkCondition)($condition, $context, 0 < $condition ? $request ?? $request = $this->request ?: $this->createRequest($pathinfo) : null)) {
continue;
foreach ($this->dynamicRoutes[$m = (int) $matches['MARK']] as [$ret, $vars, $requiredMethods, $requiredSchemes, $hasTrailingSlash, $hasTrailingVar, $condition]) {
if (null !== $condition) {
if (0 === $condition) { // marks the last route in the regexp
continue 3;
}
if (!($this->checkCondition)($condition, $context, 0 < $condition ? $request ?? $request = $this->request ?: $this->createRequest($pathinfo) : null)) {
continue;
}
}
if ('/' === $pathinfo || (!$hasTrailingSlash ? '/' !== $pathinfo[-1] || !preg_match($regex, substr($pathinfo, 0, -1), $n) || $m !== (int) $n['MARK'] : '/' === $pathinfo[-1])) {
// no-op
} elseif ($this instanceof RedirectableUrlMatcherInterface) {
return null;
} else {
$hasTrailingVar = $trimmedPathinfo !== $pathinfo && $hasTrailingVar;
if ($hasTrailingVar && ($hasTrailingSlash || (null === $n = $matches[\count($vars)] ?? null) || '/' !== ($n[-1] ?? '/')) && preg_match($regex, $this->matchHost ? $host.'.'.$trimmedPathinfo : $trimmedPathinfo, $n) && $m === (int) $n['MARK']) {
if ($hasTrailingSlash) {
$matches = $n;
} else {
$hasTrailingVar = false;
}
}
if ('/' !== $pathinfo && !$hasTrailingVar && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) {
if ($supportsRedirections && (!$requiredMethods || isset($requiredMethods['GET']))) {
return $allow = $allowSchemes = [];
}
continue;
}
@@ -144,15 +160,13 @@ trait PhpMatcherTrait
}
}
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
if ($hasRequiredScheme) {
$allow += $requiredMethods;
}
if ($requiredSchemes && !isset($requiredSchemes[$context->getScheme()])) {
$allowSchemes += $requiredSchemes;
continue;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
$allow += $requiredMethods;
continue;
}
@@ -168,6 +182,6 @@ trait PhpMatcherTrait
throw new NoConfigurationException();
}
return null;
return [];
}
}

View File

@@ -24,11 +24,9 @@ interface MatcherDumperInterface
* Dumps a set of routes to a string representation of executable code
* that can then be used to match a request against these routes.
*
* @param array $options An array of options
*
* @return string Executable code
*/
public function dump(array $options = array());
public function dump(array $options = []);
/**
* Gets the routes to dump.

View File

@@ -11,10 +11,7 @@
namespace Symfony\Component\Routing\Matcher\Dumper;
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "CompiledUrlMatcherDumper" instead.', PhpMatcherDumper::class), \E_USER_DEPRECATED);
/**
* PhpMatcherDumper creates a PHP class able to match URLs for a given set of routes.
@@ -23,17 +20,11 @@ use Symfony\Component\Routing\RouteCollection;
* @author Tobias Schultze <http://tobion.de>
* @author Arnaud Le Blanc <arnaud.lb@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*
* @deprecated since Symfony 4.3, use CompiledUrlMatcherDumper instead.
*/
class PhpMatcherDumper extends MatcherDumper
class PhpMatcherDumper extends CompiledUrlMatcherDumper
{
private $expressionLanguage;
private $signalingException;
/**
* @var ExpressionFunctionProviderInterface[]
*/
private $expressionLanguageProviders = array();
/**
* Dumps a set of routes to a PHP class.
*
@@ -46,20 +37,24 @@ class PhpMatcherDumper extends MatcherDumper
*
* @return string A PHP class representing the matcher class
*/
public function dump(array $options = array())
public function dump(array $options = [])
{
$options = array_replace(array(
$options = array_replace([
'class' => 'ProjectUrlMatcher',
'base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher',
), $options);
], $options);
// trailing slash support is only enabled if we know how to redirect the user
$interfaces = class_implements($options['base_class']);
$code = parent::dump();
$code = preg_replace('#\n ([^ ].*?) // \$(\w++)$#m', "\n \$this->$2 = $1", $code);
$code = str_replace(",\n $", ";\n $", $code);
$code = substr($code, strpos($code, '$this') - 4, -5).";\n";
$code = preg_replace('/^ \$this->\w++ = (?:null|false|\[\n \]);\n/m', '', $code);
$code = str_replace("\n ", "\n ", "\n".$code);
return <<<EOF
<?php
use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherTrait;
use Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherTrait;
use Symfony\Component\Routing\RequestContext;
/**
@@ -68,422 +63,13 @@ use Symfony\Component\Routing\RequestContext;
*/
class {$options['class']} extends {$options['base_class']}
{
use PhpMatcherTrait;
use CompiledUrlMatcherTrait;
public function __construct(RequestContext \$context)
{
\$this->context = \$context;
{$this->generateProperties()} }
\$this->context = \$context;{$code} }
}
EOF;
}
public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider)
{
$this->expressionLanguageProviders[] = $provider;
}
/**
* Generates the code for the match method implementing UrlMatcherInterface.
*/
private function generateProperties(): string
{
// Group hosts by same-suffix, re-order when possible
$matchHost = false;
$routes = new StaticPrefixCollection();
foreach ($this->getRoutes()->all() as $name => $route) {
if ($host = $route->getHost()) {
$matchHost = true;
$host = '/'.strtr(strrev($host), '}.{', '(/)');
}
$routes->addRoute($host ?: '/(.*)', array($name, $route));
}
if ($matchHost) {
$code = '$this->matchHost = true;'."\n";
$routes = $routes->populateCollection(new RouteCollection());
} else {
$code = '';
$routes = $this->getRoutes();
}
list($staticRoutes, $dynamicRoutes) = $this->groupStaticRoutes($routes);
$conditions = array(null);
$code .= $this->compileStaticRoutes($staticRoutes, $conditions);
$chunkLimit = \count($dynamicRoutes);
while (true) {
try {
$this->signalingException = new \RuntimeException('preg_match(): Compilation failed: regular expression is too large');
$code .= $this->compileDynamicRoutes($dynamicRoutes, $matchHost, $chunkLimit, $conditions);
break;
} catch (\Exception $e) {
if (1 < $chunkLimit && $this->signalingException === $e) {
$chunkLimit = 1 + ($chunkLimit >> 1);
continue;
}
throw $e;
}
}
unset($conditions[0]);
if (!$conditions) {
return $this->indent($code, 2);
}
foreach ($conditions as $expression => $condition) {
$conditions[$expression] = "case {$condition}: return {$expression};";
}
return $this->indent($code, 2).<<<EOF
\$this->checkCondition = static function (\$condition, \$context, \$request) {
switch (\$condition) {
{$this->indent(implode("\n", $conditions), 4)}
}
};
EOF;
}
/**
* Splits static routes from dynamic routes, so that they can be matched first, using a simple switch.
*/
private function groupStaticRoutes(RouteCollection $collection): array
{
$staticRoutes = $dynamicRegex = array();
$dynamicRoutes = new RouteCollection();
foreach ($collection->all() as $name => $route) {
$compiledRoute = $route->compile();
$hostRegex = $compiledRoute->getHostRegex();
$regex = $compiledRoute->getRegex();
if ($hasTrailingSlash = '/' !== $route->getPath()) {
$pos = strrpos($regex, '$');
$hasTrailingSlash = '/' === $regex[$pos - 1];
$regex = substr_replace($regex, '/?$', $pos - $hasTrailingSlash, 1 + $hasTrailingSlash);
}
if (!$compiledRoute->getPathVariables()) {
$host = !$compiledRoute->getHostVariables() ? $route->getHost() : '';
$url = $route->getPath();
if ($hasTrailingSlash) {
$url = substr($url, 0, -1);
}
foreach ($dynamicRegex as list($hostRx, $rx)) {
if (preg_match($rx, $url) && (!$host || !$hostRx || preg_match($hostRx, $host))) {
$dynamicRegex[] = array($hostRegex, $regex);
$dynamicRoutes->add($name, $route);
continue 2;
}
}
$staticRoutes[$url][$name] = array($route, $hasTrailingSlash);
} else {
$dynamicRegex[] = array($hostRegex, $regex);
$dynamicRoutes->add($name, $route);
}
}
return array($staticRoutes, $dynamicRoutes);
}
/**
* Compiles static routes in a switch statement.
*
* Condition-less paths are put in a static array in the switch's default, with generic matching logic.
* Paths that can match two or more routes, or have user-specified conditions are put in separate switch's cases.
*
* @throws \LogicException
*/
private function compileStaticRoutes(array $staticRoutes, array &$conditions): string
{
if (!$staticRoutes) {
return '';
}
$code = '';
foreach ($staticRoutes as $url => $routes) {
$code .= self::export($url)." => array(\n";
foreach ($routes as $name => list($route, $hasTrailingSlash)) {
$code .= $this->compileRoute($route, $name, !$route->compile()->getHostVariables() ? $route->getHost() : $route->compile()->getHostRegex() ?: null, $hasTrailingSlash, $conditions);
}
$code .= "),\n";
}
if ($code) {
return "\$this->staticRoutes = array(\n{$this->indent($code, 1)});\n";
}
return $code;
}
/**
* Compiles a regular expression followed by a switch statement to match dynamic routes.
*
* The regular expression matches both the host and the pathinfo at the same time. For stellar performance,
* it is built as a tree of patterns, with re-ordering logic to group same-prefix routes together when possible.
*
* Patterns are named so that we know which one matched (https://pcre.org/current/doc/html/pcre2syntax.html#SEC23).
* This name is used to "switch" to the additional logic required to match the final route.
*
* Condition-less paths are put in a static array in the switch's default, with generic matching logic.
* Paths that can match two or more routes, or have user-specified conditions are put in separate switch's cases.
*
* Last but not least:
* - Because it is not possibe to mix unicode/non-unicode patterns in a single regexp, several of them can be generated.
* - The same regexp can be used several times when the logic in the switch rejects the match. When this happens, the
* matching-but-failing subpattern is blacklisted by replacing its name by "(*F)", which forces a failure-to-match.
* To ease this backlisting operation, the name of subpatterns is also the string offset where the replacement should occur.
*/
private function compileDynamicRoutes(RouteCollection $collection, bool $matchHost, int $chunkLimit, array &$conditions): string
{
if (!$collection->all()) {
return '';
}
$code = '';
$state = (object) array(
'regex' => '',
'routes' => '',
'mark' => 0,
'markTail' => 0,
'hostVars' => array(),
'vars' => array(),
);
$state->getVars = static function ($m) use ($state) {
if ('_route' === $m[1]) {
return '?:';
}
$state->vars[] = $m[1];
return '';
};
$chunkSize = 0;
$prev = null;
$perModifiers = array();
foreach ($collection->all() as $name => $route) {
preg_match('#[a-zA-Z]*$#', $route->compile()->getRegex(), $rx);
if ($chunkLimit < ++$chunkSize || $prev !== $rx[0] && $route->compile()->getPathVariables()) {
$chunkSize = 1;
$routes = new RouteCollection();
$perModifiers[] = array($rx[0], $routes);
$prev = $rx[0];
}
$routes->add($name, $route);
}
foreach ($perModifiers as list($modifiers, $routes)) {
$prev = false;
$perHost = array();
foreach ($routes->all() as $name => $route) {
$regex = $route->compile()->getHostRegex();
if ($prev !== $regex) {
$routes = new RouteCollection();
$perHost[] = array($regex, $routes);
$prev = $regex;
}
$routes->add($name, $route);
}
$prev = false;
$rx = '{^(?';
$code .= "\n {$state->mark} => ".self::export($rx);
$state->mark += \strlen($rx);
$state->regex = $rx;
foreach ($perHost as list($hostRegex, $routes)) {
if ($matchHost) {
if ($hostRegex) {
preg_match('#^.\^(.*)\$.[a-zA-Z]*$#', $hostRegex, $rx);
$state->vars = array();
$hostRegex = '(?i:'.preg_replace_callback('#\?P<([^>]++)>#', $state->getVars, $rx[1]).')\.';
$state->hostVars = $state->vars;
} else {
$hostRegex = '(?:(?:[^./]*+\.)++)';
$state->hostVars = array();
}
$state->mark += \strlen($rx = ($prev ? ')' : '')."|{$hostRegex}(?");
$code .= "\n .".self::export($rx);
$state->regex .= $rx;
$prev = true;
}
$tree = new StaticPrefixCollection();
foreach ($routes->all() as $name => $route) {
preg_match('#^.\^(.*)\$.[a-zA-Z]*$#', $route->compile()->getRegex(), $rx);
$state->vars = array();
$regex = preg_replace_callback('#\?P<([^>]++)>#', $state->getVars, $rx[1]);
if ($hasTrailingSlash = '/' !== $regex && '/' === $regex[-1]) {
$regex = substr($regex, 0, -1);
}
$tree->addRoute($regex, array($name, $regex, $state->vars, $route, $hasTrailingSlash));
}
$code .= $this->compileStaticPrefixCollection($tree, $state, 0, $conditions);
}
if ($matchHost) {
$code .= "\n .')'";
$state->regex .= ')';
}
$rx = ")(?:/?)$}{$modifiers}";
$code .= "\n .'{$rx}',";
$state->regex .= $rx;
$state->markTail = 0;
// if the regex is too large, throw a signaling exception to recompute with smaller chunk size
set_error_handler(function ($type, $message) { throw 0 === strpos($message, $this->signalingException->getMessage()) ? $this->signalingException : new \ErrorException($message); });
try {
preg_match($state->regex, '');
} finally {
restore_error_handler();
}
}
unset($state->getVars);
return "\$this->regexpList = array({$code}\n);\n"
."\$this->dynamicRoutes = array(\n{$this->indent($state->routes, 1)});\n";
}
/**
* Compiles a regexp tree of subpatterns that matches nested same-prefix routes.
*
* @param \stdClass $state A simple state object that keeps track of the progress of the compilation,
* and gathers the generated switch's "case" and "default" statements
*/
private function compileStaticPrefixCollection(StaticPrefixCollection $tree, \stdClass $state, int $prefixLen, array &$conditions): string
{
$code = '';
$prevRegex = null;
$routes = $tree->getRoutes();
foreach ($routes as $i => $route) {
if ($route instanceof StaticPrefixCollection) {
$prevRegex = null;
$prefix = substr($route->getPrefix(), $prefixLen);
$state->mark += \strlen($rx = "|{$prefix}(?");
$code .= "\n .".self::export($rx);
$state->regex .= $rx;
$code .= $this->indent($this->compileStaticPrefixCollection($route, $state, $prefixLen + \strlen($prefix), $conditions));
$code .= "\n .')'";
$state->regex .= ')';
++$state->markTail;
continue;
}
list($name, $regex, $vars, $route, $hasTrailingSlash) = $route;
$compiledRoute = $route->compile();
$vars = array_merge($state->hostVars, $vars);
if ($compiledRoute->getRegex() === $prevRegex) {
$state->routes = substr_replace($state->routes, $this->compileRoute($route, $name, $vars, $hasTrailingSlash, $conditions), -3, 0);
continue;
}
$state->mark += 3 + $state->markTail + \strlen($regex) - $prefixLen;
$state->markTail = 2 + \strlen($state->mark);
$rx = sprintf('|%s(*:%s)', substr($regex, $prefixLen), $state->mark);
$code .= "\n .".self::export($rx);
$state->regex .= $rx;
$prevRegex = $compiledRoute->getRegex();
$state->routes .= sprintf("%s => array(\n%s),\n", $state->mark, $this->compileRoute($route, $name, $vars, $hasTrailingSlash, $conditions));
}
return $code;
}
/**
* Compiles a single Route to PHP code used to match it against the path info.
*/
private function compileRoute(Route $route, string $name, $vars, bool $hasTrailingSlash, array &$conditions): string
{
$defaults = $route->getDefaults();
if (isset($defaults['_canonical_route'])) {
$name = $defaults['_canonical_route'];
unset($defaults['_canonical_route']);
}
if ($condition = $route->getCondition()) {
$condition = $this->getExpressionLanguage()->compile($condition, array('context', 'request'));
$condition = $conditions[$condition] ?? $conditions[$condition] = (false !== strpos($condition, '$request') ? 1 : -1) * \count($conditions);
} else {
$condition = 'null';
}
return sprintf(
" array(%s, %s, %s, %s, %s, %s),\n",
self::export(array('_route' => $name) + $defaults),
self::export($vars),
self::export(array_flip($route->getMethods()) ?: null),
self::export(array_flip($route->getSchemes()) ?: null),
self::export($hasTrailingSlash),
$condition
);
}
private function getExpressionLanguage()
{
if (null === $this->expressionLanguage) {
if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
throw new \LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
}
$this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders);
}
return $this->expressionLanguage;
}
private function indent($code, $level = 1)
{
$code = preg_replace('/ => array\(\n (array\(.+),\n\),/', ' => array($1),', $code);
return preg_replace('/^./m', str_repeat(' ', $level).'$0', $code);
}
/**
* @internal
*/
public static function export($value): string
{
if (null === $value) {
return 'null';
}
if (!\is_array($value)) {
if (\is_object($value)) {
throw new \InvalidArgumentException('Symfony\Component\Routing\Route cannot contain objects.');
}
return str_replace("\n", '\'."\n".\'', var_export($value, true));
}
if (!$value) {
return 'array()';
}
$i = 0;
$export = 'array(';
foreach ($value as $k => $v) {
if ($i === $k) {
++$i;
} else {
$export .= self::export($k).' => ';
if (\is_int($k) && $i < $k) {
$i = 1 + $k;
}
}
$export .= self::export($v).', ';
}
return substr_replace($export, ')', -2);
}
}

View File

@@ -28,17 +28,17 @@ class StaticPrefixCollection
/**
* @var string[]
*/
private $staticPrefixes = array();
private $staticPrefixes = [];
/**
* @var string[]
*/
private $prefixes = array();
private $prefixes = [];
/**
* @var array[]|self[]
*/
private $items = array();
private $items = [];
public function __construct(string $prefix = '/')
{
@@ -65,12 +65,12 @@ class StaticPrefixCollection
*/
public function addRoute(string $prefix, $route)
{
list($prefix, $staticPrefix) = $this->getCommonPrefix($prefix, $prefix);
[$prefix, $staticPrefix] = $this->getCommonPrefix($prefix, $prefix);
for ($i = \count($this->items) - 1; 0 <= $i; --$i) {
$item = $this->items[$i];
list($commonPrefix, $commonStaticPrefix) = $this->getCommonPrefix($prefix, $this->prefixes[$i]);
[$commonPrefix, $commonStaticPrefix] = $this->getCommonPrefix($prefix, $this->prefixes[$i]);
if ($this->prefix === $commonPrefix) {
// the new route and a previous one have no common prefix, let's see if they are exclusive to each others
@@ -104,9 +104,9 @@ class StaticPrefixCollection
} else {
// the new route and a previous one have a common prefix, let's merge them
$child = new self($commonPrefix);
list($child->prefixes[0], $child->staticPrefixes[0]) = $child->getCommonPrefix($this->prefixes[$i], $this->prefixes[$i]);
list($child->prefixes[1], $child->staticPrefixes[1]) = $child->getCommonPrefix($prefix, $prefix);
$child->items = array($this->items[$i], $route);
[$child->prefixes[0], $child->staticPrefixes[0]] = $child->getCommonPrefix($this->prefixes[$i], $this->prefixes[$i]);
[$child->prefixes[1], $child->staticPrefixes[1]] = $child->getCommonPrefix($prefix, $prefix);
$child->items = [$this->items[$i], $route];
$this->staticPrefixes[$i] = $commonStaticPrefix;
$this->prefixes[$i] = $commonPrefix;
@@ -149,7 +149,7 @@ class StaticPrefixCollection
$baseLength = \strlen($this->prefix);
$end = min(\strlen($prefix), \strlen($anotherPrefix));
$staticLength = null;
set_error_handler(array(__CLASS__, 'handleError'));
set_error_handler([__CLASS__, 'handleError']);
for ($i = $baseLength; $i < $end && $prefix[$i] === $anotherPrefix[$i]; ++$i) {
if ('(' === $prefix[$i]) {
@@ -192,11 +192,11 @@ class StaticPrefixCollection
} while (0b10 === (\ord($prefix[$i]) >> 6));
}
return array(substr($prefix, 0, $i), substr($prefix, 0, $staticLength ?? $i));
return [substr($prefix, 0, $i), substr($prefix, 0, $staticLength ?? $i)];
}
public static function handleError($type, $msg)
public static function handleError(int $type, string $msg)
{
return 0 === strpos($msg, 'preg_match(): Compilation failed: lookbehind assertion is not fixed length');
return str_contains($msg, 'Compilation failed: lookbehind assertion is not fixed length');
}
}

View File

@@ -27,7 +27,7 @@ abstract class RedirectableUrlMatcher extends UrlMatcher implements Redirectable
try {
return parent::match($pathinfo);
} catch (ResourceNotFoundException $e) {
if (!\in_array($this->context->getMethod(), array('HEAD', 'GET'), true)) {
if (!\in_array($this->context->getMethod(), ['HEAD', 'GET'], true)) {
throw $e;
}
@@ -44,11 +44,11 @@ abstract class RedirectableUrlMatcher extends UrlMatcher implements Redirectable
} finally {
$this->context->setScheme($scheme);
}
} elseif ('/' === $pathinfo) {
} elseif ('/' === $trimmedPathinfo = rtrim($pathinfo, '/') ?: '/') {
throw $e;
} else {
try {
$pathinfo = '/' !== $pathinfo[-1] ? $pathinfo.'/' : substr($pathinfo, 0, -1);
$pathinfo = $trimmedPathinfo === $pathinfo ? $pathinfo.'/' : $trimmedPathinfo;
$ret = parent::match($pathinfo);
return $this->redirect($pathinfo, $ret['_route'] ?? null) + $ret;

View File

@@ -23,15 +23,15 @@ use Symfony\Component\Routing\RouteCollection;
*/
class TraceableUrlMatcher extends UrlMatcher
{
const ROUTE_DOES_NOT_MATCH = 0;
const ROUTE_ALMOST_MATCHES = 1;
const ROUTE_MATCHES = 2;
public const ROUTE_DOES_NOT_MATCH = 0;
public const ROUTE_ALMOST_MATCHES = 1;
public const ROUTE_MATCHES = 2;
protected $traces;
public function getTraces($pathinfo)
{
$this->traces = array();
$this->traces = [];
try {
$this->match($pathinfo);
@@ -52,12 +52,32 @@ class TraceableUrlMatcher extends UrlMatcher
protected function matchCollection($pathinfo, RouteCollection $routes)
{
// HEAD and GET are equivalent as per RFC
if ('HEAD' === $method = $this->context->getMethod()) {
$method = 'GET';
}
$supportsTrailingSlash = 'GET' === $method && $this instanceof RedirectableUrlMatcherInterface;
$trimmedPathinfo = rtrim($pathinfo, '/') ?: '/';
foreach ($routes as $name => $route) {
$compiledRoute = $route->compile();
$staticPrefix = rtrim($compiledRoute->getStaticPrefix(), '/');
$requiredMethods = $route->getMethods();
if (!preg_match($compiledRoute->getRegex(), $pathinfo, $matches)) {
// check the static prefix of the URL first. Only use the more expensive preg_match when it matches
if ('' !== $staticPrefix && !str_starts_with($trimmedPathinfo, $staticPrefix)) {
$this->addTrace(sprintf('Path "%s" does not match', $route->getPath()), self::ROUTE_DOES_NOT_MATCH, $name, $route);
continue;
}
$regex = $compiledRoute->getRegex();
$pos = strrpos($regex, '$');
$hasTrailingSlash = '/' === $regex[$pos - 1];
$regex = substr_replace($regex, '/?$', $pos - $hasTrailingSlash, 1 + $hasTrailingSlash);
if (!preg_match($regex, $pathinfo, $matches)) {
// does it match without any requirements?
$r = new Route($route->getPath(), $route->getDefaults(), array(), $route->getOptions());
$r = new Route($route->getPath(), $route->getDefaults(), [], $route->getOptions());
$cr = $r->compile();
if (!preg_match($cr->getRegex(), $pathinfo)) {
$this->addTrace(sprintf('Path "%s" does not match', $route->getPath()), self::ROUTE_DOES_NOT_MATCH, $name, $route);
@@ -66,7 +86,7 @@ class TraceableUrlMatcher extends UrlMatcher
}
foreach ($route->getRequirements() as $n => $regex) {
$r = new Route($route->getPath(), $route->getDefaults(), array($n => $regex), $route->getOptions());
$r = new Route($route->getPath(), $route->getDefaults(), [$n => $regex], $route->getOptions());
$cr = $r->compile();
if (\in_array($n, $cr->getVariables()) && !preg_match($cr->getRegex(), $pathinfo)) {
@@ -79,63 +99,66 @@ class TraceableUrlMatcher extends UrlMatcher
continue;
}
// check host requirement
$hostMatches = array();
$hasTrailingVar = $trimmedPathinfo !== $pathinfo && preg_match('#\{\w+\}/?$#', $route->getPath());
if ($hasTrailingVar && ($hasTrailingSlash || (null === $m = $matches[\count($compiledRoute->getPathVariables())] ?? null) || '/' !== ($m[-1] ?? '/')) && preg_match($regex, $trimmedPathinfo, $m)) {
if ($hasTrailingSlash) {
$matches = $m;
} else {
$hasTrailingVar = false;
}
}
$hostMatches = [];
if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) {
$this->addTrace(sprintf('Host "%s" does not match the requirement ("%s")', $this->context->getHost(), $route->getHost()), self::ROUTE_ALMOST_MATCHES, $name, $route);
continue;
}
// check HTTP method requirement
if ($requiredMethods = $route->getMethods()) {
// HEAD and GET are equivalent as per RFC
if ('HEAD' === $method = $this->context->getMethod()) {
$method = 'GET';
}
$status = $this->handleRouteRequirements($pathinfo, $name, $route);
if (!\in_array($method, $requiredMethods)) {
$this->allow = array_merge($this->allow, $requiredMethods);
$this->addTrace(sprintf('Method "%s" does not match any of the required methods (%s)', $this->context->getMethod(), implode(', ', $requiredMethods)), self::ROUTE_ALMOST_MATCHES, $name, $route);
continue;
}
if (self::REQUIREMENT_MISMATCH === $status[0]) {
$this->addTrace(sprintf('Condition "%s" does not evaluate to "true"', $route->getCondition()), self::ROUTE_ALMOST_MATCHES, $name, $route);
continue;
}
// check condition
if ($condition = $route->getCondition()) {
if (!$this->getExpressionLanguage()->evaluate($condition, array('context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)))) {
$this->addTrace(sprintf('Condition "%s" does not evaluate to "true"', $condition), self::ROUTE_ALMOST_MATCHES, $name, $route);
if ('/' !== $pathinfo && !$hasTrailingVar && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) {
if ($supportsTrailingSlash && (!$requiredMethods || \in_array('GET', $requiredMethods))) {
$this->addTrace('Route matches!', self::ROUTE_MATCHES, $name, $route);
continue;
return $this->allow = $this->allowSchemes = [];
}
$this->addTrace(sprintf('Path "%s" does not match', $route->getPath()), self::ROUTE_DOES_NOT_MATCH, $name, $route);
continue;
}
// check HTTP scheme requirement
if ($requiredSchemes = $route->getSchemes()) {
$scheme = $this->context->getScheme();
if ($route->getSchemes() && !$route->hasScheme($this->context->getScheme())) {
$this->allowSchemes = array_merge($this->allowSchemes, $route->getSchemes());
$this->addTrace(sprintf('Scheme "%s" does not match any of the required schemes (%s)', $this->context->getScheme(), implode(', ', $route->getSchemes())), self::ROUTE_ALMOST_MATCHES, $name, $route);
continue;
}
if (!$route->hasScheme($scheme)) {
$this->addTrace(sprintf('Scheme "%s" does not match any of the required schemes (%s); the user will be redirected to first required scheme', $scheme, implode(', ', $requiredSchemes)), self::ROUTE_ALMOST_MATCHES, $name, $route);
return true;
}
if ($requiredMethods && !\in_array($method, $requiredMethods)) {
$this->allow = array_merge($this->allow, $requiredMethods);
$this->addTrace(sprintf('Method "%s" does not match any of the required methods (%s)', $this->context->getMethod(), implode(', ', $requiredMethods)), self::ROUTE_ALMOST_MATCHES, $name, $route);
continue;
}
$this->addTrace('Route matches!', self::ROUTE_MATCHES, $name, $route);
return true;
return $this->getAttributes($route, $name, array_replace($matches, $hostMatches, $status[1] ?? []));
}
return [];
}
private function addTrace($log, $level = self::ROUTE_DOES_NOT_MATCH, $name = null, $route = null)
private function addTrace(string $log, int $level = self::ROUTE_DOES_NOT_MATCH, string $name = null, Route $route = null)
{
$this->traces[] = array(
$this->traces[] = [
'log' => $log,
'name' => $name,
'level' => $level,
'path' => null !== $route ? $route->getPath() : null,
);
];
}
}

View File

@@ -28,23 +28,24 @@ use Symfony\Component\Routing\RouteCollection;
*/
class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
{
const REQUIREMENT_MATCH = 0;
const REQUIREMENT_MISMATCH = 1;
const ROUTE_MATCH = 2;
public const REQUIREMENT_MATCH = 0;
public const REQUIREMENT_MISMATCH = 1;
public const ROUTE_MATCH = 2;
/** @var RequestContext */
protected $context;
/**
* Collects HTTP methods that would be allowed for the request.
*/
protected $allow = array();
protected $allow = [];
/**
* Collects URI schemes that would be allowed for the request.
*
* @internal
*/
protected $allowSchemes = array();
protected $allowSchemes = [];
protected $routes;
protected $request;
@@ -53,7 +54,7 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
/**
* @var ExpressionFunctionProviderInterface[]
*/
protected $expressionLanguageProviders = array();
protected $expressionLanguageProviders = [];
public function __construct(RouteCollection $routes, RequestContext $context)
{
@@ -82,19 +83,17 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
*/
public function match($pathinfo)
{
$this->allow = $this->allowSchemes = array();
$this->allow = $this->allowSchemes = [];
if ($ret = $this->matchCollection(rawurldecode($pathinfo), $this->routes)) {
if ($ret = $this->matchCollection(rawurldecode($pathinfo) ?: '/', $this->routes)) {
return $ret;
}
if ('/' === $pathinfo && !$this->allow) {
if ('/' === $pathinfo && !$this->allow && !$this->allowSchemes) {
throw new NoConfigurationException();
}
throw 0 < \count($this->allow)
? new MethodNotAllowedException(array_unique($this->allow))
: new ResourceNotFoundException(sprintf('No routes found for "%s".', $pathinfo));
throw 0 < \count($this->allow) ? new MethodNotAllowedException(array_unique($this->allow)) : new ResourceNotFoundException(sprintf('No routes found for "%s".', $pathinfo));
}
/**
@@ -119,8 +118,7 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
/**
* Tries to match a URL with a set of routes.
*
* @param string $pathinfo The path info to be parsed
* @param RouteCollection $routes The set of routes
* @param string $pathinfo The path info to be parsed
*
* @return array An array of parameters
*
@@ -130,46 +128,43 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
*/
protected function matchCollection($pathinfo, RouteCollection $routes)
{
$supportsTrailingSlash = '/' !== $pathinfo && '' !== $pathinfo && $this instanceof RedirectableUrlMatcherInterface;
// HEAD and GET are equivalent as per RFC
if ('HEAD' === $method = $this->context->getMethod()) {
$method = 'GET';
}
$supportsTrailingSlash = 'GET' === $method && $this instanceof RedirectableUrlMatcherInterface;
$trimmedPathinfo = rtrim($pathinfo, '/') ?: '/';
foreach ($routes as $name => $route) {
$compiledRoute = $route->compile();
$staticPrefix = $compiledRoute->getStaticPrefix();
$staticPrefix = rtrim($compiledRoute->getStaticPrefix(), '/');
$requiredMethods = $route->getMethods();
// check the static prefix of the URL first. Only use the more expensive preg_match when it matches
if ('' === $staticPrefix || 0 === strpos($pathinfo, $staticPrefix)) {
// no-op
} elseif (!$supportsTrailingSlash) {
continue;
} elseif ('/' === $staticPrefix[-1] && substr($staticPrefix, 0, -1) === $pathinfo) {
return;
} elseif ('/' === $pathinfo[-1] && substr($pathinfo, 0, -1) === $staticPrefix) {
return;
} else {
if ('' !== $staticPrefix && !str_starts_with($trimmedPathinfo, $staticPrefix)) {
continue;
}
$regex = $compiledRoute->getRegex();
if ($supportsTrailingSlash) {
$pos = strrpos($regex, '$');
$hasTrailingSlash = '/' === $regex[$pos - 1];
$regex = substr_replace($regex, '/?$', $pos - $hasTrailingSlash, 1 + $hasTrailingSlash);
}
$pos = strrpos($regex, '$');
$hasTrailingSlash = '/' === $regex[$pos - 1];
$regex = substr_replace($regex, '/?$', $pos - $hasTrailingSlash, 1 + $hasTrailingSlash);
if (!preg_match($regex, $pathinfo, $matches)) {
continue;
}
if ($supportsTrailingSlash) {
if (!$hasTrailingSlash && '/' === $pathinfo[-1] && preg_match($regex, substr($pathinfo, 0, -1))) {
return;
}
if ($hasTrailingSlash && '/' !== $pathinfo[-1]) {
return;
$hasTrailingVar = $trimmedPathinfo !== $pathinfo && preg_match('#\{\w+\}/?$#', $route->getPath());
if ($hasTrailingVar && ($hasTrailingSlash || (null === $m = $matches[\count($compiledRoute->getPathVariables())] ?? null) || '/' !== ($m[-1] ?? '/')) && preg_match($regex, $trimmedPathinfo, $m)) {
if ($hasTrailingSlash) {
$matches = $m;
} else {
$hasTrailingVar = false;
}
}
$hostMatches = array();
$hostMatches = [];
if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) {
continue;
}
@@ -180,30 +175,27 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
continue;
}
$hasRequiredScheme = !$route->getSchemes() || $route->hasScheme($this->context->getScheme());
if ($requiredMethods = $route->getMethods()) {
// HEAD and GET are equivalent as per RFC
if ('HEAD' === $method = $this->context->getMethod()) {
$method = 'GET';
if ('/' !== $pathinfo && !$hasTrailingVar && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) {
if ($supportsTrailingSlash && (!$requiredMethods || \in_array('GET', $requiredMethods))) {
return $this->allow = $this->allowSchemes = [];
}
if (!\in_array($method, $requiredMethods)) {
if ($hasRequiredScheme) {
$this->allow = array_merge($this->allow, $requiredMethods);
}
continue;
}
}
if (!$hasRequiredScheme) {
$this->allowSchemes = array_merge($this->allowSchemes, $route->getSchemes());
continue;
}
return $this->getAttributes($route, $name, array_replace($matches, $hostMatches, isset($status[1]) ? $status[1] : array()));
if ($route->getSchemes() && !$route->hasScheme($this->context->getScheme())) {
$this->allowSchemes = array_merge($this->allowSchemes, $route->getSchemes());
continue;
}
if ($requiredMethods && !\in_array($method, $requiredMethods)) {
$this->allow = array_merge($this->allow, $requiredMethods);
continue;
}
return $this->getAttributes($route, $name, array_replace($matches, $hostMatches, $status[1] ?? []));
}
return [];
}
/**
@@ -213,7 +205,6 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
* in matchers that do not have access to the matched Route instance
* (like the PHP and Apache matcher dumpers).
*
* @param Route $route The route we are matching against
* @param string $name The name of the route
* @param array $attributes An array of attributes from the matcher
*
@@ -236,18 +227,17 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
*
* @param string $pathinfo The path
* @param string $name The route name
* @param Route $route The route
*
* @return array The first element represents the status, the second contains additional information
*/
protected function handleRouteRequirements($pathinfo, $name, Route $route)
{
// expression condition
if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), array('context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)))) {
return array(self::REQUIREMENT_MISMATCH, null);
if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), ['context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)])) {
return [self::REQUIREMENT_MISMATCH, null];
}
return array(self::REQUIREMENT_MATCH, null);
return [self::REQUIREMENT_MATCH, null];
}
/**
@@ -272,7 +262,7 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
protected function getExpressionLanguage()
{
if (null === $this->expressionLanguage) {
if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
if (!class_exists(ExpressionLanguage::class)) {
throw new \LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
}
$this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders);
@@ -284,15 +274,15 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
/**
* @internal
*/
protected function createRequest($pathinfo)
protected function createRequest(string $pathinfo): ?Request
{
if (!class_exists('Symfony\Component\HttpFoundation\Request')) {
if (!class_exists(Request::class)) {
return null;
}
return Request::create($this->context->getScheme().'://'.$this->context->getHost().$this->context->getBaseUrl().$pathinfo, $this->context->getMethod(), $this->context->getParameters(), array(), array(), array(
return Request::create($this->context->getScheme().'://'.$this->context->getHost().$this->context->getBaseUrl().$pathinfo, $this->context->getMethod(), $this->context->getParameters(), [], [], [
'SCRIPT_FILENAME' => $this->context->getBaseUrl(),
'SCRIPT_NAME' => $this->context->getBaseUrl(),
));
]);
}
}

View File

@@ -3,11 +3,49 @@ Routing Component
The Routing component maps an HTTP request to a set of configuration variables.
Getting Started
---------------
```
$ composer require symfony/routing
```
```php
use App\Controller\BlogController;
use Symfony\Component\Routing\Generator\UrlGenerator;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
$route = new Route('/blog/{slug}', ['_controller' => BlogController::class]);
$routes = new RouteCollection();
$routes->add('blog_show', $route);
$context = new RequestContext();
// Routing can match routes with incoming requests
$matcher = new UrlMatcher($routes, $context);
$parameters = $matcher->match('/blog/lorem-ipsum');
// $parameters = [
// '_controller' => 'App\Controller\BlogController',
// 'slug' => 'lorem-ipsum',
// '_route' => 'blog_show'
// ]
// Routing can also generate URLs for a given route
$generator = new UrlGenerator($routes, $context);
$url = $generator->generate('blog_show', [
'slug' => 'my-blog-post',
]);
// $url = '/blog/my-blog-post'
```
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/routing/index.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)
* [Documentation](https://symfony.com/doc/current/routing.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)

View File

@@ -31,7 +31,7 @@ class RequestContext
private $httpPort;
private $httpsPort;
private $queryString;
private $parameters = array();
private $parameters = [];
public function __construct(string $baseUrl = '', string $method = 'GET', string $host = 'localhost', string $scheme = 'http', int $httpPort = 80, int $httpsPort = 443, string $path = '/', string $queryString = '')
{
@@ -57,8 +57,8 @@ class RequestContext
$this->setMethod($request->getMethod());
$this->setHost($request->getHost());
$this->setScheme($request->getScheme());
$this->setHttpPort($request->isSecure() ? $this->httpPort : $request->getPort());
$this->setHttpsPort($request->isSecure() ? $request->getPort() : $this->httpsPort);
$this->setHttpPort($request->isSecure() || null === $request->getPort() ? $this->httpPort : $request->getPort());
$this->setHttpsPort($request->isSecure() && null !== $request->getPort() ? $request->getPort() : $this->httpsPort);
$this->setQueryString($request->server->get('QUERY_STRING', ''));
return $this;
@@ -294,7 +294,7 @@ class RequestContext
*/
public function getParameter($name)
{
return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
return $this->parameters[$name] ?? null;
}
/**
@@ -306,7 +306,7 @@ class RequestContext
*/
public function hasParameter($name)
{
return array_key_exists($name, $this->parameters);
return \array_key_exists($name, $this->parameters);
}
/**

View File

@@ -21,11 +21,11 @@ class Route implements \Serializable
{
private $path = '/';
private $host = '';
private $schemes = array();
private $methods = array();
private $defaults = array();
private $requirements = array();
private $options = array();
private $schemes = [];
private $methods = [];
private $defaults = [];
private $requirements = [];
private $options = [];
private $condition = '';
/**
@@ -45,12 +45,12 @@ class Route implements \Serializable
* @param array $defaults An array of default parameter values
* @param array $requirements An array of requirements for parameters (regexes)
* @param array $options An array of options
* @param string $host The host pattern to match
* @param string|null $host The host pattern to match
* @param string|string[] $schemes A required URI scheme or an array of restricted schemes
* @param string|string[] $methods A required HTTP method or an array of restricted methods
* @param string $condition A condition that should evaluate to true for the route to match
* @param string|null $condition A condition that should evaluate to true for the route to match
*/
public function __construct(string $path, array $defaults = array(), array $requirements = array(), array $options = array(), ?string $host = '', $schemes = array(), $methods = array(), ?string $condition = '')
public function __construct(string $path, array $defaults = [], array $requirements = [], array $options = [], ?string $host = '', $schemes = [], $methods = [], ?string $condition = '')
{
$this->setPath($path);
$this->addDefaults($defaults);
@@ -62,12 +62,9 @@ class Route implements \Serializable
$this->setCondition($condition);
}
/**
* {@inheritdoc}
*/
public function serialize()
public function __serialize(): array
{
return serialize(array(
return [
'path' => $this->path,
'host' => $this->host,
'defaults' => $this->defaults,
@@ -77,15 +74,22 @@ class Route implements \Serializable
'methods' => $this->methods,
'condition' => $this->condition,
'compiled' => $this->compiled,
));
];
}
/**
* {@inheritdoc}
* @return string
*
* @internal since Symfony 4.3
* @final since Symfony 4.3
*/
public function unserialize($serialized)
public function serialize()
{
return serialize($this->__serialize());
}
public function __unserialize(array $data): void
{
$data = unserialize($serialized);
$this->path = $data['path'];
$this->host = $data['host'];
$this->defaults = $data['defaults'];
@@ -103,8 +107,15 @@ class Route implements \Serializable
}
/**
* Returns the pattern for the path.
*
* @internal since Symfony 4.3
* @final since Symfony 4.3
*/
public function unserialize($serialized)
{
$this->__unserialize(unserialize($serialized));
}
/**
* @return string The path pattern
*/
public function getPath()
@@ -115,8 +126,6 @@ class Route implements \Serializable
/**
* Sets the pattern for the path.
*
* This method implements a fluent interface.
*
* @param string $pattern The path pattern
*
* @return $this
@@ -124,15 +133,15 @@ class Route implements \Serializable
public function setPath($pattern)
{
if (false !== strpbrk($pattern, '?<')) {
$pattern = preg_replace_callback('#\{(\w++)(<.*?>)?(\?[^\}]*+)?\}#', function ($m) {
if (isset($m[3][0])) {
$this->setDefault($m[1], '?' !== $m[3] ? substr($m[3], 1) : null);
$pattern = preg_replace_callback('#\{(!?)(\w++)(<.*?>)?(\?[^\}]*+)?\}#', function ($m) {
if (isset($m[4][0])) {
$this->setDefault($m[2], '?' !== $m[4] ? substr($m[4], 1) : null);
}
if (isset($m[2][0])) {
$this->setRequirement($m[1], substr($m[2], 1, -1));
if (isset($m[3][0])) {
$this->setRequirement($m[2], substr($m[3], 1, -1));
}
return '{'.$m[1].'}';
return '{'.$m[1].$m[2].'}';
}, $pattern);
}
@@ -145,8 +154,6 @@ class Route implements \Serializable
}
/**
* Returns the pattern for the host.
*
* @return string The host pattern
*/
public function getHost()
@@ -157,8 +164,6 @@ class Route implements \Serializable
/**
* Sets the pattern for the host.
*
* This method implements a fluent interface.
*
* @param string $pattern The host pattern
*
* @return $this
@@ -186,8 +191,6 @@ class Route implements \Serializable
* Sets the schemes (e.g. 'https') this route is restricted to.
* So an empty array means that any scheme is allowed.
*
* This method implements a fluent interface.
*
* @param string|string[] $schemes The scheme or an array of schemes
*
* @return $this
@@ -227,8 +230,6 @@ class Route implements \Serializable
* Sets the HTTP methods (e.g. 'POST') this route is restricted to.
* So an empty array means that any method is allowed.
*
* This method implements a fluent interface.
*
* @param string|string[] $methods The method or an array of methods
*
* @return $this
@@ -242,8 +243,6 @@ class Route implements \Serializable
}
/**
* Returns the options.
*
* @return array The options
*/
public function getOptions()
@@ -252,30 +251,18 @@ class Route implements \Serializable
}
/**
* Sets the options.
*
* This method implements a fluent interface.
*
* @param array $options The options
*
* @return $this
*/
public function setOptions(array $options)
{
$this->options = array(
$this->options = [
'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler',
);
];
return $this->addOptions($options);
}
/**
* Adds options.
*
* This method implements a fluent interface.
*
* @param array $options The options
*
* @return $this
*/
public function addOptions(array $options)
@@ -291,8 +278,6 @@ class Route implements \Serializable
/**
* Sets an option value.
*
* This method implements a fluent interface.
*
* @param string $name An option name
* @param mixed $value The option value
*
@@ -315,7 +300,7 @@ class Route implements \Serializable
*/
public function getOption($name)
{
return isset($this->options[$name]) ? $this->options[$name] : null;
return $this->options[$name] ?? null;
}
/**
@@ -327,12 +312,10 @@ class Route implements \Serializable
*/
public function hasOption($name)
{
return array_key_exists($name, $this->options);
return \array_key_exists($name, $this->options);
}
/**
* Returns the defaults.
*
* @return array The defaults
*/
public function getDefaults()
@@ -341,32 +324,24 @@ class Route implements \Serializable
}
/**
* Sets the defaults.
*
* This method implements a fluent interface.
*
* @param array $defaults The defaults
*
* @return $this
*/
public function setDefaults(array $defaults)
{
$this->defaults = array();
$this->defaults = [];
return $this->addDefaults($defaults);
}
/**
* Adds defaults.
*
* This method implements a fluent interface.
*
* @param array $defaults The defaults
*
* @return $this
*/
public function addDefaults(array $defaults)
{
if (isset($defaults['_locale']) && $this->isLocalized()) {
unset($defaults['_locale']);
}
foreach ($defaults as $name => $default) {
$this->defaults[$name] = $default;
}
@@ -384,7 +359,7 @@ class Route implements \Serializable
*/
public function getDefault($name)
{
return isset($this->defaults[$name]) ? $this->defaults[$name] : null;
return $this->defaults[$name] ?? null;
}
/**
@@ -396,7 +371,7 @@ class Route implements \Serializable
*/
public function hasDefault($name)
{
return array_key_exists($name, $this->defaults);
return \array_key_exists($name, $this->defaults);
}
/**
@@ -409,6 +384,10 @@ class Route implements \Serializable
*/
public function setDefault($name, $default)
{
if ('_locale' === $name && $this->isLocalized()) {
return $this;
}
$this->defaults[$name] = $default;
$this->compiled = null;
@@ -416,8 +395,6 @@ class Route implements \Serializable
}
/**
* Returns the requirements.
*
* @return array The requirements
*/
public function getRequirements()
@@ -426,32 +403,24 @@ class Route implements \Serializable
}
/**
* Sets the requirements.
*
* This method implements a fluent interface.
*
* @param array $requirements The requirements
*
* @return $this
*/
public function setRequirements(array $requirements)
{
$this->requirements = array();
$this->requirements = [];
return $this->addRequirements($requirements);
}
/**
* Adds requirements.
*
* This method implements a fluent interface.
*
* @param array $requirements The requirements
*
* @return $this
*/
public function addRequirements(array $requirements)
{
if (isset($requirements['_locale']) && $this->isLocalized()) {
unset($requirements['_locale']);
}
foreach ($requirements as $key => $regex) {
$this->requirements[$key] = $this->sanitizeRequirement($key, $regex);
}
@@ -469,7 +438,7 @@ class Route implements \Serializable
*/
public function getRequirement($key)
{
return isset($this->requirements[$key]) ? $this->requirements[$key] : null;
return $this->requirements[$key] ?? null;
}
/**
@@ -481,7 +450,7 @@ class Route implements \Serializable
*/
public function hasRequirement($key)
{
return array_key_exists($key, $this->requirements);
return \array_key_exists($key, $this->requirements);
}
/**
@@ -494,6 +463,10 @@ class Route implements \Serializable
*/
public function setRequirement($key, $regex)
{
if ('_locale' === $key && $this->isLocalized()) {
return $this;
}
$this->requirements[$key] = $this->sanitizeRequirement($key, $regex);
$this->compiled = null;
@@ -501,8 +474,6 @@ class Route implements \Serializable
}
/**
* Returns the condition.
*
* @return string The condition
*/
public function getCondition()
@@ -513,8 +484,6 @@ class Route implements \Serializable
/**
* Sets the condition.
*
* This method implements a fluent interface.
*
* @param string $condition The condition
*
* @return $this
@@ -548,7 +517,7 @@ class Route implements \Serializable
return $this->compiled = $class::compile($this);
}
private function sanitizeRequirement($key, $regex)
private function sanitizeRequirement(string $key, $regex)
{
if (!\is_string($regex)) {
throw new \InvalidArgumentException(sprintf('Routing requirement for "%s" must be a string.', $key));
@@ -558,7 +527,7 @@ class Route implements \Serializable
$regex = (string) substr($regex, 1); // returns false for a single character
}
if ('$' === substr($regex, -1)) {
if (str_ends_with($regex, '$')) {
$regex = substr($regex, 0, -1);
}
@@ -568,4 +537,9 @@ class Route implements \Serializable
return $regex;
}
private function isLocalized(): bool
{
return isset($this->defaults['_locale']) && isset($this->defaults['_canonical_route']) && ($this->requirements['_locale'] ?? null) === preg_quote($this->defaults['_locale'], RouteCompiler::REGEX_DELIMITER);
}
}

View File

@@ -28,12 +28,12 @@ class RouteCollection implements \IteratorAggregate, \Countable
/**
* @var Route[]
*/
private $routes = array();
private $routes = [];
/**
* @var array
*/
private $resources = array();
private $resources = [];
public function __clone()
{
@@ -51,6 +51,7 @@ class RouteCollection implements \IteratorAggregate, \Countable
*
* @return \ArrayIterator|Route[] An \ArrayIterator object for iterating over routes
*/
#[\ReturnTypeWillChange]
public function getIterator()
{
return new \ArrayIterator($this->routes);
@@ -61,6 +62,7 @@ class RouteCollection implements \IteratorAggregate, \Countable
*
* @return int The number of routes
*/
#[\ReturnTypeWillChange]
public function count()
{
return \count($this->routes);
@@ -69,8 +71,7 @@ class RouteCollection implements \IteratorAggregate, \Countable
/**
* Adds a route.
*
* @param string $name The route name
* @param Route $route A Route instance
* @param string $name The route name
*/
public function add($name, Route $route)
{
@@ -98,7 +99,7 @@ class RouteCollection implements \IteratorAggregate, \Countable
*/
public function get($name)
{
return isset($this->routes[$name]) ? $this->routes[$name] : null;
return $this->routes[$name] ?? null;
}
/**
@@ -138,8 +139,12 @@ class RouteCollection implements \IteratorAggregate, \Countable
* @param array $defaults An array of default values
* @param array $requirements An array of requirements
*/
public function addPrefix($prefix, array $defaults = array(), array $requirements = array())
public function addPrefix($prefix, array $defaults = [], array $requirements = [])
{
if (null === $prefix) {
@trigger_error(sprintf('Passing null as $prefix to %s is deprecated in Symfony 4.4 and will trigger a TypeError in 5.0.', __METHOD__), \E_USER_DEPRECATED);
}
$prefix = trim(trim($prefix), '/');
if ('' === $prefix) {
@@ -158,7 +163,7 @@ class RouteCollection implements \IteratorAggregate, \Countable
*/
public function addNamePrefix(string $prefix)
{
$prefixedRoutes = array();
$prefixedRoutes = [];
foreach ($this->routes as $name => $route) {
$prefixedRoutes[$prefix.$name] = $route;
@@ -177,7 +182,7 @@ class RouteCollection implements \IteratorAggregate, \Countable
* @param array $defaults An array of default values
* @param array $requirements An array of requirements
*/
public function setHost($pattern, array $defaults = array(), array $requirements = array())
public function setHost($pattern, array $defaults = [], array $requirements = [])
{
foreach ($this->routes as $route) {
$route->setHost($pattern);
@@ -236,8 +241,6 @@ class RouteCollection implements \IteratorAggregate, \Countable
* Adds options to all routes.
*
* An existing option value under the same name in a route will be overridden.
*
* @param array $options An array of options
*/
public function addOptions(array $options)
{

View File

@@ -25,18 +25,18 @@ class RouteCollectionBuilder
/**
* @var Route[]|RouteCollectionBuilder[]
*/
private $routes = array();
private $routes = [];
private $loader;
private $defaults = array();
private $defaults = [];
private $prefix;
private $host;
private $condition;
private $requirements = array();
private $options = array();
private $requirements = [];
private $options = [];
private $schemes;
private $methods;
private $resources = array();
private $resources = [];
public function __construct(LoaderInterface $loader = null)
{
@@ -115,8 +115,7 @@ class RouteCollectionBuilder
/**
* Add a RouteCollectionBuilder.
*
* @param string $prefix
* @param RouteCollectionBuilder $builder
* @param string $prefix
*/
public function mount($prefix, self $builder)
{
@@ -127,7 +126,6 @@ class RouteCollectionBuilder
/**
* Adds a Route object to the builder.
*
* @param Route $route
* @param string|null $name
*
* @return $this
@@ -309,7 +307,9 @@ class RouteCollectionBuilder
} else {
/* @var self $route */
$subCollection = $route->build();
$subCollection->addPrefix($this->prefix);
if (null !== $this->prefix) {
$subCollection->addPrefix($this->prefix);
}
$routeCollection->addCollection($subCollection);
}
@@ -330,7 +330,7 @@ class RouteCollectionBuilder
$methods = implode('_', $route->getMethods()).'_';
$routeName = $methods.$route->getPath();
$routeName = str_replace(array('/', ':', '|', '-'), '_', $routeName);
$routeName = str_replace(['/', ':', '|', '-'], '_', $routeName);
$routeName = preg_replace('/[^a-z0-9A-Z_.]+/', '', $routeName);
// Collapse consecutive underscores down into a single underscore.
@@ -358,19 +358,19 @@ class RouteCollectionBuilder
if ($this->loader->supports($resource, $type)) {
$collections = $this->loader->load($resource, $type);
return \is_array($collections) ? $collections : array($collections);
return \is_array($collections) ? $collections : [$collections];
}
if (null === $resolver = $this->loader->getResolver()) {
throw new LoaderLoadException($resource, null, null, null, $type);
throw new LoaderLoadException($resource, null, 0, null, $type);
}
if (false === $loader = $resolver->resolve($resource, $type)) {
throw new LoaderLoadException($resource, null, null, null, $type);
throw new LoaderLoadException($resource, null, 0, null, $type);
}
$collections = $loader->load($resource, $type);
return \is_array($collections) ? $collections : array($collections);
return \is_array($collections) ? $collections : [$collections];
}
}

View File

@@ -19,14 +19,14 @@ namespace Symfony\Component\Routing;
*/
class RouteCompiler implements RouteCompilerInterface
{
const REGEX_DELIMITER = '#';
public const REGEX_DELIMITER = '#';
/**
* This string defines the characters that are automatically considered separators in front of
* optional placeholders (with default and no static text following). Such a single separator
* can be left out together with the optional placeholder from matching and generating URLs.
*/
const SEPARATORS = '/,;.:-_~+*=@|';
public const SEPARATORS = '/,;.:-_~+*=@|';
/**
* The maximum supported length of a PCRE subpattern name
@@ -34,7 +34,7 @@ class RouteCompiler implements RouteCompilerInterface
*
* @internal
*/
const VARIABLE_MAXIMUM_LENGTH = 32;
public const VARIABLE_MAXIMUM_LENGTH = 32;
/**
* {@inheritdoc}
@@ -46,10 +46,10 @@ class RouteCompiler implements RouteCompilerInterface
*/
public static function compile(Route $route)
{
$hostVariables = array();
$variables = array();
$hostVariables = [];
$variables = [];
$hostRegex = null;
$hostTokens = array();
$hostTokens = [];
if ('' !== $host = $route->getHost()) {
$result = self::compilePattern($route, $host, true);
@@ -61,6 +61,14 @@ class RouteCompiler implements RouteCompilerInterface
$hostRegex = $result['regex'];
}
$locale = $route->getDefault('_locale');
if (null !== $locale && null !== $route->getDefault('_canonical_route') && preg_quote($locale, self::REGEX_DELIMITER) === $route->getRequirement('_locale')) {
$requirements = $route->getRequirements();
unset($requirements['_locale']);
$route->setRequirements($requirements);
$route->setPath(str_replace('{_locale}', $locale, $route->getPath()));
}
$path = $route->getPath();
$result = self::compilePattern($route, $path, false);
@@ -92,11 +100,11 @@ class RouteCompiler implements RouteCompilerInterface
);
}
private static function compilePattern(Route $route, $pattern, $isHost)
private static function compilePattern(Route $route, string $pattern, bool $isHost): array
{
$tokens = array();
$variables = array();
$matches = array();
$tokens = [];
$variables = [];
$matches = [];
$pos = 0;
$defaultSeparator = $isHost ? '.' : '/';
$useUtf8 = preg_match('//u', $pattern);
@@ -111,9 +119,10 @@ class RouteCompiler implements RouteCompilerInterface
// Match all variables enclosed in "{}" and iterate over them. But we only want to match the innermost variable
// in case of nested "{}", e.g. {foo{bar}}. This in ensured because \w does not match "{" or "}" itself.
preg_match_all('#\{\w+\}#', $pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
preg_match_all('#\{(!)?(\w+)\}#', $pattern, $matches, \PREG_OFFSET_CAPTURE | \PREG_SET_ORDER);
foreach ($matches as $match) {
$varName = substr($match[0][0], 1, -1);
$important = $match[1][1] >= 0;
$varName = $match[2][0];
// get all static text preceding the current variable
$precedingText = substr($pattern, $pos, $match[0][1] - $pos);
$pos = $match[0][1] + \strlen($match[0][0]);
@@ -126,7 +135,7 @@ class RouteCompiler implements RouteCompilerInterface
} else {
$precedingChar = substr($precedingText, -1);
}
$isSeparator = '' !== $precedingChar && false !== strpos(static::SEPARATORS, $precedingChar);
$isSeparator = '' !== $precedingChar && str_contains(static::SEPARATORS, $precedingChar);
// A PCRE subpattern name must start with a non-digit. Also a PHP variable cannot start with a digit so the
// variable would not be usable as a Controller action argument.
@@ -138,13 +147,13 @@ class RouteCompiler implements RouteCompilerInterface
}
if (\strlen($varName) > self::VARIABLE_MAXIMUM_LENGTH) {
throw new \DomainException(sprintf('Variable name "%s" cannot be longer than %s characters in route pattern "%s". Please use a shorter name.', $varName, self::VARIABLE_MAXIMUM_LENGTH, $pattern));
throw new \DomainException(sprintf('Variable name "%s" cannot be longer than %d characters in route pattern "%s". Please use a shorter name.', $varName, self::VARIABLE_MAXIMUM_LENGTH, $pattern));
}
if ($isSeparator && $precedingText !== $precedingChar) {
$tokens[] = array('text', substr($precedingText, 0, -\strlen($precedingChar)));
} elseif (!$isSeparator && \strlen($precedingText) > 0) {
$tokens[] = array('text', $precedingText);
$tokens[] = ['text', substr($precedingText, 0, -\strlen($precedingChar))];
} elseif (!$isSeparator && '' !== $precedingText) {
$tokens[] = ['text', $precedingText];
}
$regexp = $route->getRequirement($varName);
@@ -153,7 +162,7 @@ class RouteCompiler implements RouteCompilerInterface
// Find the next static character after the variable that functions as a separator. By default, this separator and '/'
// are disallowed for the variable. This default requirement makes sure that optional variables can be matched at all
// and that the generating-matching-combination of URLs unambiguous, i.e. the params used for generating the URL are
// the same that will be matched. Example: new Route('/{page}.{_format}', array('_format' => 'html'))
// the same that will be matched. Example: new Route('/{page}.{_format}', ['_format' => 'html'])
// If {page} would also match the separating dot, {_format} would never match as {page} will eagerly consume everything.
// Also even if {_format} was not optional the requirement prevents that {page} matches something that was originally
// part of {_format} when generating the URL, e.g. _format = 'mobile.html'.
@@ -183,20 +192,27 @@ class RouteCompiler implements RouteCompilerInterface
$regexp = self::transformCapturingGroupsToNonCapturings($regexp);
}
$tokens[] = array('variable', $isSeparator ? $precedingChar : '', $regexp, $varName);
if ($important) {
$token = ['variable', $isSeparator ? $precedingChar : '', $regexp, $varName, false, true];
} else {
$token = ['variable', $isSeparator ? $precedingChar : '', $regexp, $varName];
}
$tokens[] = $token;
$variables[] = $varName;
}
if ($pos < \strlen($pattern)) {
$tokens[] = array('text', substr($pattern, $pos));
$tokens[] = ['text', substr($pattern, $pos)];
}
// find the first optional token
$firstOptional = PHP_INT_MAX;
$firstOptional = \PHP_INT_MAX;
if (!$isHost) {
for ($i = \count($tokens) - 1; $i >= 0; --$i) {
$token = $tokens[$i];
if ('variable' === $token[0] && $route->hasDefault($token[3])) {
// variable is optional when it is not important and has a default value
if ('variable' === $token[0] && !($token[5] ?? false) && $route->hasDefault($token[3])) {
$firstOptional = $i;
} else {
break;
@@ -216,17 +232,17 @@ class RouteCompiler implements RouteCompilerInterface
$regexp .= 'u';
for ($i = 0, $nbToken = \count($tokens); $i < $nbToken; ++$i) {
if ('variable' === $tokens[$i][0]) {
$tokens[$i][] = true;
$tokens[$i][4] = true;
}
}
}
return array(
return [
'staticPrefix' => self::determineStaticPrefix($route, $tokens),
'regex' => $regexp,
'tokens' => array_reverse($tokens),
'variables' => $variables,
);
];
}
/**
@@ -264,7 +280,7 @@ class RouteCompiler implements RouteCompilerInterface
preg_match('/^./u', $pattern, $pattern);
}
return false !== strpos(static::SEPARATORS, $pattern[0]) ? $pattern[0] : '';
return str_contains(static::SEPARATORS, $pattern[0]) ? $pattern[0] : '';
}
/**

View File

@@ -12,17 +12,26 @@
namespace Symfony\Component\Routing;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Routing\RedirectableUrlMatcher;
use Symfony\Component\Config\ConfigCacheFactory;
use Symfony\Component\Config\ConfigCacheFactoryInterface;
use Symfony\Component\Config\ConfigCacheInterface;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\CompiledUrlGenerator;
use Symfony\Component\Routing\Generator\ConfigurableRequirementsInterface;
use Symfony\Component\Routing\Generator\Dumper\CompiledUrlGeneratorDumper;
use Symfony\Component\Routing\Generator\Dumper\GeneratorDumperInterface;
use Symfony\Component\Routing\Generator\Dumper\PhpGeneratorDumper;
use Symfony\Component\Routing\Generator\UrlGenerator;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\Matcher\CompiledUrlMatcher;
use Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherDumper;
use Symfony\Component\Routing\Matcher\Dumper\MatcherDumperInterface;
use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper;
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
/**
@@ -66,13 +75,18 @@ class Router implements RouterInterface, RequestMatcherInterface
/**
* @var array
*/
protected $options = array();
protected $options = [];
/**
* @var LoggerInterface|null
*/
protected $logger;
/**
* @var string|null
*/
protected $defaultLocale;
/**
* @var ConfigCacheFactoryInterface|null
*/
@@ -81,22 +95,21 @@ class Router implements RouterInterface, RequestMatcherInterface
/**
* @var ExpressionFunctionProviderInterface[]
*/
private $expressionLanguageProviders = array();
private $expressionLanguageProviders = [];
private static $cache = [];
/**
* @param LoaderInterface $loader A LoaderInterface instance
* @param mixed $resource The main resource to load
* @param array $options An array of options
* @param RequestContext $context The context
* @param LoggerInterface $logger A logger instance
* @param mixed $resource The main resource to load
*/
public function __construct(LoaderInterface $loader, $resource, array $options = array(), RequestContext $context = null, LoggerInterface $logger = null)
public function __construct(LoaderInterface $loader, $resource, array $options = [], RequestContext $context = null, LoggerInterface $logger = null, string $defaultLocale = null)
{
$this->loader = $loader;
$this->resource = $resource;
$this->logger = $logger;
$this->context = $context ?: new RequestContext();
$this->context = $context ?? new RequestContext();
$this->setOptions($options);
$this->defaultLocale = $defaultLocale;
}
/**
@@ -107,42 +120,37 @@ class Router implements RouterInterface, RequestMatcherInterface
* * cache_dir: The cache directory (or null to disable caching)
* * debug: Whether to enable debugging or not (false by default)
* * generator_class: The name of a UrlGeneratorInterface implementation
* * generator_base_class: The base class for the dumped generator class
* * generator_cache_class: The class name for the dumped generator class
* * generator_dumper_class: The name of a GeneratorDumperInterface implementation
* * matcher_class: The name of a UrlMatcherInterface implementation
* * matcher_base_class: The base class for the dumped matcher class
* * matcher_dumper_class: The class name for the dumped matcher class
* * matcher_cache_class: The name of a MatcherDumperInterface implementation
* * matcher_dumper_class: The name of a MatcherDumperInterface implementation
* * resource_type: Type hint for the main resource (optional)
* * strict_requirements: Configure strict requirement checking for generators
* implementing ConfigurableRequirementsInterface (default is true)
*
* @param array $options An array of options
*
* @throws \InvalidArgumentException When unsupported option is provided
*/
public function setOptions(array $options)
{
$this->options = array(
$this->options = [
'cache_dir' => null,
'debug' => false,
'generator_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator',
'generator_base_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator',
'generator_dumper_class' => 'Symfony\\Component\\Routing\\Generator\\Dumper\\PhpGeneratorDumper',
'generator_cache_class' => 'ProjectUrlGenerator',
'matcher_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher',
'matcher_base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher',
'matcher_dumper_class' => 'Symfony\\Component\\Routing\\Matcher\\Dumper\\PhpMatcherDumper',
'matcher_cache_class' => 'ProjectUrlMatcher',
'generator_class' => CompiledUrlGenerator::class,
'generator_base_class' => UrlGenerator::class, // deprecated
'generator_dumper_class' => CompiledUrlGeneratorDumper::class,
'generator_cache_class' => 'UrlGenerator', // deprecated
'matcher_class' => CompiledUrlMatcher::class,
'matcher_base_class' => UrlMatcher::class, // deprecated
'matcher_dumper_class' => CompiledUrlMatcherDumper::class,
'matcher_cache_class' => 'UrlMatcher', // deprecated
'resource_type' => null,
'strict_requirements' => true,
);
];
// check option names and live merge, if errors are encountered Exception will be thrown
$invalid = array();
$invalid = [];
foreach ($options as $key => $value) {
if (array_key_exists($key, $this->options)) {
$this->checkDeprecatedOption($key);
if (\array_key_exists($key, $this->options)) {
$this->options[$key] = $value;
} else {
$invalid[] = $key;
@@ -164,10 +172,12 @@ class Router implements RouterInterface, RequestMatcherInterface
*/
public function setOption($key, $value)
{
if (!array_key_exists($key, $this->options)) {
if (!\array_key_exists($key, $this->options)) {
throw new \InvalidArgumentException(sprintf('The Router does not support the "%s" option.', $key));
}
$this->checkDeprecatedOption($key);
$this->options[$key] = $value;
}
@@ -182,10 +192,12 @@ class Router implements RouterInterface, RequestMatcherInterface
*/
public function getOption($key)
{
if (!array_key_exists($key, $this->options)) {
if (!\array_key_exists($key, $this->options)) {
throw new \InvalidArgumentException(sprintf('The Router does not support the "%s" option.', $key));
}
$this->checkDeprecatedOption($key);
return $this->options[$key];
}
@@ -235,7 +247,7 @@ class Router implements RouterInterface, RequestMatcherInterface
/**
* {@inheritdoc}
*/
public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH)
public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH)
{
return $this->getGenerator()->generate($name, $parameters, $referenceType);
}
@@ -263,9 +275,9 @@ class Router implements RouterInterface, RequestMatcherInterface
}
/**
* Gets the UrlMatcher instance associated with this Router.
* Gets the UrlMatcher or RequestMatcher instance associated with this Router.
*
* @return UrlMatcherInterface A UrlMatcherInterface instance
* @return UrlMatcherInterface|RequestMatcherInterface
*/
public function getMatcher()
{
@@ -273,8 +285,14 @@ class Router implements RouterInterface, RequestMatcherInterface
return $this->matcher;
}
$compiled = is_a($this->options['matcher_class'], CompiledUrlMatcher::class, true) && (UrlMatcher::class === $this->options['matcher_base_class'] || RedirectableUrlMatcher::class === $this->options['matcher_base_class']) && is_a($this->options['matcher_dumper_class'], CompiledUrlMatcherDumper::class, true);
if (null === $this->options['cache_dir'] || null === $this->options['matcher_cache_class']) {
$this->matcher = new $this->options['matcher_class']($this->getRouteCollection(), $this->context);
$routes = $this->getRouteCollection();
if ($compiled) {
$routes = (new CompiledUrlMatcherDumper($routes))->getCompiledRoutes();
}
$this->matcher = new $this->options['matcher_class']($routes, $this->context);
if (method_exists($this->matcher, 'addExpressionLanguageProvider')) {
foreach ($this->expressionLanguageProviders as $provider) {
$this->matcher->addExpressionLanguageProvider($provider);
@@ -293,15 +311,19 @@ class Router implements RouterInterface, RequestMatcherInterface
}
}
$options = array(
$options = [
'class' => $this->options['matcher_cache_class'],
'base_class' => $this->options['matcher_base_class'],
);
];
$cache->write($dumper->dump($options), $this->getRouteCollection()->getResources());
}
);
if ($compiled) {
return $this->matcher = new $this->options['matcher_class'](self::getCompiledRoutes($cache->getPath()), $this->context);
}
if (!class_exists($this->options['matcher_cache_class'], false)) {
require_once $cache->getPath();
}
@@ -320,27 +342,37 @@ class Router implements RouterInterface, RequestMatcherInterface
return $this->generator;
}
$compiled = is_a($this->options['generator_class'], CompiledUrlGenerator::class, true) && UrlGenerator::class === $this->options['generator_base_class'] && is_a($this->options['generator_dumper_class'], CompiledUrlGeneratorDumper::class, true);
if (null === $this->options['cache_dir'] || null === $this->options['generator_cache_class']) {
$this->generator = new $this->options['generator_class']($this->getRouteCollection(), $this->context, $this->logger);
$routes = $this->getRouteCollection();
if ($compiled) {
$routes = (new CompiledUrlGeneratorDumper($routes))->getCompiledRoutes();
}
$this->generator = new $this->options['generator_class']($routes, $this->context, $this->logger, $this->defaultLocale);
} else {
$cache = $this->getConfigCacheFactory()->cache($this->options['cache_dir'].'/'.$this->options['generator_cache_class'].'.php',
function (ConfigCacheInterface $cache) {
$dumper = $this->getGeneratorDumperInstance();
$options = array(
$options = [
'class' => $this->options['generator_cache_class'],
'base_class' => $this->options['generator_base_class'],
);
];
$cache->write($dumper->dump($options), $this->getRouteCollection()->getResources());
}
);
if (!class_exists($this->options['generator_cache_class'], false)) {
require_once $cache->getPath();
}
if ($compiled) {
$this->generator = new $this->options['generator_class'](self::getCompiledRoutes($cache->getPath()), $this->context, $this->logger, $this->defaultLocale);
} else {
if (!class_exists($this->options['generator_cache_class'], false)) {
require_once $cache->getPath();
}
$this->generator = new $this->options['generator_cache_class']($this->context, $this->logger);
$this->generator = new $this->options['generator_cache_class']($this->context, $this->logger, $this->defaultLocale);
}
}
if ($this->generator instanceof ConfigurableRequirementsInterface) {
@@ -360,6 +392,11 @@ class Router implements RouterInterface, RequestMatcherInterface
*/
protected function getGeneratorDumperInstance()
{
// For BC, fallback to PhpGeneratorDumper (which is the old default value) if the old UrlGenerator is used with the new default CompiledUrlGeneratorDumper
if (!is_a($this->options['generator_class'], CompiledUrlGenerator::class, true) && is_a($this->options['generator_dumper_class'], CompiledUrlGeneratorDumper::class, true)) {
return new PhpGeneratorDumper($this->getRouteCollection());
}
return new $this->options['generator_dumper_class']($this->getRouteCollection());
}
@@ -368,16 +405,19 @@ class Router implements RouterInterface, RequestMatcherInterface
*/
protected function getMatcherDumperInstance()
{
// For BC, fallback to PhpMatcherDumper (which is the old default value) if the old UrlMatcher is used with the new default CompiledUrlMatcherDumper
if (!is_a($this->options['matcher_class'], CompiledUrlMatcher::class, true) && is_a($this->options['matcher_dumper_class'], CompiledUrlMatcherDumper::class, true)) {
return new PhpMatcherDumper($this->getRouteCollection());
}
return new $this->options['matcher_dumper_class']($this->getRouteCollection());
}
/**
* Provides the ConfigCache factory implementation, falling back to a
* default implementation if necessary.
*
* @return ConfigCacheFactoryInterface
*/
private function getConfigCacheFactory()
private function getConfigCacheFactory(): ConfigCacheFactoryInterface
{
if (null === $this->configCacheFactory) {
$this->configCacheFactory = new ConfigCacheFactory($this->options['debug']);
@@ -385,4 +425,32 @@ class Router implements RouterInterface, RequestMatcherInterface
return $this->configCacheFactory;
}
private function checkDeprecatedOption(string $key)
{
switch ($key) {
case 'generator_base_class':
case 'generator_cache_class':
case 'matcher_base_class':
case 'matcher_cache_class':
@trigger_error(sprintf('Option "%s" given to router %s is deprecated since Symfony 4.3.', $key, static::class), \E_USER_DEPRECATED);
}
}
private static function getCompiledRoutes(string $path): array
{
if ([] === self::$cache && \function_exists('opcache_invalidate') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) || filter_var(\ini_get('opcache.enable_cli'), \FILTER_VALIDATE_BOOLEAN))) {
self::$cache = null;
}
if (null === self::$cache) {
return require $path;
}
if (isset(self::$cache[$path])) {
return self::$cache[$path];
}
return self::$cache[$path] = require $path;
}
}

View File

@@ -26,6 +26,9 @@ interface RouterInterface extends UrlMatcherInterface, UrlGeneratorInterface
/**
* Gets the RouteCollection instance associated with this Router.
*
* WARNING: This method should never be used at runtime as it is SLOW.
* You might use it in a cache warmer though.
*
* @return RouteCollection A RouteCollection instance
*/
public function getRouteCollection();

View File

@@ -1,59 +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\Tests\Annotation;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Routing\Annotation\Route;
class RouteTest extends TestCase
{
/**
* @expectedException \BadMethodCallException
*/
public function testInvalidRouteParameter()
{
$route = new Route(array('foo' => 'bar'));
}
/**
* @expectedException \BadMethodCallException
*/
public function testTryingToSetLocalesDirectly()
{
$route = new Route(array('locales' => array('nl' => 'bar')));
}
/**
* @dataProvider getValidParameters
*/
public function testRouteParameters($parameter, $value, $getter)
{
$route = new Route(array($parameter => $value));
$this->assertEquals($route->$getter(), $value);
}
public function getValidParameters()
{
return array(
array('value', '/Blog', 'getPath'),
array('requirements', array('locale' => 'en'), 'getRequirements'),
array('options', array('compiler_class' => 'RouteCompiler'), 'getOptions'),
array('name', 'blog_index', 'getName'),
array('defaults', array('_controller' => 'MyBlogBundle:Blog:index'), 'getDefaults'),
array('schemes', array('https'), 'getSchemes'),
array('methods', array('GET', 'POST'), 'getMethods'),
array('host', '{locale}.example.com', 'getHost'),
array('condition', 'context.getMethod() == "GET"', 'getCondition'),
array('value', array('nl' => '/hier', 'en' => '/here'), 'getLocalizedPaths'),
);
}
}

View File

@@ -1,27 +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\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Routing\CompiledRoute;
class CompiledRouteTest extends TestCase
{
public function testAccessors()
{
$compiled = new CompiledRoute('prefix', 'regex', array('tokens'), array(), null, array(), array(), array('variables'));
$this->assertEquals('prefix', $compiled->getStaticPrefix(), '__construct() takes a static prefix as its second argument');
$this->assertEquals('regex', $compiled->getRegex(), '__construct() takes a regexp as its third argument');
$this->assertEquals(array('tokens'), $compiled->getTokens(), '__construct() takes an array of tokens as its fourth argument');
$this->assertEquals(array('variables'), $compiled->getVariables(), '__construct() takes an array of variables as its ninth argument');
}
}

View File

@@ -1,36 +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\Tests\DependencyInjection;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Loader\LoaderResolver;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Routing\DependencyInjection\RoutingResolverPass;
class RoutingResolverPassTest extends TestCase
{
public function testProcess()
{
$container = new ContainerBuilder();
$container->register('routing.resolver', LoaderResolver::class);
$container->register('loader1')->addTag('routing.loader');
$container->register('loader2')->addTag('routing.loader');
(new RoutingResolverPass())->process($container);
$this->assertEquals(
array(array('addLoader', array(new Reference('loader1'))), array('addLoader', array(new Reference('loader2')))),
$container->getDefinition('routing.resolver')->getMethodCalls()
);
}
}

View File

@@ -1,16 +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\Tests\Fixtures\AnnotatedClasses;
abstract class AbstractClass
{
}

View File

@@ -1,19 +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\Tests\Fixtures\AnnotatedClasses;
class BarClass
{
public function routeAction($arg1, $arg2 = 'defaultValue2', $arg3 = 'defaultValue3')
{
}
}

View File

@@ -1,19 +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\Tests\Fixtures\AnnotatedClasses;
class BazClass
{
public function __invoke()
{
}
}

View File

@@ -1,16 +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\Tests\Fixtures\AnnotatedClasses;
class FooClass
{
}

View File

@@ -1,13 +0,0 @@
<?php
namespace Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses;
trait FooTrait
{
public function doBar()
{
$baz = self::class;
if (true) {
}
}
}

View File

@@ -1,7 +0,0 @@
<?php
namespace Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures;
abstract class AbstractClassController
{
}

View File

@@ -1,15 +0,0 @@
<?php
namespace Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures;
use Symfony\Component\Routing\Annotation\Route;
class ActionPathController
{
/**
* @Route("/path", name="action")
*/
public function action()
{
}
}

View File

@@ -1,15 +0,0 @@
<?php
namespace Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures;
use Symfony\Component\Routing\Annotation\Route;
class DefaultValueController
{
/**
* @Route("/{default}/path", name="action")
*/
public function action($default = 'value')
{
}
}

View File

@@ -1,15 +0,0 @@
<?php
namespace Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures;
use Symfony\Component\Routing\Annotation\Route;
class ExplicitLocalizedActionPathController
{
/**
* @Route(path={"en": "/path", "nl": "/pad"}, name="action")
*/
public function action()
{
}
}

View File

@@ -1,15 +0,0 @@
<?php
namespace Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/here", name="lol", methods={"GET", "POST"}, schemes={"https"})
*/
class InvokableController
{
public function __invoke()
{
}
}

View File

@@ -1,15 +0,0 @@
<?php
namespace Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route(path={"nl": "/hier", "en": "/here"}, name="action")
*/
class InvokableLocalizedController
{
public function __invoke()
{
}
}

View File

@@ -1,15 +0,0 @@
<?php
namespace Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures;
use Symfony\Component\Routing\Annotation\Route;
class LocalizedActionPathController
{
/**
* @Route(path={"en": "/path", "nl": "/pad"}, name="action")
*/
public function action()
{
}
}

View File

@@ -1,25 +0,0 @@
<?php
namespace Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route(path={"en": "/the/path", "nl": "/het/pad"})
*/
class LocalizedMethodActionControllers
{
/**
* @Route(name="post", methods={"POST"})
*/
public function post()
{
}
/**
* @Route(name="put", methods={"PUT"})
*/
public function put()
{
}
}

View File

@@ -1,18 +0,0 @@
<?php
namespace Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route(path={"nl": "/nl", "en": "/en"})
*/
class LocalizedPrefixLocalizedActionController
{
/**
* @Route(path={"nl": "/actie", "en": "/action"}, name="action")
*/
public function action()
{
}
}

View File

@@ -1,18 +0,0 @@
<?php
namespace Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route(path={"nl": "/nl"})
*/
class LocalizedPrefixMissingLocaleActionController
{
/**
* @Route(path={"nl": "/actie", "en": "/action"}, name="action")
*/
public function action()
{
}
}

View File

@@ -1,18 +0,0 @@
<?php
namespace Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route(path={"nl": "/nl", "en": "/en"})
*/
class LocalizedPrefixMissingRouteLocaleActionController
{
/**
* @Route(path={"nl": "/actie"}, name="action")
*/
public function action()
{
}
}

View File

@@ -1,18 +0,0 @@
<?php
namespace Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route(path={"en": "/en", "nl": "/nl"})
*/
class LocalizedPrefixWithRouteWithoutLocale
{
/**
* @Route("/suffix", name="action")
*/
public function action()
{
}
}

View File

@@ -1,25 +0,0 @@
<?php
namespace Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/the/path")
*/
class MethodActionControllers
{
/**
* @Route(name="post", methods={"POST"})
*/
public function post()
{
}
/**
* @Route(name="put", methods={"PUT"})
*/
public function put()
{
}
}

View File

@@ -1,15 +0,0 @@
<?php
namespace Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures;
use Symfony\Component\Routing\Annotation\Route;
class MissingRouteNameController
{
/**
* @Route("/path")
*/
public function action()
{
}
}

View File

@@ -1,15 +0,0 @@
<?php
namespace Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures;
use Symfony\Component\Routing\Annotation\Route;
class NothingButNameController
{
/**
* @Route(name="action")
*/
public function action()
{
}
}

View File

@@ -1,18 +0,0 @@
<?php
namespace Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/prefix")
*/
class PrefixedActionLocalizedRouteController
{
/**
* @Route(path={"en": "/path", "nl": "/pad"}, name="action")
*/
public function action()
{
}
}

View File

@@ -1,18 +0,0 @@
<?php
namespace Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/prefix", host="frankdejonge.nl", condition="lol=fun")
*/
class PrefixedActionPathController
{
/**
* @Route("/path", name="action")
*/
public function action()
{
}
}

View File

@@ -1,27 +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\Tests\Fixtures\AnnotationFixtures;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/", requirements={"foo", "\d+"})
*/
class RequirementsWithoutPlaceholderNameController
{
/**
* @Route("/{foo}", name="foo", requirements={"foo", "\d+"})
*/
public function foo()
{
}
}

View File

@@ -1,18 +0,0 @@
<?php
namespace Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/prefix")
*/
class RouteWithPrefixController
{
/**
* @Route("/path", name="action")
*/
public function action()
{
}
}

View File

@@ -1,18 +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\Tests\Fixtures;
use Symfony\Component\Routing\CompiledRoute;
class CustomCompiledRoute extends CompiledRoute
{
}

View File

@@ -1,26 +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\Tests\Fixtures;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCompiler;
class CustomRouteCompiler extends RouteCompiler
{
/**
* {@inheritdoc}
*/
public static function compile(Route $route)
{
return new CustomCompiledRoute('', '', array(), array());
}
}

View File

@@ -1,26 +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\Tests\Fixtures;
use Symfony\Component\Config\Util\XmlUtils;
use Symfony\Component\Routing\Loader\XmlFileLoader;
/**
* XmlFileLoader with schema validation turned off.
*/
class CustomXmlFileLoader extends XmlFileLoader
{
protected function loadFile($file)
{
return XmlUtils::loadFile($file, function () { return true; });
}
}

View File

@@ -1,24 +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\Tests\Fixtures\OtherAnnotatedClasses;
trait AnonymousClassInTrait
{
public function test()
{
return new class() {
public function foo()
{
}
};
}
}

View File

@@ -1,3 +0,0 @@
class NoStartTagClass
{
}

View File

@@ -1,19 +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\Tests\Fixtures\OtherAnnotatedClasses;
class VariadicClass
{
public function routeAction(...$params)
{
}
}

View File

@@ -1,30 +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\Tests\Fixtures;
use Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface;
use Symfony\Component\Routing\Matcher\UrlMatcher;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class RedirectableUrlMatcher extends UrlMatcher implements RedirectableUrlMatcherInterface
{
public function redirect($path, $route, $scheme = null)
{
return array(
'_controller' => 'Some controller reference...',
'path' => $path,
'scheme' => $scheme,
);
}
}

View File

@@ -1,3 +0,0 @@
blog_show:
path: /blog/{slug}
defaults: { _controller: "MyBundle:Blog:show" }

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd">
<import resource="routing.xml">
<default key="_controller">FrameworkBundle:Template:template</default>
</import>
</routes>

View File

@@ -1,4 +0,0 @@
_static:
resource: routing.yml
defaults:
_controller: FrameworkBundle:Template:template

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd">
<import resource="routing.xml" controller="FrameworkBundle:Template:template" />
</routes>

View File

@@ -1,3 +0,0 @@
_static:
resource: routing.yml
controller: FrameworkBundle:Template:template

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd">
<import resource="routing.xml" controller="FrameworkBundle:Template:template">
<default key="_controller">AppBundle:Blog:index</default>
</import>
</routes>

View File

@@ -1,5 +0,0 @@
_static:
resource: routing.yml
controller: FrameworkBundle:Template:template
defaults:
_controller: AppBundle:Homepage:show

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="app_blog" path="/blog" controller="AppBundle:Homepage:show">
<default key="_controller">AppBundle:Blog:index</default>
</route>
</routes>

View File

@@ -1,5 +0,0 @@
app_blog:
path: /blog
controller: AppBundle:Homepage:show
defaults:
_controller: AppBundle:Blog:index

View File

@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="app_homepage" path="/" controller="AppBundle:Homepage:show" />
<route id="app_blog" path="/blog">
<default key="_controller">AppBundle:Blog:list</default>
</route>
<route id="app_logout" path="/logout" />
</routes>

View File

@@ -1,11 +0,0 @@
app_homepage:
path: /
controller: AppBundle:Homepage:show
app_blog:
path: /blog
defaults:
_controller: AppBundle:Blog:list
app_logout:
path: /logout

View File

@@ -1,2 +0,0 @@
route1:
path: /route/1

View File

@@ -1,2 +0,0 @@
route2:
path: /route/2

View File

@@ -1,2 +0,0 @@
route3:
path: /route/3

View File

@@ -1,3 +0,0 @@
_directory:
resource: "../directory"
type: directory

View File

@@ -1,18 +0,0 @@
<?php
use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherTrait;
use Symfony\Component\Routing\RequestContext;
/**
* This class has been auto-generated
* by the Symfony Routing Component.
*/
class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
{
use PhpMatcherTrait;
public function __construct(RequestContext $context)
{
$this->context = $context;
}
}

View File

@@ -1,113 +0,0 @@
<?php
use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherTrait;
use Symfony\Component\Routing\RequestContext;
/**
* This class has been auto-generated
* by the Symfony Routing Component.
*/
class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
{
use PhpMatcherTrait;
public function __construct(RequestContext $context)
{
$this->context = $context;
$this->matchHost = true;
$this->staticRoutes = array(
'/test/baz' => array(array(array('_route' => 'baz'), null, null, null, false, null)),
'/test/baz.html' => array(array(array('_route' => 'baz2'), null, null, null, false, null)),
'/test/baz3' => array(array(array('_route' => 'baz3'), null, null, null, true, null)),
'/foofoo' => array(array(array('_route' => 'foofoo', 'def' => 'test'), null, null, null, false, null)),
'/spa ce' => array(array(array('_route' => 'space'), null, null, null, false, null)),
'/multi/new' => array(array(array('_route' => 'overridden2'), null, null, null, false, null)),
'/multi/hey' => array(array(array('_route' => 'hey'), null, null, null, true, null)),
'/ababa' => array(array(array('_route' => 'ababa'), null, null, null, false, null)),
'/route1' => array(array(array('_route' => 'route1'), 'a.example.com', null, null, false, null)),
'/c2/route2' => array(array(array('_route' => 'route2'), 'a.example.com', null, null, false, null)),
'/route4' => array(array(array('_route' => 'route4'), 'a.example.com', null, null, false, null)),
'/c2/route3' => array(array(array('_route' => 'route3'), 'b.example.com', null, null, false, null)),
'/route5' => array(array(array('_route' => 'route5'), 'c.example.com', null, null, false, null)),
'/route6' => array(array(array('_route' => 'route6'), null, null, null, false, null)),
'/route11' => array(array(array('_route' => 'route11'), '#^(?P<var1>[^\\.]++)\\.example\\.com$#sDi', null, null, false, null)),
'/route12' => array(array(array('_route' => 'route12', 'var1' => 'val'), '#^(?P<var1>[^\\.]++)\\.example\\.com$#sDi', null, null, false, null)),
'/route17' => array(array(array('_route' => 'route17'), null, null, null, false, null)),
);
$this->regexpList = array(
0 => '{^(?'
.'|(?:(?:[^./]*+\\.)++)(?'
.'|/foo/(baz|symfony)(*:47)'
.'|/bar(?'
.'|/([^/]++)(*:70)'
.'|head/([^/]++)(*:90)'
.')'
.'|/test/([^/]++)(?'
.'|(*:115)'
.')'
.'|/([\']+)(*:131)'
.'|/a/(?'
.'|b\'b/([^/]++)(?'
.'|(*:160)'
.'|(*:168)'
.')'
.'|(.*)(*:181)'
.'|b\'b/([^/]++)(?'
.'|(*:204)'
.'|(*:212)'
.')'
.')'
.'|/multi/hello(?:/([^/]++))?(*:248)'
.'|/([^/]++)/b/([^/]++)(?'
.'|(*:279)'
.'|(*:287)'
.')'
.'|/aba/([^/]++)(*:309)'
.')|(?i:([^\\.]++)\\.example\\.com)\\.(?'
.'|/route1(?'
.'|3/([^/]++)(*:371)'
.'|4/([^/]++)(*:389)'
.')'
.')|(?i:c\\.example\\.com)\\.(?'
.'|/route15/([^/]++)(*:441)'
.')|(?:(?:[^./]*+\\.)++)(?'
.'|/route16/([^/]++)(*:489)'
.'|/a/(?'
.'|a\\.\\.\\.(*:510)'
.'|b/(?'
.'|([^/]++)(*:531)'
.'|c/([^/]++)(*:549)'
.')'
.')'
.')'
.')(?:/?)$}sD',
);
$this->dynamicRoutes = array(
47 => array(array(array('_route' => 'foo', 'def' => 'test'), array('bar'), null, null, false, null)),
70 => array(array(array('_route' => 'bar'), array('foo'), array('GET' => 0, 'HEAD' => 1), null, false, null)),
90 => array(array(array('_route' => 'barhead'), array('foo'), array('GET' => 0), null, false, null)),
115 => array(
array(array('_route' => 'baz4'), array('foo'), null, null, true, null),
array(array('_route' => 'baz5'), array('foo'), array('POST' => 0), null, true, null),
array(array('_route' => 'baz.baz6'), array('foo'), array('PUT' => 0), null, true, null),
),
131 => array(array(array('_route' => 'quoter'), array('quoter'), null, null, false, null)),
160 => array(array(array('_route' => 'foo1'), array('foo'), array('PUT' => 0), null, false, null)),
168 => array(array(array('_route' => 'bar1'), array('bar'), null, null, false, null)),
181 => array(array(array('_route' => 'overridden'), array('var'), null, null, false, null)),
204 => array(array(array('_route' => 'foo2'), array('foo1'), null, null, false, null)),
212 => array(array(array('_route' => 'bar2'), array('bar1'), null, null, false, null)),
248 => array(array(array('_route' => 'helloWorld', 'who' => 'World!'), array('who'), null, null, false, null)),
279 => array(array(array('_route' => 'foo3'), array('_locale', 'foo'), null, null, false, null)),
287 => array(array(array('_route' => 'bar3'), array('_locale', 'bar'), null, null, false, null)),
309 => array(array(array('_route' => 'foo4'), array('foo'), null, null, false, null)),
371 => array(array(array('_route' => 'route13'), array('var1', 'name'), null, null, false, null)),
389 => array(array(array('_route' => 'route14', 'var1' => 'val'), array('var1', 'name'), null, null, false, null)),
441 => array(array(array('_route' => 'route15'), array('name'), null, null, false, null)),
489 => array(array(array('_route' => 'route16', 'var1' => 'val'), array('name'), null, null, false, null)),
510 => array(array(array('_route' => 'a'), array(), null, null, false, null)),
531 => array(array(array('_route' => 'b'), array('var'), null, null, false, null)),
549 => array(array(array('_route' => 'c'), array('var'), null, null, false, null)),
);
}
}

Some files were not shown because too many files have changed in this diff Show More