updated backend

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

View file

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

View file

@ -22,14 +22,18 @@ namespace Symfony\Component\Routing\Annotation;
class Route
{
private $path;
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;
private $locale;
private $format;
private $utf8;
/**
* @param array $data An array of key/value parameters
@ -38,11 +42,35 @@ 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)));
}
if (isset($data['value'])) {
$data['path'] = $data['value'];
$data[\is_array($data['value']) ? 'localized_paths' : 'path'] = $data['value'];
unset($data['value']);
}
if (isset($data['path']) && \is_array($data['path'])) {
$data['localized_paths'] = $data['path'];
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)) {
@ -62,6 +90,16 @@ class Route
return $this->path;
}
public function setLocalizedPaths(array $localizedPaths)
{
$this->localizedPaths = $localizedPaths;
}
public function getLocalizedPaths(): array
{
return $this->localizedPaths;
}
public function setHost($pattern)
{
$this->host = $pattern;
@ -114,7 +152,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()
@ -124,7 +162,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,37 @@
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
-----
* added fallback to cultureless locale for internationalized routes
4.0.0
-----
* dropped support for using UTF-8 route patterns without using the `utf8` option
* dropped support for using UTF-8 route requirements without using the `utf8` option
3.4.0
-----
@ -41,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:
@ -49,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
@ -57,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
-----
@ -114,7 +145,7 @@ CHANGELOG
```php
$route = new Route();
$route->setPath('/article/{id}');
$route->setMethods(array('POST', 'PUT'));
$route->setMethods(['POST', 'PUT']);
$route->setSchemes('https');
```
@ -169,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
@ -197,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,9 +37,9 @@ 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($staticPrefix, $regex, array $tokens, array $pathVariables, $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 = (string) $staticPrefix;
$this->staticPrefix = $staticPrefix;
$this->regex = $regex;
$this->tokens = $tokens;
$this->pathVariables = $pathVariables;
@ -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,20 +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()
{
if (\PHP_VERSION_ID >= 70000) {
$data = unserialize($serialized, array('allowed_classes' => false));
} else {
$data = unserialize($serialized);
}
return serialize($this->__serialize());
}
public function __unserialize(array $data): void
{
$this->variables = $data['vars'];
$this->staticPrefix = $data['path_prefix'];
$this->regex = $data['path_regex'];
@ -87,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

@ -28,7 +28,7 @@ class RoutingResolverPass implements CompilerPassInterface
private $resolverServiceId;
private $loaderTag;
public function __construct($resolverServiceId = 'routing.resolver', $loaderTag = 'routing.loader')
public function __construct(string $resolverServiceId = 'routing.resolver', string $loaderTag = 'routing.loader')
{
$this->resolverServiceId = $resolverServiceId;
$this->loaderTag = $loaderTag;
@ -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

@ -16,6 +16,6 @@ namespace Symfony\Component\Routing\Exception;
*
* @author Alexandre Salomé <alexandre.salome@gmail.com>
*/
interface ExceptionInterface
interface ExceptionInterface extends \Throwable
{
}

View file

@ -20,9 +20,9 @@ namespace Symfony\Component\Routing\Exception;
*/
class MethodNotAllowedException extends \RuntimeException implements ExceptionInterface
{
protected $allowedMethods = array();
protected $allowedMethods = [];
public function __construct(array $allowedMethods, $message = null, $code = 0, \Exception $previous = null)
public function __construct(array $allowedMethods, string $message = null, int $code = 0, \Throwable $previous = null)
{
$this->allowedMethods = array_map('strtoupper', $allowedMethods);

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

@ -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,11 +11,17 @@
namespace Symfony\Component\Routing\Generator\Dumper;
@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
{
@ -31,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
@ -52,11 +58,13 @@ use Psr\Log\LoggerInterface;
class {$options['class']} extends {$options['base_class']}
{
private static \$declaredRoutes;
private \$defaultLocale;
public function __construct(RequestContext \$context, LoggerInterface \$logger = null)
public function __construct(RequestContext \$context, LoggerInterface \$logger = null, string \$defaultLocale = null)
{
\$this->context = \$context;
\$this->logger = \$logger;
\$this->defaultLocale = \$defaultLocale;
if (null === self::\$declaredRoutes) {
self::\$declaredRoutes = {$this->generateDeclaredRoutes()};
}
@ -71,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();
@ -88,23 +94,35 @@ EOF;
$properties[] = $compiledRoute->getHostTokens();
$properties[] = $route->getSchemes();
$routes .= sprintf(" '%s' => %s,\n", $name, str_replace("\n", '', var_export($properties, true)));
$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')
?: $this->defaultLocale;
if (null !== $locale && null !== $name) {
do {
if ((self::$declaredRoutes[$name.'.'.$locale][1]['_canonical_route'] ?? null) === $name) {
unset($parameters['_locale']);
$name .= '.'.$locale;
break;
}
} while (false !== $locale = strstr($locale, '_', true));
}
if (!isset(self::$declaredRoutes[$name])) {
throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name));
}

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;
@ -37,6 +51,8 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
protected $logger;
private $defaultLocale;
/**
* This array defines the characters (besides alphanumeric ones) that will not be percent-encoded in the path segment of the generated URL.
*
@ -45,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
@ -63,13 +79,14 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
'%21' => '!',
'%2A' => '*',
'%7C' => '|',
);
];
public function __construct(RouteCollection $routes, RequestContext $context, LoggerInterface $logger = null)
public function __construct(RouteCollection $routes, RequestContext $context, LoggerInterface $logger = null, string $defaultLocale = null)
{
$this->routes = $routes;
$this->context = $context;
$this->logger = $logger;
$this->defaultLocale = $defaultLocale;
}
/**
@ -107,9 +124,23 @@ 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)
{
if (null === $route = $this->routes->get($name)) {
$route = null;
$locale = $parameters['_locale']
?? $this->context->getParameter('_locale')
?: $this->defaultLocale;
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));
}
if (null === $route = $route ?? $this->routes->get($name)) {
throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name));
}
@ -123,8 +154,10 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
* @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);
@ -139,21 +172,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 {
@ -173,7 +210,7 @@ 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/'));
$url = strtr($url, ['/../' => '/%2E%2E/', '/./' => '/%2E/']);
if ('/..' === substr($url, -3)) {
$url = substr($url, 0, -2).'%2E%2E';
} elseif ('/.' === substr($url, -2)) {
@ -195,16 +232,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;
@ -221,16 +259,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) {
@ -245,10 +285,7 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
});
// extract fragment
$fragment = '';
if (isset($defaults['_fragment'])) {
$fragment = $defaults['_fragment'];
}
$fragment = $defaults['_fragment'] ?? '';
if (isset($extra['_fragment'])) {
$fragment = $extra['_fragment'];
@ -256,13 +293,11 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
}
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' => '/'));
$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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,159 +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\Matcher\Dumper;
/**
* Collection of routes.
*
* @author Arnaud Le Blanc <arnaud.lb@gmail.com>
*
* @internal
*/
class DumperCollection implements \IteratorAggregate
{
/**
* @var DumperCollection|null
*/
private $parent;
/**
* @var DumperCollection[]|DumperRoute[]
*/
private $children = array();
/**
* @var array
*/
private $attributes = array();
/**
* Returns the children routes and collections.
*
* @return self[]|DumperRoute[]
*/
public function all()
{
return $this->children;
}
/**
* Adds a route or collection.
*
* @param DumperRoute|DumperCollection The route or collection
*/
public function add($child)
{
if ($child instanceof self) {
$child->setParent($this);
}
$this->children[] = $child;
}
/**
* Sets children.
*
* @param array $children The children
*/
public function setAll(array $children)
{
foreach ($children as $child) {
if ($child instanceof self) {
$child->setParent($this);
}
}
$this->children = $children;
}
/**
* Returns an iterator over the children.
*
* @return \Iterator|DumperCollection[]|DumperRoute[] The iterator
*/
public function getIterator()
{
return new \ArrayIterator($this->children);
}
/**
* Returns the root of the collection.
*
* @return self The root collection
*/
public function getRoot()
{
return (null !== $this->parent) ? $this->parent->getRoot() : $this;
}
/**
* Returns the parent collection.
*
* @return self|null The parent collection or null if the collection has no parent
*/
protected function getParent()
{
return $this->parent;
}
/**
* Sets the parent collection.
*/
protected function setParent(self $parent)
{
$this->parent = $parent;
}
/**
* Returns true if the attribute is defined.
*
* @param string $name The attribute name
*
* @return bool true if the attribute is defined, false otherwise
*/
public function hasAttribute($name)
{
return array_key_exists($name, $this->attributes);
}
/**
* Returns an attribute by name.
*
* @param string $name The attribute name
* @param mixed $default Default value is the attribute doesn't exist
*
* @return mixed The attribute value
*/
public function getAttribute($name, $default = null)
{
return $this->hasAttribute($name) ? $this->attributes[$name] : $default;
}
/**
* Sets an attribute by name.
*
* @param string $name The attribute name
* @param mixed $value The attribute value
*/
public function setAttribute($name, $value)
{
$this->attributes[$name] = $value;
}
/**
* Sets multiple attributes.
*
* @param array $attributes The attributes
*/
public function setAttributes($attributes)
{
$this->attributes = $attributes;
}
}

View file

@ -1,57 +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\Matcher\Dumper;
use Symfony\Component\Routing\Route;
/**
* Container for a Route.
*
* @author Arnaud Le Blanc <arnaud.lb@gmail.com>
*
* @internal
*/
class DumperRoute
{
private $name;
private $route;
/**
* @param string $name The route name
* @param Route $route The route
*/
public function __construct($name, Route $route)
{
$this->name = $name;
$this->route = $route;
}
/**
* Returns the route name.
*
* @return string The route name
*/
public function getName()
{
return $this->name;
}
/**
* Returns the route.
*
* @return Route The route
*/
public function getRoute()
{
return $this->route;
}
}

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.
@ -22,16 +19,12 @@ use Symfony\Component\Routing\RouteCollection;
* @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>
*
* @deprecated since Symfony 4.3, use CompiledUrlMatcherDumper instead.
*/
class PhpMatcherDumper extends MatcherDumper
class PhpMatcherDumper extends CompiledUrlMatcherDumper
{
private $expressionLanguage;
/**
* @var ExpressionFunctionProviderInterface[]
*/
private $expressionLanguageProviders = array();
/**
* Dumps a set of routes to a PHP class.
*
@ -44,22 +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']);
$supportsRedirections = isset($interfaces['Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcherInterface']);
$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\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherTrait;
use Symfony\Component\Routing\RequestContext;
/**
@ -68,362 +63,13 @@ use Symfony\Component\Routing\RequestContext;
*/
class {$options['class']} extends {$options['base_class']}
{
use CompiledUrlMatcherTrait;
public function __construct(RequestContext \$context)
{
\$this->context = \$context;
}
{$this->generateMatchMethod($supportsRedirections)}
\$this->context = \$context;{$code} }
}
EOF;
}
public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider)
{
$this->expressionLanguageProviders[] = $provider;
}
/**
* Generates the code for the match method implementing UrlMatcherInterface.
*
* @param bool $supportsRedirections Whether redirections are supported by the base class
*
* @return string Match method as PHP code
*/
private function generateMatchMethod($supportsRedirections)
{
$code = rtrim($this->compileRoutes($this->getRoutes(), $supportsRedirections), "\n");
return <<<EOF
public function match(\$rawPathinfo)
{
\$allow = array();
\$pathinfo = rawurldecode(\$rawPathinfo);
\$trimmedPathinfo = rtrim(\$pathinfo, '/');
\$context = \$this->context;
\$request = \$this->request ?: \$this->createRequest(\$pathinfo);
\$requestMethod = \$canonicalMethod = \$context->getMethod();
if ('HEAD' === \$requestMethod) {
\$canonicalMethod = 'GET';
}
$code
throw 0 < count(\$allow) ? new MethodNotAllowedException(array_unique(\$allow)) : new ResourceNotFoundException();
}
EOF;
}
/**
* Generates PHP code to match a RouteCollection with all its routes.
*
* @param RouteCollection $routes A RouteCollection instance
* @param bool $supportsRedirections Whether redirections are supported by the base class
*
* @return string PHP code
*/
private function compileRoutes(RouteCollection $routes, $supportsRedirections)
{
$fetchedHost = false;
$groups = $this->groupRoutesByHostRegex($routes);
$code = '';
foreach ($groups as $collection) {
if (null !== $regex = $collection->getAttribute('host_regex')) {
if (!$fetchedHost) {
$code .= " \$host = \$context->getHost();\n\n";
$fetchedHost = true;
}
$code .= sprintf(" if (preg_match(%s, \$host, \$hostMatches)) {\n", var_export($regex, true));
}
$tree = $this->buildStaticPrefixCollection($collection);
$groupCode = $this->compileStaticPrefixRoutes($tree, $supportsRedirections);
if (null !== $regex) {
// apply extra indention at each line (except empty ones)
$groupCode = preg_replace('/^.{2,}$/m', ' $0', $groupCode);
$code .= $groupCode;
$code .= " }\n\n";
} else {
$code .= $groupCode;
}
}
// used to display the Welcome Page in apps that don't define a homepage
$code .= " if ('/' === \$pathinfo && !\$allow) {\n";
$code .= " throw new Symfony\Component\Routing\Exception\NoConfigurationException();\n";
$code .= " }\n";
return $code;
}
private function buildStaticPrefixCollection(DumperCollection $collection)
{
$prefixCollection = new StaticPrefixCollection();
foreach ($collection as $dumperRoute) {
$prefix = $dumperRoute->getRoute()->compile()->getStaticPrefix();
$prefixCollection->addRoute($prefix, $dumperRoute);
}
$prefixCollection->optimizeGroups();
return $prefixCollection;
}
/**
* Generates PHP code to match a tree of routes.
*
* @param StaticPrefixCollection $collection A StaticPrefixCollection instance
* @param bool $supportsRedirections Whether redirections are supported by the base class
* @param string $ifOrElseIf either "if" or "elseif" to influence chaining
*
* @return string PHP code
*/
private function compileStaticPrefixRoutes(StaticPrefixCollection $collection, $supportsRedirections, $ifOrElseIf = 'if')
{
$code = '';
$prefix = $collection->getPrefix();
if (!empty($prefix) && '/' !== $prefix) {
$code .= sprintf(" %s (0 === strpos(\$pathinfo, %s)) {\n", $ifOrElseIf, var_export($prefix, true));
}
$ifOrElseIf = 'if';
foreach ($collection->getItems() as $route) {
if ($route instanceof StaticPrefixCollection) {
$code .= $this->compileStaticPrefixRoutes($route, $supportsRedirections, $ifOrElseIf);
$ifOrElseIf = 'elseif';
} else {
$code .= $this->compileRoute($route[1]->getRoute(), $route[1]->getName(), $supportsRedirections, $prefix)."\n";
$ifOrElseIf = 'if';
}
}
if (!empty($prefix) && '/' !== $prefix) {
$code .= " }\n\n";
// apply extra indention at each line (except empty ones)
$code = preg_replace('/^.{2,}$/m', ' $0', $code);
}
return $code;
}
/**
* Compiles a single Route to PHP code used to match it against the path info.
*
* @param Route $route A Route instance
* @param string $name The name of the Route
* @param bool $supportsRedirections Whether redirections are supported by the base class
* @param string|null $parentPrefix The prefix of the parent collection used to optimize the code
*
* @return string PHP code
*
* @throws \LogicException
*/
private function compileRoute(Route $route, $name, $supportsRedirections, $parentPrefix = null)
{
$code = '';
$compiledRoute = $route->compile();
$conditions = array();
$hasTrailingSlash = false;
$matches = false;
$hostMatches = false;
$methods = $route->getMethods();
$supportsTrailingSlash = $supportsRedirections && (!$methods || \in_array('GET', $methods));
$regex = $compiledRoute->getRegex();
if (!\count($compiledRoute->getPathVariables()) && false !== preg_match('#^(.)\^(?P<url>.*?)\$\1#'.('u' === substr($regex, -1) ? 'u' : ''), $regex, $m)) {
if ($supportsTrailingSlash && '/' === substr($m['url'], -1)) {
$conditions[] = sprintf('%s === $trimmedPathinfo', var_export(rtrim(str_replace('\\', '', $m['url']), '/'), true));
$hasTrailingSlash = true;
} else {
$conditions[] = sprintf('%s === $pathinfo', var_export(str_replace('\\', '', $m['url']), true));
}
} else {
if ($compiledRoute->getStaticPrefix() && $compiledRoute->getStaticPrefix() !== $parentPrefix) {
$conditions[] = sprintf('0 === strpos($pathinfo, %s)', var_export($compiledRoute->getStaticPrefix(), true));
}
if ($supportsTrailingSlash && $pos = strpos($regex, '/$')) {
$regex = substr($regex, 0, $pos).'/?$'.substr($regex, $pos + 2);
$hasTrailingSlash = true;
}
$conditions[] = sprintf('preg_match(%s, $pathinfo, $matches)', var_export($regex, true));
$matches = true;
}
if ($compiledRoute->getHostVariables()) {
$hostMatches = true;
}
if ($route->getCondition()) {
$conditions[] = $this->getExpressionLanguage()->compile($route->getCondition(), array('context', 'request'));
}
$conditions = implode(' && ', $conditions);
$code .= <<<EOF
// $name
if ($conditions) {
EOF;
$gotoname = 'not_'.preg_replace('/[^A-Za-z0-9_]/', '', $name);
// the offset where the return value is appended below, with indendation
$retOffset = 12 + \strlen($code);
// optimize parameters array
if ($matches || $hostMatches) {
$vars = array();
if ($hostMatches) {
$vars[] = '$hostMatches';
}
if ($matches) {
$vars[] = '$matches';
}
$vars[] = "array('_route' => '$name')";
$code .= sprintf(
" \$ret = \$this->mergeDefaults(array_replace(%s), %s);\n",
implode(', ', $vars),
str_replace("\n", '', var_export($route->getDefaults(), true))
);
} elseif ($route->getDefaults()) {
$code .= sprintf(" \$ret = %s;\n", str_replace("\n", '', var_export(array_replace($route->getDefaults(), array('_route' => $name)), true)));
} else {
$code .= sprintf(" \$ret = array('_route' => '%s');\n", $name);
}
if ($hasTrailingSlash) {
$code .= <<<EOF
if ('/' === substr(\$pathinfo, -1)) {
// no-op
} elseif ('GET' !== \$canonicalMethod) {
goto $gotoname;
} else {
return array_replace(\$ret, \$this->redirect(\$rawPathinfo.'/', '$name'));
}
EOF;
}
if ($methods) {
$methodVariable = \in_array('GET', $methods) ? '$canonicalMethod' : '$requestMethod';
$methods = implode("', '", $methods);
}
if ($schemes = $route->getSchemes()) {
if (!$supportsRedirections) {
throw new \LogicException('The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface.');
}
$schemes = str_replace("\n", '', var_export(array_flip($schemes), true));
if ($methods) {
$code .= <<<EOF
\$requiredSchemes = $schemes;
\$hasRequiredScheme = isset(\$requiredSchemes[\$context->getScheme()]);
if (!in_array($methodVariable, array('$methods'))) {
if (\$hasRequiredScheme) {
\$allow = array_merge(\$allow, array('$methods'));
}
goto $gotoname;
}
if (!\$hasRequiredScheme) {
if ('GET' !== \$canonicalMethod) {
goto $gotoname;
}
return array_replace(\$ret, \$this->redirect(\$rawPathinfo, '$name', key(\$requiredSchemes)));
}
EOF;
} else {
$code .= <<<EOF
\$requiredSchemes = $schemes;
if (!isset(\$requiredSchemes[\$context->getScheme()])) {
if ('GET' !== \$canonicalMethod) {
goto $gotoname;
}
return array_replace(\$ret, \$this->redirect(\$rawPathinfo, '$name', key(\$requiredSchemes)));
}
EOF;
}
} elseif ($methods) {
$code .= <<<EOF
if (!in_array($methodVariable, array('$methods'))) {
\$allow = array_merge(\$allow, array('$methods'));
goto $gotoname;
}
EOF;
}
if ($hasTrailingSlash || $schemes || $methods) {
$code .= " return \$ret;\n";
} else {
$code = substr_replace($code, 'return', $retOffset, 6);
}
$code .= " }\n";
if ($hasTrailingSlash || $schemes || $methods) {
$code .= " $gotoname:\n";
}
return $code;
}
/**
* Groups consecutive routes having the same host regex.
*
* The result is a collection of collections of routes having the same host regex.
*
* @param RouteCollection $routes A flat RouteCollection
*
* @return DumperCollection A collection with routes grouped by host regex in sub-collections
*/
private function groupRoutesByHostRegex(RouteCollection $routes)
{
$groups = new DumperCollection();
$currentGroup = new DumperCollection();
$currentGroup->setAttribute('host_regex', null);
$groups->add($currentGroup);
foreach ($routes as $name => $route) {
$hostRegex = $route->compile()->getHostRegex();
if ($currentGroup->getAttribute('host_regex') !== $hostRegex) {
$currentGroup = new DumperCollection();
$currentGroup->setAttribute('host_regex', $hostRegex);
$groups->add($currentGroup);
}
$currentGroup->add(new DumperRoute($name, $route));
}
return $groups;
}
private function getExpressionLanguage()
{
if (null === $this->expressionLanguage) {
if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
}
$this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders);
}
return $this->expressionLanguage;
}
}

View file

@ -11,44 +11,49 @@
namespace Symfony\Component\Routing\Matcher\Dumper;
use Symfony\Component\Routing\RouteCollection;
/**
* Prefix tree of routes preserving routes order.
*
* @author Frank de Jonge <info@frankdejonge.nl>
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
class StaticPrefixCollection
{
/**
* @var string
*/
private $prefix;
/**
* @var array[]|StaticPrefixCollection[]
* @var string[]
*/
private $items = array();
private $staticPrefixes = [];
/**
* @var int
* @var string[]
*/
private $matchStart = 0;
private $prefixes = [];
public function __construct($prefix = '')
/**
* @var array[]|self[]
*/
private $items = [];
public function __construct(string $prefix = '/')
{
$this->prefix = $prefix;
}
public function getPrefix()
public function getPrefix(): string
{
return $this->prefix;
}
/**
* @return mixed[]|StaticPrefixCollection[]
* @return array[]|self[]
*/
public function getItems()
public function getRoutes(): array
{
return $this->items;
}
@ -56,183 +61,142 @@ class StaticPrefixCollection
/**
* Adds a route to a group.
*
* @param string $prefix
* @param mixed $route
* @param array|self $route
*/
public function addRoute($prefix, $route)
public function addRoute(string $prefix, $route)
{
$prefix = '/' === $prefix ? $prefix : rtrim($prefix, '/');
$this->guardAgainstAddingNotAcceptedRoutes($prefix);
list($prefix, $staticPrefix) = $this->getCommonPrefix($prefix, $prefix);
if ($this->prefix === $prefix) {
// When a prefix is exactly the same as the base we move up the match start position.
// This is needed because otherwise routes that come afterwards have higher precedence
// than a possible regular expression, which goes against the input order sorting.
$this->items[] = array($prefix, $route);
$this->matchStart = \count($this->items);
for ($i = \count($this->items) - 1; 0 <= $i; --$i) {
$item = $this->items[$i];
return;
}
list($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
if ($this->prefix !== $staticPrefix && $this->prefix !== $this->staticPrefixes[$i]) {
// the new route and the previous one have exclusive static prefixes
continue;
}
if ($this->prefix === $staticPrefix && $this->prefix === $this->staticPrefixes[$i]) {
// the new route and the previous one have no static prefix
break;
}
if ($this->prefixes[$i] !== $this->staticPrefixes[$i] && $this->prefix === $this->staticPrefixes[$i]) {
// the previous route is non-static and has no static prefix
break;
}
if ($prefix !== $staticPrefix && $this->prefix === $staticPrefix) {
// the new route is non-static and has no static prefix
break;
}
foreach ($this->items as $i => $item) {
if ($i < $this->matchStart) {
continue;
}
if ($item instanceof self && $item->accepts($prefix)) {
if ($item instanceof self && $this->prefixes[$i] === $commonPrefix) {
// the new route is a child of a previous one, let's nest it
$item->addRoute($prefix, $route);
} 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 = [$this->items[$i], $route];
return;
$this->staticPrefixes[$i] = $commonStaticPrefix;
$this->prefixes[$i] = $commonPrefix;
$this->items[$i] = $child;
}
$group = $this->groupWithItem($item, $prefix, $route);
if ($group instanceof self) {
$this->items[$i] = $group;
return;
}
return;
}
// No optimised case was found, in this case we simple add the route for possible
// grouping when new routes are added.
$this->items[] = array($prefix, $route);
$this->staticPrefixes[] = $staticPrefix;
$this->prefixes[] = $prefix;
$this->items[] = $route;
}
/**
* Tries to combine a route with another route or group.
*
* @param StaticPrefixCollection|array $item
* @param string $prefix
* @param mixed $route
*
* @return null|StaticPrefixCollection
* Linearizes back a set of nested routes into a collection.
*/
private function groupWithItem($item, $prefix, $route)
public function populateCollection(RouteCollection $routes): RouteCollection
{
$itemPrefix = $item instanceof self ? $item->prefix : $item[0];
$commonPrefix = $this->detectCommonPrefix($prefix, $itemPrefix);
if (!$commonPrefix) {
return;
foreach ($this->items as $route) {
if ($route instanceof self) {
$route->populateCollection($routes);
} else {
$routes->add(...$route);
}
}
$child = new self($commonPrefix);
if ($item instanceof self) {
$child->items = array($item);
} else {
$child->addRoute($item[0], $item[1]);
}
$child->addRoute($prefix, $route);
return $child;
return $routes;
}
/**
* Checks whether a prefix can be contained within the group.
* Gets the full and static common prefixes between two route patterns.
*
* @param string $prefix
*
* @return bool Whether a prefix could belong in a given group
* The static prefix stops at last at the first opening bracket.
*/
private function accepts($prefix)
{
return '' === $this->prefix || 0 === strpos($prefix, $this->prefix);
}
/**
* Detects whether there's a common prefix relative to the group prefix and returns it.
*
* @param string $prefix
* @param string $anotherPrefix
*
* @return false|string A common prefix, longer than the base/group prefix, or false when none available
*/
private function detectCommonPrefix($prefix, $anotherPrefix)
private function getCommonPrefix(string $prefix, string $anotherPrefix): array
{
$baseLength = \strlen($this->prefix);
$commonLength = $baseLength;
$end = min(\strlen($prefix), \strlen($anotherPrefix));
$staticLength = null;
set_error_handler([__CLASS__, 'handleError']);
for ($i = $baseLength; $i <= $end; ++$i) {
if (substr($prefix, 0, $i) !== substr($anotherPrefix, 0, $i)) {
for ($i = $baseLength; $i < $end && $prefix[$i] === $anotherPrefix[$i]; ++$i) {
if ('(' === $prefix[$i]) {
$staticLength = $staticLength ?? $i;
for ($j = 1 + $i, $n = 1; $j < $end && 0 < $n; ++$j) {
if ($prefix[$j] !== $anotherPrefix[$j]) {
break 2;
}
if ('(' === $prefix[$j]) {
++$n;
} elseif (')' === $prefix[$j]) {
--$n;
} elseif ('\\' === $prefix[$j] && (++$j === $end || $prefix[$j] !== $anotherPrefix[$j])) {
--$j;
break;
}
}
if (0 < $n) {
break;
}
if (('?' === ($prefix[$j] ?? '') || '?' === ($anotherPrefix[$j] ?? '')) && ($prefix[$j] ?? '') !== ($anotherPrefix[$j] ?? '')) {
break;
}
$subPattern = substr($prefix, $i, $j - $i);
if ($prefix !== $anotherPrefix && !preg_match('/^\(\[[^\]]++\]\+\+\)$/', $subPattern) && !preg_match('{(?<!'.$subPattern.')}', '')) {
// sub-patterns of variable length are not considered as common prefixes because their greediness would break in-order matching
break;
}
$i = $j - 1;
} elseif ('\\' === $prefix[$i] && (++$i === $end || $prefix[$i] !== $anotherPrefix[$i])) {
--$i;
break;
}
$commonLength = $i;
}
restore_error_handler();
if ($i < $end && 0b10 === (\ord($prefix[$i]) >> 6) && preg_match('//u', $prefix.' '.$anotherPrefix)) {
do {
// Prevent cutting in the middle of an UTF-8 characters
--$i;
} while (0b10 === (\ord($prefix[$i]) >> 6));
}
$commonPrefix = rtrim(substr($prefix, 0, $commonLength), '/');
if (\strlen($commonPrefix) > $baseLength) {
return $commonPrefix;
}
return false;
return [substr($prefix, 0, $i), substr($prefix, 0, $staticLength ?? $i)];
}
/**
* Optimizes the tree by inlining items from groups with less than 3 items.
*/
public function optimizeGroups()
public static function handleError($type, $msg)
{
$index = -1;
while (isset($this->items[++$index])) {
$item = $this->items[$index];
if ($item instanceof self) {
$item->optimizeGroups();
// When a group contains only two items there's no reason to optimize because at minimum
// the amount of prefix check is 2. In this case inline the group.
if ($item->shouldBeInlined()) {
array_splice($this->items, $index, 1, $item->items);
// Lower index to pass through the same index again after optimizing.
// The first item of the replacements might be a group needing optimization.
--$index;
}
}
}
}
private function shouldBeInlined()
{
if (\count($this->items) >= 3) {
return false;
}
foreach ($this->items as $item) {
if ($item instanceof self) {
return true;
}
}
foreach ($this->items as $item) {
if (\is_array($item) && $item[0] === $this->prefix) {
return false;
}
}
return true;
}
/**
* Guards against adding incompatible prefixes in a group.
*
* @param string $prefix
*
* @throws \LogicException when a prefix does not belong in a group
*/
private function guardAgainstAddingNotAcceptedRoutes($prefix)
{
if (!$this->accepts($prefix)) {
$message = sprintf('Could not add route with prefix %s to collection with prefix %s', $prefix, $this->prefix);
throw new \LogicException($message);
}
return false !== strpos($msg, 'Compilation failed: lookbehind assertion is not fixed length');
}
}

View file

@ -11,8 +11,8 @@
namespace Symfony\Component\Routing\Matcher;
use Symfony\Component\Routing\Exception\ExceptionInterface;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Route;
/**
* @author Fabien Potencier <fabien@symfony.com>
@ -25,41 +25,40 @@ abstract class RedirectableUrlMatcher extends UrlMatcher implements Redirectable
public function match($pathinfo)
{
try {
$parameters = parent::match($pathinfo);
return parent::match($pathinfo);
} catch (ResourceNotFoundException $e) {
if ('/' === substr($pathinfo, -1) || !\in_array($this->context->getMethod(), array('HEAD', 'GET'))) {
if (!\in_array($this->context->getMethod(), ['HEAD', 'GET'], true)) {
throw $e;
}
try {
$parameters = parent::match($pathinfo.'/');
if ($this->allowSchemes) {
redirect_scheme:
$scheme = $this->context->getScheme();
$this->context->setScheme(current($this->allowSchemes));
try {
$ret = parent::match($pathinfo);
return array_replace($parameters, $this->redirect($pathinfo.'/', isset($parameters['_route']) ? $parameters['_route'] : null));
} catch (ResourceNotFoundException $e2) {
return $this->redirect($pathinfo, $ret['_route'] ?? null, $this->context->getScheme()) + $ret;
} catch (ExceptionInterface $e2) {
throw $e;
} finally {
$this->context->setScheme($scheme);
}
} elseif ('/' === $trimmedPathinfo = rtrim($pathinfo, '/') ?: '/') {
throw $e;
} else {
try {
$pathinfo = $trimmedPathinfo === $pathinfo ? $pathinfo.'/' : $trimmedPathinfo;
$ret = parent::match($pathinfo);
return $this->redirect($pathinfo, $ret['_route'] ?? null) + $ret;
} catch (ExceptionInterface $e2) {
if ($this->allowSchemes) {
goto redirect_scheme;
}
throw $e;
}
}
}
return $parameters;
}
/**
* {@inheritdoc}
*/
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);
}
// check HTTP scheme requirement
$scheme = $this->context->getScheme();
$schemes = $route->getSchemes();
if ($schemes && !$route->hasScheme($scheme)) {
return array(self::ROUTE_MATCH, $this->redirect($pathinfo, $name, current($schemes)));
}
return array(self::REQUIREMENT_MATCH, null);
}
}

View file

@ -31,7 +31,7 @@ class TraceableUrlMatcher extends UrlMatcher
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 && 0 !== strpos($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, isset($status[1]) ? $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

@ -32,8 +32,21 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
const REQUIREMENT_MISMATCH = 1;
const ROUTE_MATCH = 2;
/** @var RequestContext */
protected $context;
protected $allow = array();
/**
* Collects HTTP methods that would be allowed for the request.
*/
protected $allow = [];
/**
* Collects URI schemes that would be allowed for the request.
*
* @internal
*/
protected $allowSchemes = [];
protected $routes;
protected $request;
protected $expressionLanguage;
@ -41,7 +54,7 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
/**
* @var ExpressionFunctionProviderInterface[]
*/
protected $expressionLanguageProviders = array();
protected $expressionLanguageProviders = [];
public function __construct(RouteCollection $routes, RequestContext $context)
{
@ -70,19 +83,17 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
*/
public function match($pathinfo)
{
$this->allow = 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));
}
/**
@ -107,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
*
@ -118,19 +128,43 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
*/
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();
// check the static prefix of the URL first. Only use the more expensive preg_match when it matches
if ('' !== $compiledRoute->getStaticPrefix() && 0 !== strpos($pathinfo, $compiledRoute->getStaticPrefix())) {
if ('' !== $staticPrefix && 0 !== strpos($trimmedPathinfo, $staticPrefix)) {
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)) {
continue;
}
if (!preg_match($compiledRoute->getRegex(), $pathinfo, $matches)) {
continue;
$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;
}
@ -141,24 +175,27 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
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';
}
if (!\in_array($method, $requiredMethods)) {
if (self::REQUIREMENT_MATCH === $status[0]) {
$this->allow = array_merge($this->allow, $requiredMethods);
}
continue;
if ('/' !== $pathinfo && !$hasTrailingVar && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) {
if ($supportsTrailingSlash && (!$requiredMethods || \in_array('GET', $requiredMethods))) {
return $this->allow = $this->allowSchemes = [];
}
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, isset($status[1]) ? $status[1] : []));
}
return [];
}
/**
@ -168,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
*
@ -176,9 +212,14 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
*/
protected function getAttributes(Route $route, $name, array $attributes)
{
$defaults = $route->getDefaults();
if (isset($defaults['_canonical_route'])) {
$name = $defaults['_canonical_route'];
unset($defaults['_canonical_route']);
}
$attributes['_route'] = $name;
return $this->mergeDefaults($attributes, $route->getDefaults());
return $this->mergeDefaults($attributes, $defaults);
}
/**
@ -186,22 +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];
}
// check HTTP scheme requirement
$scheme = $this->context->getScheme();
$status = $route->getSchemes() && !$route->hasScheme($scheme) ? self::REQUIREMENT_MISMATCH : self::REQUIREMENT_MATCH;
return array($status, null);
return [self::REQUIREMENT_MATCH, null];
}
/**
@ -215,7 +251,7 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
protected function mergeDefaults($params, $defaults)
{
foreach ($params as $key => $value) {
if (!\is_int($key)) {
if (!\is_int($key) && null !== $value) {
$defaults[$key] = $value;
}
}
@ -227,7 +263,7 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
{
if (null === $this->expressionLanguage) {
if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
throw new \LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
}
$this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders);
}
@ -238,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')) {
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

@ -31,19 +31,9 @@ class RequestContext
private $httpPort;
private $httpsPort;
private $queryString;
private $parameters = array();
private $parameters = [];
/**
* @param string $baseUrl The base URL
* @param string $method The HTTP method
* @param string $host The HTTP host name
* @param string $scheme The HTTP scheme
* @param int $httpPort The HTTP port
* @param int $httpsPort The HTTPS port
* @param string $path The path
* @param string $queryString The query string
*/
public function __construct($baseUrl = '', $method = 'GET', $host = 'localhost', $scheme = 'http', $httpPort = 80, $httpsPort = 443, $path = '/', $queryString = '')
public function __construct(string $baseUrl = '', string $method = 'GET', string $host = 'localhost', string $scheme = 'http', int $httpPort = 80, int $httpsPort = 443, string $path = '/', string $queryString = '')
{
$this->setBaseUrl($baseUrl);
$this->setMethod($method);
@ -67,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;
@ -316,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,15 +21,15 @@ 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 = '';
/**
* @var null|CompiledRoute
* @var CompiledRoute|null
*/
private $compiled;
@ -45,16 +45,16 @@ 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($path, array $defaults = array(), array $requirements = array(), array $options = array(), $host = '', $schemes = array(), $methods = array(), $condition = '')
public function __construct(string $path, array $defaults = [], array $requirements = [], array $options = [], ?string $host = '', $schemes = [], $methods = [], ?string $condition = '')
{
$this->setPath($path);
$this->setDefaults($defaults);
$this->setRequirements($requirements);
$this->addDefaults($defaults);
$this->addRequirements($requirements);
$this->setOptions($options);
$this->setHost($host);
$this->setSchemes($schemes);
@ -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'];
@ -102,6 +106,15 @@ class Route implements \Serializable
}
}
/**
* @internal since Symfony 4.3
* @final since Symfony 4.3
*/
public function unserialize($serialized)
{
$this->__unserialize(unserialize($serialized));
}
/**
* Returns the pattern for the path.
*
@ -123,6 +136,19 @@ 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);
}
if (isset($m[2][0])) {
$this->setRequirement($m[1], substr($m[2], 1, -1));
}
return '{'.$m[1].'}';
}, $pattern);
}
// A pattern must start with a slash and must not have multiple slashes at the beginning because the
// generated path for this route would be confused with a network path, e.g. '//domain.com/path'.
$this->path = '/'.ltrim(trim($pattern), '/');
@ -243,15 +269,13 @@ class Route implements \Serializable
*
* 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);
}
@ -261,8 +285,6 @@ class Route implements \Serializable
*
* This method implements a fluent interface.
*
* @param array $options The options
*
* @return $this
*/
public function addOptions(array $options)
@ -314,7 +336,7 @@ class Route implements \Serializable
*/
public function hasOption($name)
{
return array_key_exists($name, $this->options);
return \array_key_exists($name, $this->options);
}
/**
@ -338,7 +360,7 @@ class Route implements \Serializable
*/
public function setDefaults(array $defaults)
{
$this->defaults = array();
$this->defaults = [];
return $this->addDefaults($defaults);
}
@ -383,7 +405,7 @@ class Route implements \Serializable
*/
public function hasDefault($name)
{
return array_key_exists($name, $this->defaults);
return \array_key_exists($name, $this->defaults);
}
/**
@ -423,7 +445,7 @@ class Route implements \Serializable
*/
public function setRequirements(array $requirements)
{
$this->requirements = array();
$this->requirements = [];
return $this->addRequirements($requirements);
}
@ -468,7 +490,7 @@ class Route implements \Serializable
*/
public function hasRequirement($key)
{
return array_key_exists($key, $this->requirements);
return \array_key_exists($key, $this->requirements);
}
/**
@ -535,7 +557,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));

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()
{
@ -69,8 +69,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)
{
@ -138,8 +137,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) {
@ -153,6 +156,23 @@ class RouteCollection implements \IteratorAggregate, \Countable
}
}
/**
* Adds a prefix to the name of all the routes within in the collection.
*/
public function addNamePrefix(string $prefix)
{
$prefixedRoutes = [];
foreach ($this->routes as $name => $route) {
$prefixedRoutes[$prefix.$name] = $route;
if (null !== $name = $route->getDefault('_canonical_route')) {
$route->setDefault('_canonical_route', $prefix.$name);
}
}
$this->routes = $prefixedRoutes;
}
/**
* Sets the host pattern on all routes.
*
@ -160,7 +180,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);
@ -219,8 +239,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

@ -11,7 +11,7 @@
namespace Symfony\Component\Routing;
use Symfony\Component\Config\Exception\FileLoaderLoadException;
use Symfony\Component\Config\Exception\LoaderLoadException;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\Config\Resource\ResourceInterface;
@ -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)
{
@ -46,7 +46,7 @@ class RouteCollectionBuilder
/**
* Import an external routing resource and returns the RouteCollectionBuilder.
*
* $routes->import('blog.yml', '/blog');
* $routes->import('blog.yml', '/blog');
*
* @param mixed $resource
* @param string|null $prefix
@ -54,11 +54,11 @@ class RouteCollectionBuilder
*
* @return self
*
* @throws FileLoaderLoadException
* @throws LoaderLoadException
*/
public function import($resource, $prefix = '/', $type = null)
{
/** @var RouteCollection[] $collection */
/** @var RouteCollection[] $collections */
$collections = $this->load($resource, $type);
// create a builder from the RouteCollection
@ -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
@ -253,7 +251,7 @@ class RouteCollectionBuilder
*
* @return $this
*/
private function addResource(ResourceInterface $resource)
private function addResource(ResourceInterface $resource): self
{
$this->resources[] = $resource;
@ -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);
}
@ -324,15 +324,13 @@ class RouteCollectionBuilder
/**
* Generates a route name based on details of this route.
*
* @return string
*/
private function generateRouteName(Route $route)
private function generateRouteName(Route $route): string
{
$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.
@ -349,9 +347,9 @@ class RouteCollectionBuilder
*
* @return RouteCollection[]
*
* @throws FileLoaderLoadException If no loader is found
* @throws LoaderLoadException If no loader is found
*/
private function load($resource, $type = null)
private function load($resource, string $type = null): array
{
if (null === $this->loader) {
throw new \BadMethodCallException('Cannot import other routing resources: you must pass a LoaderInterface when constructing RouteCollectionBuilder.');
@ -360,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 FileLoaderLoadException($resource, null, null, null, $type);
throw new LoaderLoadException($resource, null, null, null, $type);
}
if (false === $loader = $resolver->resolve($resource, $type)) {
throw new FileLoaderLoadException($resource, null, null, null, $type);
throw new LoaderLoadException($resource, null, null, null, $type);
}
$collections = $loader->load($resource, $type);
return \is_array($collections) ? $collections : array($collections);
return \is_array($collections) ? $collections : [$collections];
}
}

View file

@ -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);
@ -92,19 +92,18 @@ 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);
$needsUtf8 = $route->getOption('utf8');
if (!$needsUtf8 && $useUtf8 && preg_match('/[\x80-\xFF]/', $pattern)) {
$needsUtf8 = true;
@trigger_error(sprintf('Using UTF-8 route patterns without setting the "utf8" option is deprecated since Symfony 3.2 and will throw a LogicException in 4.0. Turn on the "utf8" route option for pattern "%s".', $pattern), E_USER_DEPRECATED);
throw new \LogicException(sprintf('Cannot use UTF-8 route patterns without setting the "utf8" option for route "%s".', $route->getPath()));
}
if (!$useUtf8 && $needsUtf8) {
throw new \LogicException(sprintf('Cannot mix UTF-8 requirements with non-UTF-8 pattern "%s".', $pattern));
@ -112,9 +111,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]);
@ -143,9 +143,9 @@ class RouteCompiler implements RouteCompilerInterface
}
if ($isSeparator && $precedingText !== $precedingChar) {
$tokens[] = array('text', substr($precedingText, 0, -\strlen($precedingChar)));
$tokens[] = ['text', substr($precedingText, 0, -\strlen($precedingChar))];
} elseif (!$isSeparator && \strlen($precedingText) > 0) {
$tokens[] = array('text', $precedingText);
$tokens[] = ['text', $precedingText];
}
$regexp = $route->getRequirement($varName);
@ -154,7 +154,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'.
@ -176,20 +176,26 @@ class RouteCompiler implements RouteCompilerInterface
if (!preg_match('//u', $regexp)) {
$useUtf8 = false;
} elseif (!$needsUtf8 && preg_match('/[\x80-\xFF]|(?<!\\\\)\\\\(?:\\\\\\\\)*+(?-i:X|[pP][\{CLMNPSZ]|x\{[A-Fa-f0-9]{3})/', $regexp)) {
$needsUtf8 = true;
@trigger_error(sprintf('Using UTF-8 route requirements without setting the "utf8" option is deprecated since Symfony 3.2 and will throw a LogicException in 4.0. Turn on the "utf8" route option for variable "%s" in pattern "%s".', $varName, $pattern), E_USER_DEPRECATED);
throw new \LogicException(sprintf('Cannot use UTF-8 route requirements without setting the "utf8" option for variable "%s" in pattern "%s".', $varName, $pattern));
}
if (!$useUtf8 && $needsUtf8) {
throw new \LogicException(sprintf('Cannot mix UTF-8 requirement with non-UTF-8 charset for variable "%s" in pattern "%s".', $varName, $pattern));
}
$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
@ -197,7 +203,8 @@ class RouteCompiler implements RouteCompilerInterface
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;
@ -217,25 +224,23 @@ 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,
);
];
}
/**
* Determines the longest static prefix possible for a route.
*
* @return string The leading static part of a route's path
*/
private static function determineStaticPrefix(Route $route, array $tokens)
private static function determineStaticPrefix(Route $route, array $tokens): string
{
if ('text' !== $tokens[0][0]) {
return ($route->hasDefault($tokens[0][3]) || '/' === $tokens[0][1]) ? '' : $tokens[0][1];
@ -251,14 +256,9 @@ class RouteCompiler implements RouteCompilerInterface
}
/**
* Returns the next static character in the Route pattern that will serve as a separator.
*
* @param string $pattern The route pattern
* @param bool $useUtf8 Whether the character is encoded in UTF-8 or not
*
* @return string The next static character that functions as separator (or empty string when none available)
* Returns the next static character in the Route pattern that will serve as a separator (or the empty string when none available).
*/
private static function findNextSeparator($pattern, $useUtf8)
private static function findNextSeparator(string $pattern, bool $useUtf8): string
{
if ('' == $pattern) {
// return empty string if pattern is empty or false (false which can be returned by substr)
@ -284,7 +284,7 @@ class RouteCompiler implements RouteCompilerInterface
*
* @return string The regexp pattern for a single token
*/
private static function computeRegexp(array $tokens, $index, $firstOptional)
private static function computeRegexp(array $tokens, int $index, int $firstOptional): string
{
$token = $tokens[$index];
if ('text' === $token[0]) {
@ -313,4 +313,25 @@ class RouteCompiler implements RouteCompilerInterface
}
}
}
private static function transformCapturingGroupsToNonCapturings(string $regexp): string
{
for ($i = 0; $i < \strlen($regexp); ++$i) {
if ('\\' === $regexp[$i]) {
++$i;
continue;
}
if ('(' !== $regexp[$i] || !isset($regexp[$i + 2])) {
continue;
}
if ('*' === $regexp[++$i] || '?' === $regexp[$i]) {
++$i;
continue;
}
$regexp = substr_replace($regexp, '?:', $i, 0);
++$i;
}
return $regexp;
}
}

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->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']);
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'];
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 if the UrlGenerator and UrlGeneratorDumper are not consistent with each other
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 if the UrlMatcher and UrlMatcherDumper are not consistent with each other
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 $configCacheFactory
*/
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,50 +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'));
}
/**
* @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'),
);
}
}

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,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,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,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,318 +0,0 @@
<?php
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\RequestContext;
/**
* This class has been auto-generated
* by the Symfony Routing Component.
*/
class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
{
public function __construct(RequestContext $context)
{
$this->context = $context;
}
public function match($rawPathinfo)
{
$allow = array();
$pathinfo = rawurldecode($rawPathinfo);
$trimmedPathinfo = rtrim($pathinfo, '/');
$context = $this->context;
$request = $this->request ?: $this->createRequest($pathinfo);
$requestMethod = $canonicalMethod = $context->getMethod();
if ('HEAD' === $requestMethod) {
$canonicalMethod = 'GET';
}
if (0 === strpos($pathinfo, '/foo')) {
// foo
if (preg_match('#^/foo/(?P<bar>baz|symfony)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo')), array ( 'def' => 'test',));
}
// foofoo
if ('/foofoo' === $pathinfo) {
return array ( 'def' => 'test', '_route' => 'foofoo',);
}
}
elseif (0 === strpos($pathinfo, '/bar')) {
// bar
if (preg_match('#^/bar/(?P<foo>[^/]++)$#sD', $pathinfo, $matches)) {
$ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'bar')), array ());
if (!in_array($canonicalMethod, array('GET', 'HEAD'))) {
$allow = array_merge($allow, array('GET', 'HEAD'));
goto not_bar;
}
return $ret;
}
not_bar:
// barhead
if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?P<foo>[^/]++)$#sD', $pathinfo, $matches)) {
$ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'barhead')), array ());
if (!in_array($canonicalMethod, array('GET'))) {
$allow = array_merge($allow, array('GET'));
goto not_barhead;
}
return $ret;
}
not_barhead:
}
elseif (0 === strpos($pathinfo, '/test')) {
if (0 === strpos($pathinfo, '/test/baz')) {
// baz
if ('/test/baz' === $pathinfo) {
return array('_route' => 'baz');
}
// baz2
if ('/test/baz.html' === $pathinfo) {
return array('_route' => 'baz2');
}
// baz3
if ('/test/baz3/' === $pathinfo) {
return array('_route' => 'baz3');
}
}
// baz4
if (preg_match('#^/test/(?P<foo>[^/]++)/$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz4')), array ());
}
// baz5
if (preg_match('#^/test/(?P<foo>[^/]++)/$#sD', $pathinfo, $matches)) {
$ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'baz5')), array ());
if (!in_array($requestMethod, array('POST'))) {
$allow = array_merge($allow, array('POST'));
goto not_baz5;
}
return $ret;
}
not_baz5:
// baz.baz6
if (preg_match('#^/test/(?P<foo>[^/]++)/$#sD', $pathinfo, $matches)) {
$ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'baz.baz6')), array ());
if (!in_array($requestMethod, array('PUT'))) {
$allow = array_merge($allow, array('PUT'));
goto not_bazbaz6;
}
return $ret;
}
not_bazbaz6:
}
// quoter
if (preg_match('#^/(?P<quoter>[\']+)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'quoter')), array ());
}
// space
if ('/spa ce' === $pathinfo) {
return array('_route' => 'space');
}
if (0 === strpos($pathinfo, '/a')) {
if (0 === strpos($pathinfo, '/a/b\'b')) {
// foo1
if (preg_match('#^/a/b\'b/(?P<foo>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo1')), array ());
}
// bar1
if (preg_match('#^/a/b\'b/(?P<bar>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar1')), array ());
}
}
// overridden
if (preg_match('#^/a/(?P<var>.*)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'overridden')), array ());
}
if (0 === strpos($pathinfo, '/a/b\'b')) {
// foo2
if (preg_match('#^/a/b\'b/(?P<foo1>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo2')), array ());
}
// bar2
if (preg_match('#^/a/b\'b/(?P<bar1>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar2')), array ());
}
}
}
elseif (0 === strpos($pathinfo, '/multi')) {
// helloWorld
if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?P<who>[^/]++))?$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'helloWorld')), array ( 'who' => 'World!',));
}
// hey
if ('/multi/hey/' === $pathinfo) {
return array('_route' => 'hey');
}
// overridden2
if ('/multi/new' === $pathinfo) {
return array('_route' => 'overridden2');
}
}
// foo3
if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P<foo>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo3')), array ());
}
// bar3
if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P<bar>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar3')), array ());
}
if (0 === strpos($pathinfo, '/aba')) {
// ababa
if ('/ababa' === $pathinfo) {
return array('_route' => 'ababa');
}
// foo4
if (preg_match('#^/aba/(?P<foo>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo4')), array ());
}
}
$host = $context->getHost();
if (preg_match('#^a\\.example\\.com$#sDi', $host, $hostMatches)) {
// route1
if ('/route1' === $pathinfo) {
return array('_route' => 'route1');
}
// route2
if ('/c2/route2' === $pathinfo) {
return array('_route' => 'route2');
}
}
if (preg_match('#^b\\.example\\.com$#sDi', $host, $hostMatches)) {
// route3
if ('/c2/route3' === $pathinfo) {
return array('_route' => 'route3');
}
}
if (preg_match('#^a\\.example\\.com$#sDi', $host, $hostMatches)) {
// route4
if ('/route4' === $pathinfo) {
return array('_route' => 'route4');
}
}
if (preg_match('#^c\\.example\\.com$#sDi', $host, $hostMatches)) {
// route5
if ('/route5' === $pathinfo) {
return array('_route' => 'route5');
}
}
// route6
if ('/route6' === $pathinfo) {
return array('_route' => 'route6');
}
if (preg_match('#^(?P<var1>[^\\.]++)\\.example\\.com$#sDi', $host, $hostMatches)) {
if (0 === strpos($pathinfo, '/route1')) {
// route11
if ('/route11' === $pathinfo) {
return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route11')), array ());
}
// route12
if ('/route12' === $pathinfo) {
return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route12')), array ( 'var1' => 'val',));
}
// route13
if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?P<name>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route13')), array ());
}
// route14
if (0 === strpos($pathinfo, '/route14') && preg_match('#^/route14/(?P<name>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route14')), array ( 'var1' => 'val',));
}
}
}
if (preg_match('#^c\\.example\\.com$#sDi', $host, $hostMatches)) {
// route15
if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?P<name>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'route15')), array ());
}
}
// route16
if (0 === strpos($pathinfo, '/route16') && preg_match('#^/route16/(?P<name>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'route16')), array ( 'var1' => 'val',));
}
// route17
if ('/route17' === $pathinfo) {
return array('_route' => 'route17');
}
// a
if ('/a/a...' === $pathinfo) {
return array('_route' => 'a');
}
if (0 === strpos($pathinfo, '/a/b')) {
// b
if (preg_match('#^/a/b/(?P<var>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'b')), array ());
}
// c
if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?P<var>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'c')), array ());
}
}
if ('/' === $pathinfo && !$allow) {
throw new Symfony\Component\Routing\Exception\NoConfigurationException();
}
throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException();
}
}

View file

@ -1,380 +0,0 @@
<?php
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\RequestContext;
/**
* This class has been auto-generated
* by the Symfony Routing Component.
*/
class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\RedirectableUrlMatcher
{
public function __construct(RequestContext $context)
{
$this->context = $context;
}
public function match($rawPathinfo)
{
$allow = array();
$pathinfo = rawurldecode($rawPathinfo);
$trimmedPathinfo = rtrim($pathinfo, '/');
$context = $this->context;
$request = $this->request ?: $this->createRequest($pathinfo);
$requestMethod = $canonicalMethod = $context->getMethod();
if ('HEAD' === $requestMethod) {
$canonicalMethod = 'GET';
}
if (0 === strpos($pathinfo, '/foo')) {
// foo
if (preg_match('#^/foo/(?P<bar>baz|symfony)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo')), array ( 'def' => 'test',));
}
// foofoo
if ('/foofoo' === $pathinfo) {
return array ( 'def' => 'test', '_route' => 'foofoo',);
}
}
elseif (0 === strpos($pathinfo, '/bar')) {
// bar
if (preg_match('#^/bar/(?P<foo>[^/]++)$#sD', $pathinfo, $matches)) {
$ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'bar')), array ());
if (!in_array($canonicalMethod, array('GET', 'HEAD'))) {
$allow = array_merge($allow, array('GET', 'HEAD'));
goto not_bar;
}
return $ret;
}
not_bar:
// barhead
if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?P<foo>[^/]++)$#sD', $pathinfo, $matches)) {
$ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'barhead')), array ());
if (!in_array($canonicalMethod, array('GET'))) {
$allow = array_merge($allow, array('GET'));
goto not_barhead;
}
return $ret;
}
not_barhead:
}
elseif (0 === strpos($pathinfo, '/test')) {
if (0 === strpos($pathinfo, '/test/baz')) {
// baz
if ('/test/baz' === $pathinfo) {
return array('_route' => 'baz');
}
// baz2
if ('/test/baz.html' === $pathinfo) {
return array('_route' => 'baz2');
}
// baz3
if ('/test/baz3' === $trimmedPathinfo) {
$ret = array('_route' => 'baz3');
if ('/' === substr($pathinfo, -1)) {
// no-op
} elseif ('GET' !== $canonicalMethod) {
goto not_baz3;
} else {
return array_replace($ret, $this->redirect($rawPathinfo.'/', 'baz3'));
}
return $ret;
}
not_baz3:
}
// baz4
if (preg_match('#^/test/(?P<foo>[^/]++)/?$#sD', $pathinfo, $matches)) {
$ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'baz4')), array ());
if ('/' === substr($pathinfo, -1)) {
// no-op
} elseif ('GET' !== $canonicalMethod) {
goto not_baz4;
} else {
return array_replace($ret, $this->redirect($rawPathinfo.'/', 'baz4'));
}
return $ret;
}
not_baz4:
// baz5
if (preg_match('#^/test/(?P<foo>[^/]++)/$#sD', $pathinfo, $matches)) {
$ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'baz5')), array ());
if (!in_array($requestMethod, array('POST'))) {
$allow = array_merge($allow, array('POST'));
goto not_baz5;
}
return $ret;
}
not_baz5:
// baz.baz6
if (preg_match('#^/test/(?P<foo>[^/]++)/$#sD', $pathinfo, $matches)) {
$ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'baz.baz6')), array ());
if (!in_array($requestMethod, array('PUT'))) {
$allow = array_merge($allow, array('PUT'));
goto not_bazbaz6;
}
return $ret;
}
not_bazbaz6:
}
// quoter
if (preg_match('#^/(?P<quoter>[\']+)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'quoter')), array ());
}
// space
if ('/spa ce' === $pathinfo) {
return array('_route' => 'space');
}
if (0 === strpos($pathinfo, '/a')) {
if (0 === strpos($pathinfo, '/a/b\'b')) {
// foo1
if (preg_match('#^/a/b\'b/(?P<foo>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo1')), array ());
}
// bar1
if (preg_match('#^/a/b\'b/(?P<bar>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar1')), array ());
}
}
// overridden
if (preg_match('#^/a/(?P<var>.*)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'overridden')), array ());
}
if (0 === strpos($pathinfo, '/a/b\'b')) {
// foo2
if (preg_match('#^/a/b\'b/(?P<foo1>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo2')), array ());
}
// bar2
if (preg_match('#^/a/b\'b/(?P<bar1>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar2')), array ());
}
}
}
elseif (0 === strpos($pathinfo, '/multi')) {
// helloWorld
if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?P<who>[^/]++))?$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'helloWorld')), array ( 'who' => 'World!',));
}
// hey
if ('/multi/hey' === $trimmedPathinfo) {
$ret = array('_route' => 'hey');
if ('/' === substr($pathinfo, -1)) {
// no-op
} elseif ('GET' !== $canonicalMethod) {
goto not_hey;
} else {
return array_replace($ret, $this->redirect($rawPathinfo.'/', 'hey'));
}
return $ret;
}
not_hey:
// overridden2
if ('/multi/new' === $pathinfo) {
return array('_route' => 'overridden2');
}
}
// foo3
if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P<foo>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo3')), array ());
}
// bar3
if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P<bar>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar3')), array ());
}
if (0 === strpos($pathinfo, '/aba')) {
// ababa
if ('/ababa' === $pathinfo) {
return array('_route' => 'ababa');
}
// foo4
if (preg_match('#^/aba/(?P<foo>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo4')), array ());
}
}
$host = $context->getHost();
if (preg_match('#^a\\.example\\.com$#sDi', $host, $hostMatches)) {
// route1
if ('/route1' === $pathinfo) {
return array('_route' => 'route1');
}
// route2
if ('/c2/route2' === $pathinfo) {
return array('_route' => 'route2');
}
}
if (preg_match('#^b\\.example\\.com$#sDi', $host, $hostMatches)) {
// route3
if ('/c2/route3' === $pathinfo) {
return array('_route' => 'route3');
}
}
if (preg_match('#^a\\.example\\.com$#sDi', $host, $hostMatches)) {
// route4
if ('/route4' === $pathinfo) {
return array('_route' => 'route4');
}
}
if (preg_match('#^c\\.example\\.com$#sDi', $host, $hostMatches)) {
// route5
if ('/route5' === $pathinfo) {
return array('_route' => 'route5');
}
}
// route6
if ('/route6' === $pathinfo) {
return array('_route' => 'route6');
}
if (preg_match('#^(?P<var1>[^\\.]++)\\.example\\.com$#sDi', $host, $hostMatches)) {
if (0 === strpos($pathinfo, '/route1')) {
// route11
if ('/route11' === $pathinfo) {
return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route11')), array ());
}
// route12
if ('/route12' === $pathinfo) {
return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route12')), array ( 'var1' => 'val',));
}
// route13
if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?P<name>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route13')), array ());
}
// route14
if (0 === strpos($pathinfo, '/route14') && preg_match('#^/route14/(?P<name>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route14')), array ( 'var1' => 'val',));
}
}
}
if (preg_match('#^c\\.example\\.com$#sDi', $host, $hostMatches)) {
// route15
if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?P<name>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'route15')), array ());
}
}
// route16
if (0 === strpos($pathinfo, '/route16') && preg_match('#^/route16/(?P<name>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'route16')), array ( 'var1' => 'val',));
}
// route17
if ('/route17' === $pathinfo) {
return array('_route' => 'route17');
}
// a
if ('/a/a...' === $pathinfo) {
return array('_route' => 'a');
}
if (0 === strpos($pathinfo, '/a/b')) {
// b
if (preg_match('#^/a/b/(?P<var>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'b')), array ());
}
// c
if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?P<var>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'c')), array ());
}
}
// secure
if ('/secure' === $pathinfo) {
$ret = array('_route' => 'secure');
$requiredSchemes = array ( 'https' => 0,);
if (!isset($requiredSchemes[$context->getScheme()])) {
if ('GET' !== $canonicalMethod) {
goto not_secure;
}
return array_replace($ret, $this->redirect($rawPathinfo, 'secure', key($requiredSchemes)));
}
return $ret;
}
not_secure:
// nonsecure
if ('/nonsecure' === $pathinfo) {
$ret = array('_route' => 'nonsecure');
$requiredSchemes = array ( 'http' => 0,);
if (!isset($requiredSchemes[$context->getScheme()])) {
if ('GET' !== $canonicalMethod) {
goto not_nonsecure;
}
return array_replace($ret, $this->redirect($rawPathinfo, 'nonsecure', key($requiredSchemes)));
}
return $ret;
}
not_nonsecure:
if ('/' === $pathinfo && !$allow) {
throw new Symfony\Component\Routing\Exception\NoConfigurationException();
}
throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException();
}
}

View file

@ -1,55 +0,0 @@
<?php
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\RequestContext;
/**
* This class has been auto-generated
* by the Symfony Routing Component.
*/
class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
{
public function __construct(RequestContext $context)
{
$this->context = $context;
}
public function match($rawPathinfo)
{
$allow = array();
$pathinfo = rawurldecode($rawPathinfo);
$trimmedPathinfo = rtrim($pathinfo, '/');
$context = $this->context;
$request = $this->request ?: $this->createRequest($pathinfo);
$requestMethod = $canonicalMethod = $context->getMethod();
if ('HEAD' === $requestMethod) {
$canonicalMethod = 'GET';
}
if (0 === strpos($pathinfo, '/rootprefix')) {
// static
if ('/rootprefix/test' === $pathinfo) {
return array('_route' => 'static');
}
// dynamic
if (preg_match('#^/rootprefix/(?P<var>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'dynamic')), array ());
}
}
// with-condition
if ('/with-condition' === $pathinfo && ($context->getMethod() == "GET")) {
return array('_route' => 'with-condition');
}
if ('/' === $pathinfo && !$allow) {
throw new Symfony\Component\Routing\Exception\NoConfigurationException();
}
throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException();
}
}

View file

@ -1,112 +0,0 @@
<?php
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\RequestContext;
/**
* This class has been auto-generated
* by the Symfony Routing Component.
*/
class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
{
public function __construct(RequestContext $context)
{
$this->context = $context;
}
public function match($rawPathinfo)
{
$allow = array();
$pathinfo = rawurldecode($rawPathinfo);
$trimmedPathinfo = rtrim($pathinfo, '/');
$context = $this->context;
$request = $this->request ?: $this->createRequest($pathinfo);
$requestMethod = $canonicalMethod = $context->getMethod();
if ('HEAD' === $requestMethod) {
$canonicalMethod = 'GET';
}
// just_head
if ('/just_head' === $pathinfo) {
$ret = array('_route' => 'just_head');
if (!in_array($requestMethod, array('HEAD'))) {
$allow = array_merge($allow, array('HEAD'));
goto not_just_head;
}
return $ret;
}
not_just_head:
// head_and_get
if ('/head_and_get' === $pathinfo) {
$ret = array('_route' => 'head_and_get');
if (!in_array($canonicalMethod, array('HEAD', 'GET'))) {
$allow = array_merge($allow, array('HEAD', 'GET'));
goto not_head_and_get;
}
return $ret;
}
not_head_and_get:
// get_and_head
if ('/get_and_head' === $pathinfo) {
$ret = array('_route' => 'get_and_head');
if (!in_array($canonicalMethod, array('GET', 'HEAD'))) {
$allow = array_merge($allow, array('GET', 'HEAD'));
goto not_get_and_head;
}
return $ret;
}
not_get_and_head:
// post_and_head
if ('/post_and_head' === $pathinfo) {
$ret = array('_route' => 'post_and_head');
if (!in_array($requestMethod, array('POST', 'HEAD'))) {
$allow = array_merge($allow, array('POST', 'HEAD'));
goto not_post_and_head;
}
return $ret;
}
not_post_and_head:
if (0 === strpos($pathinfo, '/put_and_post')) {
// put_and_post
if ('/put_and_post' === $pathinfo) {
$ret = array('_route' => 'put_and_post');
if (!in_array($requestMethod, array('PUT', 'POST'))) {
$allow = array_merge($allow, array('PUT', 'POST'));
goto not_put_and_post;
}
return $ret;
}
not_put_and_post:
// put_and_get_and_head
if ('/put_and_post' === $pathinfo) {
$ret = array('_route' => 'put_and_get_and_head');
if (!in_array($canonicalMethod, array('PUT', 'GET', 'HEAD'))) {
$allow = array_merge($allow, array('PUT', 'GET', 'HEAD'));
goto not_put_and_get_and_head;
}
return $ret;
}
not_put_and_get_and_head:
}
if ('/' === $pathinfo && !$allow) {
throw new Symfony\Component\Routing\Exception\NoConfigurationException();
}
throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException();
}
}

View file

@ -1,209 +0,0 @@
<?php
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\RequestContext;
/**
* This class has been auto-generated
* by the Symfony Routing Component.
*/
class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\RedirectableUrlMatcher
{
public function __construct(RequestContext $context)
{
$this->context = $context;
}
public function match($rawPathinfo)
{
$allow = array();
$pathinfo = rawurldecode($rawPathinfo);
$trimmedPathinfo = rtrim($pathinfo, '/');
$context = $this->context;
$request = $this->request ?: $this->createRequest($pathinfo);
$requestMethod = $canonicalMethod = $context->getMethod();
if ('HEAD' === $requestMethod) {
$canonicalMethod = 'GET';
}
if (0 === strpos($pathinfo, '/a')) {
// a_first
if ('/a/11' === $pathinfo) {
return array('_route' => 'a_first');
}
// a_second
if ('/a/22' === $pathinfo) {
return array('_route' => 'a_second');
}
// a_third
if ('/a/333' === $pathinfo) {
return array('_route' => 'a_third');
}
}
// a_wildcard
if (preg_match('#^/(?P<param>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'a_wildcard')), array ());
}
if (0 === strpos($pathinfo, '/a')) {
// a_fourth
if ('/a/44' === $trimmedPathinfo) {
$ret = array('_route' => 'a_fourth');
if ('/' === substr($pathinfo, -1)) {
// no-op
} elseif ('GET' !== $canonicalMethod) {
goto not_a_fourth;
} else {
return array_replace($ret, $this->redirect($rawPathinfo.'/', 'a_fourth'));
}
return $ret;
}
not_a_fourth:
// a_fifth
if ('/a/55' === $trimmedPathinfo) {
$ret = array('_route' => 'a_fifth');
if ('/' === substr($pathinfo, -1)) {
// no-op
} elseif ('GET' !== $canonicalMethod) {
goto not_a_fifth;
} else {
return array_replace($ret, $this->redirect($rawPathinfo.'/', 'a_fifth'));
}
return $ret;
}
not_a_fifth:
// a_sixth
if ('/a/66' === $trimmedPathinfo) {
$ret = array('_route' => 'a_sixth');
if ('/' === substr($pathinfo, -1)) {
// no-op
} elseif ('GET' !== $canonicalMethod) {
goto not_a_sixth;
} else {
return array_replace($ret, $this->redirect($rawPathinfo.'/', 'a_sixth'));
}
return $ret;
}
not_a_sixth:
}
// nested_wildcard
if (0 === strpos($pathinfo, '/nested') && preg_match('#^/nested/(?P<param>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'nested_wildcard')), array ());
}
if (0 === strpos($pathinfo, '/nested/group')) {
// nested_a
if ('/nested/group/a' === $trimmedPathinfo) {
$ret = array('_route' => 'nested_a');
if ('/' === substr($pathinfo, -1)) {
// no-op
} elseif ('GET' !== $canonicalMethod) {
goto not_nested_a;
} else {
return array_replace($ret, $this->redirect($rawPathinfo.'/', 'nested_a'));
}
return $ret;
}
not_nested_a:
// nested_b
if ('/nested/group/b' === $trimmedPathinfo) {
$ret = array('_route' => 'nested_b');
if ('/' === substr($pathinfo, -1)) {
// no-op
} elseif ('GET' !== $canonicalMethod) {
goto not_nested_b;
} else {
return array_replace($ret, $this->redirect($rawPathinfo.'/', 'nested_b'));
}
return $ret;
}
not_nested_b:
// nested_c
if ('/nested/group/c' === $trimmedPathinfo) {
$ret = array('_route' => 'nested_c');
if ('/' === substr($pathinfo, -1)) {
// no-op
} elseif ('GET' !== $canonicalMethod) {
goto not_nested_c;
} else {
return array_replace($ret, $this->redirect($rawPathinfo.'/', 'nested_c'));
}
return $ret;
}
not_nested_c:
}
elseif (0 === strpos($pathinfo, '/slashed/group')) {
// slashed_a
if ('/slashed/group' === $trimmedPathinfo) {
$ret = array('_route' => 'slashed_a');
if ('/' === substr($pathinfo, -1)) {
// no-op
} elseif ('GET' !== $canonicalMethod) {
goto not_slashed_a;
} else {
return array_replace($ret, $this->redirect($rawPathinfo.'/', 'slashed_a'));
}
return $ret;
}
not_slashed_a:
// slashed_b
if ('/slashed/group/b' === $trimmedPathinfo) {
$ret = array('_route' => 'slashed_b');
if ('/' === substr($pathinfo, -1)) {
// no-op
} elseif ('GET' !== $canonicalMethod) {
goto not_slashed_b;
} else {
return array_replace($ret, $this->redirect($rawPathinfo.'/', 'slashed_b'));
}
return $ret;
}
not_slashed_b:
// slashed_c
if ('/slashed/group/c' === $trimmedPathinfo) {
$ret = array('_route' => 'slashed_c');
if ('/' === substr($pathinfo, -1)) {
// no-op
} elseif ('GET' !== $canonicalMethod) {
goto not_slashed_c;
} else {
return array_replace($ret, $this->redirect($rawPathinfo.'/', 'slashed_c'));
}
return $ret;
}
not_slashed_c:
}
if ('/' === $pathinfo && !$allow) {
throw new Symfony\Component\Routing\Exception\NoConfigurationException();
}
throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException();
}
}

View file

@ -1,213 +0,0 @@
<?php
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\RequestContext;
/**
* This class has been auto-generated
* by the Symfony Routing Component.
*/
class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
{
public function __construct(RequestContext $context)
{
$this->context = $context;
}
public function match($rawPathinfo)
{
$allow = array();
$pathinfo = rawurldecode($rawPathinfo);
$trimmedPathinfo = rtrim($pathinfo, '/');
$context = $this->context;
$request = $this->request ?: $this->createRequest($pathinfo);
$requestMethod = $canonicalMethod = $context->getMethod();
if ('HEAD' === $requestMethod) {
$canonicalMethod = 'GET';
}
if (0 === strpos($pathinfo, '/trailing/simple')) {
// simple_trailing_slash_no_methods
if ('/trailing/simple/no-methods/' === $pathinfo) {
return array('_route' => 'simple_trailing_slash_no_methods');
}
// simple_trailing_slash_GET_method
if ('/trailing/simple/get-method/' === $pathinfo) {
$ret = array('_route' => 'simple_trailing_slash_GET_method');
if (!in_array($canonicalMethod, array('GET'))) {
$allow = array_merge($allow, array('GET'));
goto not_simple_trailing_slash_GET_method;
}
return $ret;
}
not_simple_trailing_slash_GET_method:
// simple_trailing_slash_HEAD_method
if ('/trailing/simple/head-method/' === $pathinfo) {
$ret = array('_route' => 'simple_trailing_slash_HEAD_method');
if (!in_array($requestMethod, array('HEAD'))) {
$allow = array_merge($allow, array('HEAD'));
goto not_simple_trailing_slash_HEAD_method;
}
return $ret;
}
not_simple_trailing_slash_HEAD_method:
// simple_trailing_slash_POST_method
if ('/trailing/simple/post-method/' === $pathinfo) {
$ret = array('_route' => 'simple_trailing_slash_POST_method');
if (!in_array($requestMethod, array('POST'))) {
$allow = array_merge($allow, array('POST'));
goto not_simple_trailing_slash_POST_method;
}
return $ret;
}
not_simple_trailing_slash_POST_method:
}
elseif (0 === strpos($pathinfo, '/trailing/regex')) {
// regex_trailing_slash_no_methods
if (0 === strpos($pathinfo, '/trailing/regex/no-methods') && preg_match('#^/trailing/regex/no\\-methods/(?P<param>[^/]++)/$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_no_methods')), array ());
}
// regex_trailing_slash_GET_method
if (0 === strpos($pathinfo, '/trailing/regex/get-method') && preg_match('#^/trailing/regex/get\\-method/(?P<param>[^/]++)/$#sD', $pathinfo, $matches)) {
$ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_GET_method')), array ());
if (!in_array($canonicalMethod, array('GET'))) {
$allow = array_merge($allow, array('GET'));
goto not_regex_trailing_slash_GET_method;
}
return $ret;
}
not_regex_trailing_slash_GET_method:
// regex_trailing_slash_HEAD_method
if (0 === strpos($pathinfo, '/trailing/regex/head-method') && preg_match('#^/trailing/regex/head\\-method/(?P<param>[^/]++)/$#sD', $pathinfo, $matches)) {
$ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_HEAD_method')), array ());
if (!in_array($requestMethod, array('HEAD'))) {
$allow = array_merge($allow, array('HEAD'));
goto not_regex_trailing_slash_HEAD_method;
}
return $ret;
}
not_regex_trailing_slash_HEAD_method:
// regex_trailing_slash_POST_method
if (0 === strpos($pathinfo, '/trailing/regex/post-method') && preg_match('#^/trailing/regex/post\\-method/(?P<param>[^/]++)/$#sD', $pathinfo, $matches)) {
$ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_POST_method')), array ());
if (!in_array($requestMethod, array('POST'))) {
$allow = array_merge($allow, array('POST'));
goto not_regex_trailing_slash_POST_method;
}
return $ret;
}
not_regex_trailing_slash_POST_method:
}
elseif (0 === strpos($pathinfo, '/not-trailing/simple')) {
// simple_not_trailing_slash_no_methods
if ('/not-trailing/simple/no-methods' === $pathinfo) {
return array('_route' => 'simple_not_trailing_slash_no_methods');
}
// simple_not_trailing_slash_GET_method
if ('/not-trailing/simple/get-method' === $pathinfo) {
$ret = array('_route' => 'simple_not_trailing_slash_GET_method');
if (!in_array($canonicalMethod, array('GET'))) {
$allow = array_merge($allow, array('GET'));
goto not_simple_not_trailing_slash_GET_method;
}
return $ret;
}
not_simple_not_trailing_slash_GET_method:
// simple_not_trailing_slash_HEAD_method
if ('/not-trailing/simple/head-method' === $pathinfo) {
$ret = array('_route' => 'simple_not_trailing_slash_HEAD_method');
if (!in_array($requestMethod, array('HEAD'))) {
$allow = array_merge($allow, array('HEAD'));
goto not_simple_not_trailing_slash_HEAD_method;
}
return $ret;
}
not_simple_not_trailing_slash_HEAD_method:
// simple_not_trailing_slash_POST_method
if ('/not-trailing/simple/post-method' === $pathinfo) {
$ret = array('_route' => 'simple_not_trailing_slash_POST_method');
if (!in_array($requestMethod, array('POST'))) {
$allow = array_merge($allow, array('POST'));
goto not_simple_not_trailing_slash_POST_method;
}
return $ret;
}
not_simple_not_trailing_slash_POST_method:
}
elseif (0 === strpos($pathinfo, '/not-trailing/regex')) {
// regex_not_trailing_slash_no_methods
if (0 === strpos($pathinfo, '/not-trailing/regex/no-methods') && preg_match('#^/not\\-trailing/regex/no\\-methods/(?P<param>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_no_methods')), array ());
}
// regex_not_trailing_slash_GET_method
if (0 === strpos($pathinfo, '/not-trailing/regex/get-method') && preg_match('#^/not\\-trailing/regex/get\\-method/(?P<param>[^/]++)$#sD', $pathinfo, $matches)) {
$ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_GET_method')), array ());
if (!in_array($canonicalMethod, array('GET'))) {
$allow = array_merge($allow, array('GET'));
goto not_regex_not_trailing_slash_GET_method;
}
return $ret;
}
not_regex_not_trailing_slash_GET_method:
// regex_not_trailing_slash_HEAD_method
if (0 === strpos($pathinfo, '/not-trailing/regex/head-method') && preg_match('#^/not\\-trailing/regex/head\\-method/(?P<param>[^/]++)$#sD', $pathinfo, $matches)) {
$ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_HEAD_method')), array ());
if (!in_array($requestMethod, array('HEAD'))) {
$allow = array_merge($allow, array('HEAD'));
goto not_regex_not_trailing_slash_HEAD_method;
}
return $ret;
}
not_regex_not_trailing_slash_HEAD_method:
// regex_not_trailing_slash_POST_method
if (0 === strpos($pathinfo, '/not-trailing/regex/post-method') && preg_match('#^/not\\-trailing/regex/post\\-method/(?P<param>[^/]++)$#sD', $pathinfo, $matches)) {
$ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_POST_method')), array ());
if (!in_array($requestMethod, array('POST'))) {
$allow = array_merge($allow, array('POST'));
goto not_regex_not_trailing_slash_POST_method;
}
return $ret;
}
not_regex_not_trailing_slash_POST_method:
}
if ('/' === $pathinfo && !$allow) {
throw new Symfony\Component\Routing\Exception\NoConfigurationException();
}
throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException();
}
}

View file

@ -1,249 +0,0 @@
<?php
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\RequestContext;
/**
* This class has been auto-generated
* by the Symfony Routing Component.
*/
class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\RedirectableUrlMatcher
{
public function __construct(RequestContext $context)
{
$this->context = $context;
}
public function match($rawPathinfo)
{
$allow = array();
$pathinfo = rawurldecode($rawPathinfo);
$trimmedPathinfo = rtrim($pathinfo, '/');
$context = $this->context;
$request = $this->request ?: $this->createRequest($pathinfo);
$requestMethod = $canonicalMethod = $context->getMethod();
if ('HEAD' === $requestMethod) {
$canonicalMethod = 'GET';
}
if (0 === strpos($pathinfo, '/trailing/simple')) {
// simple_trailing_slash_no_methods
if ('/trailing/simple/no-methods' === $trimmedPathinfo) {
$ret = array('_route' => 'simple_trailing_slash_no_methods');
if ('/' === substr($pathinfo, -1)) {
// no-op
} elseif ('GET' !== $canonicalMethod) {
goto not_simple_trailing_slash_no_methods;
} else {
return array_replace($ret, $this->redirect($rawPathinfo.'/', 'simple_trailing_slash_no_methods'));
}
return $ret;
}
not_simple_trailing_slash_no_methods:
// simple_trailing_slash_GET_method
if ('/trailing/simple/get-method' === $trimmedPathinfo) {
$ret = array('_route' => 'simple_trailing_slash_GET_method');
if ('/' === substr($pathinfo, -1)) {
// no-op
} elseif ('GET' !== $canonicalMethod) {
goto not_simple_trailing_slash_GET_method;
} else {
return array_replace($ret, $this->redirect($rawPathinfo.'/', 'simple_trailing_slash_GET_method'));
}
if (!in_array($canonicalMethod, array('GET'))) {
$allow = array_merge($allow, array('GET'));
goto not_simple_trailing_slash_GET_method;
}
return $ret;
}
not_simple_trailing_slash_GET_method:
// simple_trailing_slash_HEAD_method
if ('/trailing/simple/head-method/' === $pathinfo) {
$ret = array('_route' => 'simple_trailing_slash_HEAD_method');
if (!in_array($requestMethod, array('HEAD'))) {
$allow = array_merge($allow, array('HEAD'));
goto not_simple_trailing_slash_HEAD_method;
}
return $ret;
}
not_simple_trailing_slash_HEAD_method:
// simple_trailing_slash_POST_method
if ('/trailing/simple/post-method/' === $pathinfo) {
$ret = array('_route' => 'simple_trailing_slash_POST_method');
if (!in_array($requestMethod, array('POST'))) {
$allow = array_merge($allow, array('POST'));
goto not_simple_trailing_slash_POST_method;
}
return $ret;
}
not_simple_trailing_slash_POST_method:
}
elseif (0 === strpos($pathinfo, '/trailing/regex')) {
// regex_trailing_slash_no_methods
if (0 === strpos($pathinfo, '/trailing/regex/no-methods') && preg_match('#^/trailing/regex/no\\-methods/(?P<param>[^/]++)/?$#sD', $pathinfo, $matches)) {
$ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_no_methods')), array ());
if ('/' === substr($pathinfo, -1)) {
// no-op
} elseif ('GET' !== $canonicalMethod) {
goto not_regex_trailing_slash_no_methods;
} else {
return array_replace($ret, $this->redirect($rawPathinfo.'/', 'regex_trailing_slash_no_methods'));
}
return $ret;
}
not_regex_trailing_slash_no_methods:
// regex_trailing_slash_GET_method
if (0 === strpos($pathinfo, '/trailing/regex/get-method') && preg_match('#^/trailing/regex/get\\-method/(?P<param>[^/]++)/?$#sD', $pathinfo, $matches)) {
$ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_GET_method')), array ());
if ('/' === substr($pathinfo, -1)) {
// no-op
} elseif ('GET' !== $canonicalMethod) {
goto not_regex_trailing_slash_GET_method;
} else {
return array_replace($ret, $this->redirect($rawPathinfo.'/', 'regex_trailing_slash_GET_method'));
}
if (!in_array($canonicalMethod, array('GET'))) {
$allow = array_merge($allow, array('GET'));
goto not_regex_trailing_slash_GET_method;
}
return $ret;
}
not_regex_trailing_slash_GET_method:
// regex_trailing_slash_HEAD_method
if (0 === strpos($pathinfo, '/trailing/regex/head-method') && preg_match('#^/trailing/regex/head\\-method/(?P<param>[^/]++)/$#sD', $pathinfo, $matches)) {
$ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_HEAD_method')), array ());
if (!in_array($requestMethod, array('HEAD'))) {
$allow = array_merge($allow, array('HEAD'));
goto not_regex_trailing_slash_HEAD_method;
}
return $ret;
}
not_regex_trailing_slash_HEAD_method:
// regex_trailing_slash_POST_method
if (0 === strpos($pathinfo, '/trailing/regex/post-method') && preg_match('#^/trailing/regex/post\\-method/(?P<param>[^/]++)/$#sD', $pathinfo, $matches)) {
$ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_POST_method')), array ());
if (!in_array($requestMethod, array('POST'))) {
$allow = array_merge($allow, array('POST'));
goto not_regex_trailing_slash_POST_method;
}
return $ret;
}
not_regex_trailing_slash_POST_method:
}
elseif (0 === strpos($pathinfo, '/not-trailing/simple')) {
// simple_not_trailing_slash_no_methods
if ('/not-trailing/simple/no-methods' === $pathinfo) {
return array('_route' => 'simple_not_trailing_slash_no_methods');
}
// simple_not_trailing_slash_GET_method
if ('/not-trailing/simple/get-method' === $pathinfo) {
$ret = array('_route' => 'simple_not_trailing_slash_GET_method');
if (!in_array($canonicalMethod, array('GET'))) {
$allow = array_merge($allow, array('GET'));
goto not_simple_not_trailing_slash_GET_method;
}
return $ret;
}
not_simple_not_trailing_slash_GET_method:
// simple_not_trailing_slash_HEAD_method
if ('/not-trailing/simple/head-method' === $pathinfo) {
$ret = array('_route' => 'simple_not_trailing_slash_HEAD_method');
if (!in_array($requestMethod, array('HEAD'))) {
$allow = array_merge($allow, array('HEAD'));
goto not_simple_not_trailing_slash_HEAD_method;
}
return $ret;
}
not_simple_not_trailing_slash_HEAD_method:
// simple_not_trailing_slash_POST_method
if ('/not-trailing/simple/post-method' === $pathinfo) {
$ret = array('_route' => 'simple_not_trailing_slash_POST_method');
if (!in_array($requestMethod, array('POST'))) {
$allow = array_merge($allow, array('POST'));
goto not_simple_not_trailing_slash_POST_method;
}
return $ret;
}
not_simple_not_trailing_slash_POST_method:
}
elseif (0 === strpos($pathinfo, '/not-trailing/regex')) {
// regex_not_trailing_slash_no_methods
if (0 === strpos($pathinfo, '/not-trailing/regex/no-methods') && preg_match('#^/not\\-trailing/regex/no\\-methods/(?P<param>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_no_methods')), array ());
}
// regex_not_trailing_slash_GET_method
if (0 === strpos($pathinfo, '/not-trailing/regex/get-method') && preg_match('#^/not\\-trailing/regex/get\\-method/(?P<param>[^/]++)$#sD', $pathinfo, $matches)) {
$ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_GET_method')), array ());
if (!in_array($canonicalMethod, array('GET'))) {
$allow = array_merge($allow, array('GET'));
goto not_regex_not_trailing_slash_GET_method;
}
return $ret;
}
not_regex_not_trailing_slash_GET_method:
// regex_not_trailing_slash_HEAD_method
if (0 === strpos($pathinfo, '/not-trailing/regex/head-method') && preg_match('#^/not\\-trailing/regex/head\\-method/(?P<param>[^/]++)$#sD', $pathinfo, $matches)) {
$ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_HEAD_method')), array ());
if (!in_array($requestMethod, array('HEAD'))) {
$allow = array_merge($allow, array('HEAD'));
goto not_regex_not_trailing_slash_HEAD_method;
}
return $ret;
}
not_regex_not_trailing_slash_HEAD_method:
// regex_not_trailing_slash_POST_method
if (0 === strpos($pathinfo, '/not-trailing/regex/post-method') && preg_match('#^/not\\-trailing/regex/post\\-method/(?P<param>[^/]++)$#sD', $pathinfo, $matches)) {
$ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_POST_method')), array ());
if (!in_array($requestMethod, array('POST'))) {
$allow = array_merge($allow, array('POST'));
goto not_regex_not_trailing_slash_POST_method;
}
return $ret;
}
not_regex_not_trailing_slash_POST_method:
}
if ('/' === $pathinfo && !$allow) {
throw new Symfony\Component\Routing\Exception\NoConfigurationException();
}
throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException();
}
}

View file

@ -1,2 +0,0 @@
blog_show:
defaults: { _controller: MyBlogBundle:Blog:show }

View file

@ -1,20 +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="blog" path="/blog">
<default key="_controller">
<string>AcmeBlogBundle:Blog:index</string>
</default>
<default key="values">
<list>
<bool>true</bool>
<int>1</int>
<float>3.5</float>
<string>foo</string>
</list>
</default>
</route>
</routes>

View file

@ -1,22 +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="blog" path="/blog">
<default key="_controller">
<string>AcmeBlogBundle:Blog:index</string>
</default>
<default key="values">
<list>
<list>
<bool>true</bool>
<int>1</int>
<float>3.5</float>
<string>foo</string>
</list>
</list>
</default>
</route>
</routes>

View file

@ -1,22 +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="blog" path="/blog">
<default key="_controller">
<string>AcmeBlogBundle:Blog:index</string>
</default>
<default key="values">
<map>
<list key="list">
<bool>true</bool>
<int>1</int>
<float>3.5</float>
<string>foo</string>
</list>
</map>
</default>
</route>
</routes>

View file

@ -1,22 +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="blog" path="/blog">
<default key="_controller">
<string>AcmeBlogBundle:Blog:index</string>
</default>
<default key="list">
<list>
<bool xsi:nil="true" />
<int xsi:nil="true" />
<float xsi:nil="1" />
<string xsi:nil="true" />
<list xsi:nil="true" />
<map xsi:nil="true" />
</list>
</default>
</route>
</routes>

View file

@ -1,20 +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="blog" path="/blog">
<default key="_controller">
<string>AcmeBlogBundle:Blog:index</string>
</default>
<default key="values">
<map>
<bool key="public">true</bool>
<int key="page">1</int>
<float key="price">3.5</float>
<string key="title">foo</string>
</map>
</default>
</route>
</routes>

View file

@ -1,22 +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="blog" path="/blog">
<default key="_controller">
<string>AcmeBlogBundle:Blog:index</string>
</default>
<default key="values">
<list>
<map>
<bool key="public">true</bool>
<int key="page">1</int>
<float key="price">3.5</float>
<string key="title">foo</string>
</map>
</list>
</default>
</route>
</routes>

View file

@ -1,22 +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="blog" path="/blog">
<default key="_controller">
<string>AcmeBlogBundle:Blog:index</string>
</default>
<default key="values">
<map>
<map key="map">
<bool key="public">true</bool>
<int key="page">1</int>
<float key="price">3.5</float>
<string key="title">foo</string>
</map>
</map>
</default>
</route>
</routes>

View file

@ -1,22 +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="blog" path="/blog">
<default key="_controller">
<string>AcmeBlogBundle:Blog:index</string>
</default>
<default key="map">
<map>
<bool key="boolean" xsi:nil="true" />
<int key="integer" xsi:nil="true" />
<float key="float" xsi:nil="true" />
<string key="string" xsi:nil="1" />
<list key="list" xsi:nil="true" />
<map key="map" xsi:nil="true" />
</map>
</default>
</route>
</routes>

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">
<route path="/test"></route>
</routes>

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">
<route id="myroute"></route>
</routes>

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<r:routes xmlns:r="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">
<r:route id="blog_show" path="/blog/{slug}" host="{_locale}.example.com">
<r:default key="_controller">MyBundle:Blog:show</r:default>
<requirement xmlns="http://symfony.com/schema/routing" key="slug">\w+</requirement>
<r2:requirement xmlns:r2="http://symfony.com/schema/routing" key="_locale">en|fr|de</r2:requirement>
<r:option key="compiler_class">RouteCompiler</r:option>
<r:default key="page">
<r3:int xmlns:r3="http://symfony.com/schema/routing">1</r3:int>
</r:default>
</r:route>
</r:routes>

View file

@ -1,3 +0,0 @@
blog_show:
resource: validpattern.yml
path: /test

View file

@ -1,3 +0,0 @@
blog_show:
path: /blog/{slug}
type: custom

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="blog_show" path="/blog/{slug}">
<default key="_controller">MyBundle:Blog:show</default>
<!-- </route> -->
</routes>

View file

@ -1 +0,0 @@
foo

View file

@ -1 +0,0 @@
route: string

View file

@ -1,3 +0,0 @@
someroute:
resource: path/to/some.yml
name_prefix: test_

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">
<foo>bar</foo>
</routes>

View file

@ -1,12 +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="blog_show" path="/blog/{slug}">
<default key="_controller">MyBundle:Blog:show</default>
<option key="compiler_class">RouteCompiler</option>
<foo key="bar">baz</foo>
</route>
</routes>

View file

@ -1,12 +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="blog_show" path="/blog/{slug}">
<default key="foo" xsi:nil="true" />
<default key="bar" xsi:nil="1" />
<default key="foobar" xsi:nil="false">foo</default>
<default key="baz" xsi:nil="0">bar</default>
</route>
</routes>

View file

@ -1,33 +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="blog" path="/blog">
<default key="_controller">
<string>AcmeBlogBundle:Blog:index</string>
</default>
<default key="slug" xsi:nil="true" />
<default key="published">
<bool>true</bool>
</default>
<default key="page">
<int>1</int>
</default>
<default key="price">
<float>3.5</float>
</default>
<default key="archived">
<bool>false</bool>
</default>
<default key="free">
<bool>1</bool>
</default>
<default key="locked">
<bool>0</bool>
</default>
<default key="foo" xsi:nil="true" />
<default key="bar" xsi:nil="1" />
</route>
</routes>

View file

@ -1,2 +0,0 @@
"#$péß^a|":
path: "true"

View file

@ -1,18 +0,0 @@
<?php
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
$collection = new RouteCollection();
$collection->add('blog_show', new Route(
'/blog/{slug}',
array('_controller' => 'MyBlogBundle:Blog:show'),
array('locale' => '\w+'),
array('compiler_class' => 'RouteCompiler'),
'{locale}.example.com',
array('https'),
array('GET', 'POST', 'put', 'OpTiOnS'),
'context.getMethod() == "GET"'
));
return $collection;

View file

@ -1,15 +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="blog_show" path="/blog/{slug}" host="{locale}.example.com" methods="GET|POST put,OpTiOnS" schemes="hTTps">
<default key="_controller">MyBundle:Blog:show</default>
<requirement key="locale">\w+</requirement>
<option key="compiler_class">RouteCompiler</option>
<condition>context.getMethod() == "GET"</condition>
</route>
<route id="blog_show_inherited" path="/blog/{slug}" />
</routes>

View file

@ -1,13 +0,0 @@
blog_show:
path: /blog/{slug}
defaults: { _controller: "MyBundle:Blog:show" }
host: "{locale}.example.com"
requirements: { 'locale': '\w+' }
methods: ['GET','POST','put','OpTiOnS']
schemes: ['https']
condition: 'context.getMethod() == "GET"'
options:
compiler_class: RouteCompiler
blog_show_inherited:
path: /blog/{slug}

View file

@ -1,18 +0,0 @@
<?php
/** @var $loader \Symfony\Component\Routing\Loader\PhpFileLoader */
/** @var \Symfony\Component\Routing\RouteCollection $collection */
$collection = $loader->import('validpattern.php');
$collection->addDefaults(array(
'foo' => 123,
));
$collection->addRequirements(array(
'foo' => '\d+',
));
$collection->addOptions(array(
'foo' => 'bar',
));
$collection->setCondition('context.getMethod() == "POST"');
$collection->addPrefix('/prefix');
return $collection;

View file

@ -1,13 +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="validpattern.xml" prefix="/{foo}" host="">
<default key="foo">123</default>
<requirement key="foo">\d+</requirement>
<option key="foo">bar</option>
<condition>context.getMethod() == "POST"</condition>
</import>
</routes>

View file

@ -1,8 +0,0 @@
_blog:
resource: validpattern.yml
prefix: /{foo}
defaults: { 'foo': '123' }
requirements: { 'foo': '\d+' }
options: { 'foo': 'bar' }
host: ""
condition: 'context.getMethod() == "POST"'

View file

@ -1,5 +0,0 @@
<?php
$path = '/1/2/3';
return new \Symfony\Component\Routing\RouteCollection();

View file

@ -1,3 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE foo>
<foo></foo>

View file

@ -1,181 +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\Generator\Dumper;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Routing\Generator\Dumper\PhpGeneratorDumper;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
class PhpGeneratorDumperTest extends TestCase
{
/**
* @var RouteCollection
*/
private $routeCollection;
/**
* @var PhpGeneratorDumper
*/
private $generatorDumper;
/**
* @var string
*/
private $testTmpFilepath;
/**
* @var string
*/
private $largeTestTmpFilepath;
protected function setUp()
{
parent::setUp();
$this->routeCollection = new RouteCollection();
$this->generatorDumper = new PhpGeneratorDumper($this->routeCollection);
$this->testTmpFilepath = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'php_generator.'.$this->getName().'.php';
$this->largeTestTmpFilepath = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'php_generator.'.$this->getName().'.large.php';
@unlink($this->testTmpFilepath);
@unlink($this->largeTestTmpFilepath);
}
protected function tearDown()
{
parent::tearDown();
@unlink($this->testTmpFilepath);
$this->routeCollection = null;
$this->generatorDumper = null;
$this->testTmpFilepath = null;
}
public function testDumpWithRoutes()
{
$this->routeCollection->add('Test', new Route('/testing/{foo}'));
$this->routeCollection->add('Test2', new Route('/testing2'));
file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump());
include $this->testTmpFilepath;
$projectUrlGenerator = new \ProjectUrlGenerator(new RequestContext('/app.php'));
$absoluteUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL);
$absoluteUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), UrlGeneratorInterface::ABSOLUTE_URL);
$relativeUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_PATH);
$relativeUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), UrlGeneratorInterface::ABSOLUTE_PATH);
$this->assertEquals('http://localhost/app.php/testing/bar', $absoluteUrlWithParameter);
$this->assertEquals('http://localhost/app.php/testing2', $absoluteUrlWithoutParameter);
$this->assertEquals('/app.php/testing/bar', $relativeUrlWithParameter);
$this->assertEquals('/app.php/testing2', $relativeUrlWithoutParameter);
}
public function testDumpWithTooManyRoutes()
{
if (\defined('HHVM_VERSION_ID')) {
$this->markTestSkipped('HHVM consumes too much memory on this test.');
}
$this->routeCollection->add('Test', new Route('/testing/{foo}'));
for ($i = 0; $i < 32769; ++$i) {
$this->routeCollection->add('route_'.$i, new Route('/route_'.$i));
}
$this->routeCollection->add('Test2', new Route('/testing2'));
file_put_contents($this->largeTestTmpFilepath, $this->generatorDumper->dump(array(
'class' => 'ProjectLargeUrlGenerator',
)));
$this->routeCollection = $this->generatorDumper = null;
include $this->largeTestTmpFilepath;
$projectUrlGenerator = new \ProjectLargeUrlGenerator(new RequestContext('/app.php'));
$absoluteUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL);
$absoluteUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), UrlGeneratorInterface::ABSOLUTE_URL);
$relativeUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_PATH);
$relativeUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), UrlGeneratorInterface::ABSOLUTE_PATH);
$this->assertEquals('http://localhost/app.php/testing/bar', $absoluteUrlWithParameter);
$this->assertEquals('http://localhost/app.php/testing2', $absoluteUrlWithoutParameter);
$this->assertEquals('/app.php/testing/bar', $relativeUrlWithParameter);
$this->assertEquals('/app.php/testing2', $relativeUrlWithoutParameter);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testDumpWithoutRoutes()
{
file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump(array('class' => 'WithoutRoutesUrlGenerator')));
include $this->testTmpFilepath;
$projectUrlGenerator = new \WithoutRoutesUrlGenerator(new RequestContext('/app.php'));
$projectUrlGenerator->generate('Test', array());
}
/**
* @expectedException \Symfony\Component\Routing\Exception\RouteNotFoundException
*/
public function testGenerateNonExistingRoute()
{
$this->routeCollection->add('Test', new Route('/test'));
file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump(array('class' => 'NonExistingRoutesUrlGenerator')));
include $this->testTmpFilepath;
$projectUrlGenerator = new \NonExistingRoutesUrlGenerator(new RequestContext());
$url = $projectUrlGenerator->generate('NonExisting', array());
}
public function testDumpForRouteWithDefaults()
{
$this->routeCollection->add('Test', new Route('/testing/{foo}', array('foo' => 'bar')));
file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump(array('class' => 'DefaultRoutesUrlGenerator')));
include $this->testTmpFilepath;
$projectUrlGenerator = new \DefaultRoutesUrlGenerator(new RequestContext());
$url = $projectUrlGenerator->generate('Test', array());
$this->assertEquals('/testing', $url);
}
public function testDumpWithSchemeRequirement()
{
$this->routeCollection->add('Test1', new Route('/testing', array(), array(), array(), '', array('ftp', 'https')));
file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump(array('class' => 'SchemeUrlGenerator')));
include $this->testTmpFilepath;
$projectUrlGenerator = new \SchemeUrlGenerator(new RequestContext('/app.php'));
$absoluteUrl = $projectUrlGenerator->generate('Test1', array(), UrlGeneratorInterface::ABSOLUTE_URL);
$relativeUrl = $projectUrlGenerator->generate('Test1', array(), UrlGeneratorInterface::ABSOLUTE_PATH);
$this->assertEquals('ftp://localhost/app.php/testing', $absoluteUrl);
$this->assertEquals('ftp://localhost/app.php/testing', $relativeUrl);
$projectUrlGenerator = new \SchemeUrlGenerator(new RequestContext('/app.php', 'GET', 'localhost', 'https'));
$absoluteUrl = $projectUrlGenerator->generate('Test1', array(), UrlGeneratorInterface::ABSOLUTE_URL);
$relativeUrl = $projectUrlGenerator->generate('Test1', array(), UrlGeneratorInterface::ABSOLUTE_PATH);
$this->assertEquals('https://localhost/app.php/testing', $absoluteUrl);
$this->assertEquals('/app.php/testing', $relativeUrl);
}
}

View file

@ -1,724 +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\Generator;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Routing\Generator\UrlGenerator;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
class UrlGeneratorTest extends TestCase
{
public function testAbsoluteUrlWithPort80()
{
$routes = $this->getRoutes('test', new Route('/testing'));
$url = $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL);
$this->assertEquals('http://localhost/app.php/testing', $url);
}
public function testAbsoluteSecureUrlWithPort443()
{
$routes = $this->getRoutes('test', new Route('/testing'));
$url = $this->getGenerator($routes, array('scheme' => 'https'))->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL);
$this->assertEquals('https://localhost/app.php/testing', $url);
}
public function testAbsoluteUrlWithNonStandardPort()
{
$routes = $this->getRoutes('test', new Route('/testing'));
$url = $this->getGenerator($routes, array('httpPort' => 8080))->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL);
$this->assertEquals('http://localhost:8080/app.php/testing', $url);
}
public function testAbsoluteSecureUrlWithNonStandardPort()
{
$routes = $this->getRoutes('test', new Route('/testing'));
$url = $this->getGenerator($routes, array('httpsPort' => 8080, 'scheme' => 'https'))->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL);
$this->assertEquals('https://localhost:8080/app.php/testing', $url);
}
public function testRelativeUrlWithoutParameters()
{
$routes = $this->getRoutes('test', new Route('/testing'));
$url = $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_PATH);
$this->assertEquals('/app.php/testing', $url);
}
public function testRelativeUrlWithParameter()
{
$routes = $this->getRoutes('test', new Route('/testing/{foo}'));
$url = $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_PATH);
$this->assertEquals('/app.php/testing/bar', $url);
}
public function testRelativeUrlWithNullParameter()
{
$routes = $this->getRoutes('test', new Route('/testing.{format}', array('format' => null)));
$url = $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_PATH);
$this->assertEquals('/app.php/testing', $url);
}
/**
* @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
*/
public function testRelativeUrlWithNullParameterButNotOptional()
{
$routes = $this->getRoutes('test', new Route('/testing/{foo}/bar', array('foo' => null)));
// This must raise an exception because the default requirement for "foo" is "[^/]+" which is not met with these params.
// Generating path "/testing//bar" would be wrong as matching this route would fail.
$this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_PATH);
}
public function testRelativeUrlWithOptionalZeroParameter()
{
$routes = $this->getRoutes('test', new Route('/testing/{page}'));
$url = $this->getGenerator($routes)->generate('test', array('page' => 0), UrlGeneratorInterface::ABSOLUTE_PATH);
$this->assertEquals('/app.php/testing/0', $url);
}
public function testNotPassedOptionalParameterInBetween()
{
$routes = $this->getRoutes('test', new Route('/{slug}/{page}', array('slug' => 'index', 'page' => 0)));
$this->assertSame('/app.php/index/1', $this->getGenerator($routes)->generate('test', array('page' => 1)));
$this->assertSame('/app.php/', $this->getGenerator($routes)->generate('test'));
}
public function testRelativeUrlWithExtraParameters()
{
$routes = $this->getRoutes('test', new Route('/testing'));
$url = $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_PATH);
$this->assertEquals('/app.php/testing?foo=bar', $url);
}
public function testAbsoluteUrlWithExtraParameters()
{
$routes = $this->getRoutes('test', new Route('/testing'));
$url = $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL);
$this->assertEquals('http://localhost/app.php/testing?foo=bar', $url);
}
public function testUrlWithNullExtraParameters()
{
$routes = $this->getRoutes('test', new Route('/testing'));
$url = $this->getGenerator($routes)->generate('test', array('foo' => null), UrlGeneratorInterface::ABSOLUTE_URL);
$this->assertEquals('http://localhost/app.php/testing', $url);
}
public function testUrlWithExtraParametersFromGlobals()
{
$routes = $this->getRoutes('test', new Route('/testing'));
$generator = $this->getGenerator($routes);
$context = new RequestContext('/app.php');
$context->setParameter('bar', 'bar');
$generator->setContext($context);
$url = $generator->generate('test', array('foo' => 'bar'));
$this->assertEquals('/app.php/testing?foo=bar', $url);
}
public function testUrlWithGlobalParameter()
{
$routes = $this->getRoutes('test', new Route('/testing/{foo}'));
$generator = $this->getGenerator($routes);
$context = new RequestContext('/app.php');
$context->setParameter('foo', 'bar');
$generator->setContext($context);
$url = $generator->generate('test', array());
$this->assertEquals('/app.php/testing/bar', $url);
}
public function testGlobalParameterHasHigherPriorityThanDefault()
{
$routes = $this->getRoutes('test', new Route('/{_locale}', array('_locale' => 'en')));
$generator = $this->getGenerator($routes);
$context = new RequestContext('/app.php');
$context->setParameter('_locale', 'de');
$generator->setContext($context);
$url = $generator->generate('test', array());
$this->assertSame('/app.php/de', $url);
}
/**
* @expectedException \Symfony\Component\Routing\Exception\RouteNotFoundException
*/
public function testGenerateWithoutRoutes()
{
$routes = $this->getRoutes('foo', new Route('/testing/{foo}'));
$this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL);
}
/**
* @expectedException \Symfony\Component\Routing\Exception\MissingMandatoryParametersException
*/
public function testGenerateForRouteWithoutMandatoryParameter()
{
$routes = $this->getRoutes('test', new Route('/testing/{foo}'));
$this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL);
}
/**
* @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
*/
public function testGenerateForRouteWithInvalidOptionalParameter()
{
$routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+')));
$this->getGenerator($routes)->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL);
}
/**
* @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
*/
public function testGenerateForRouteWithInvalidParameter()
{
$routes = $this->getRoutes('test', new Route('/testing/{foo}', array(), array('foo' => '1|2')));
$this->getGenerator($routes)->generate('test', array('foo' => '0'), UrlGeneratorInterface::ABSOLUTE_URL);
}
public function testGenerateForRouteWithInvalidOptionalParameterNonStrict()
{
$routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+')));
$generator = $this->getGenerator($routes);
$generator->setStrictRequirements(false);
$this->assertNull($generator->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL));
}
public function testGenerateForRouteWithInvalidOptionalParameterNonStrictWithLogger()
{
$routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+')));
$logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
$logger->expects($this->once())
->method('error');
$generator = $this->getGenerator($routes, array(), $logger);
$generator->setStrictRequirements(false);
$this->assertNull($generator->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL));
}
public function testGenerateForRouteWithInvalidParameterButDisabledRequirementsCheck()
{
$routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+')));
$generator = $this->getGenerator($routes);
$generator->setStrictRequirements(null);
$this->assertSame('/app.php/testing/bar', $generator->generate('test', array('foo' => 'bar')));
}
/**
* @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
*/
public function testGenerateForRouteWithInvalidMandatoryParameter()
{
$routes = $this->getRoutes('test', new Route('/testing/{foo}', array(), array('foo' => 'd+')));
$this->getGenerator($routes)->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL);
}
/**
* @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
*/
public function testGenerateForRouteWithInvalidUtf8Parameter()
{
$routes = $this->getRoutes('test', new Route('/testing/{foo}', array(), array('foo' => '\pL+'), array('utf8' => true)));
$this->getGenerator($routes)->generate('test', array('foo' => 'abc123'), UrlGeneratorInterface::ABSOLUTE_URL);
}
/**
* @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
*/
public function testRequiredParamAndEmptyPassed()
{
$routes = $this->getRoutes('test', new Route('/{slug}', array(), array('slug' => '.+')));
$this->getGenerator($routes)->generate('test', array('slug' => ''));
}
public function testSchemeRequirementDoesNothingIfSameCurrentScheme()
{
$routes = $this->getRoutes('test', new Route('/', array(), array(), array(), '', array('http')));
$this->assertEquals('/app.php/', $this->getGenerator($routes)->generate('test'));
$routes = $this->getRoutes('test', new Route('/', array(), array(), array(), '', array('https')));
$this->assertEquals('/app.php/', $this->getGenerator($routes, array('scheme' => 'https'))->generate('test'));
}
public function testSchemeRequirementForcesAbsoluteUrl()
{
$routes = $this->getRoutes('test', new Route('/', array(), array(), array(), '', array('https')));
$this->assertEquals('https://localhost/app.php/', $this->getGenerator($routes)->generate('test'));
$routes = $this->getRoutes('test', new Route('/', array(), array(), array(), '', array('http')));
$this->assertEquals('http://localhost/app.php/', $this->getGenerator($routes, array('scheme' => 'https'))->generate('test'));
}
public function testSchemeRequirementCreatesUrlForFirstRequiredScheme()
{
$routes = $this->getRoutes('test', new Route('/', array(), array(), array(), '', array('Ftp', 'https')));
$this->assertEquals('ftp://localhost/app.php/', $this->getGenerator($routes)->generate('test'));
}
public function testPathWithTwoStartingSlashes()
{
$routes = $this->getRoutes('test', new Route('//path-and-not-domain'));
// this must not generate '//path-and-not-domain' because that would be a network path
$this->assertSame('/path-and-not-domain', $this->getGenerator($routes, array('BaseUrl' => ''))->generate('test'));
}
public function testNoTrailingSlashForMultipleOptionalParameters()
{
$routes = $this->getRoutes('test', new Route('/category/{slug1}/{slug2}/{slug3}', array('slug2' => null, 'slug3' => null)));
$this->assertEquals('/app.php/category/foo', $this->getGenerator($routes)->generate('test', array('slug1' => 'foo')));
}
public function testWithAnIntegerAsADefaultValue()
{
$routes = $this->getRoutes('test', new Route('/{default}', array('default' => 0)));
$this->assertEquals('/app.php/foo', $this->getGenerator($routes)->generate('test', array('default' => 'foo')));
}
public function testNullForOptionalParameterIsIgnored()
{
$routes = $this->getRoutes('test', new Route('/test/{default}', array('default' => 0)));
$this->assertEquals('/app.php/test', $this->getGenerator($routes)->generate('test', array('default' => null)));
}
public function testQueryParamSameAsDefault()
{
$routes = $this->getRoutes('test', new Route('/test', array('page' => 1)));
$this->assertSame('/app.php/test?page=2', $this->getGenerator($routes)->generate('test', array('page' => 2)));
$this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test', array('page' => 1)));
$this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test', array('page' => '1')));
$this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test'));
}
public function testArrayQueryParamSameAsDefault()
{
$routes = $this->getRoutes('test', new Route('/test', array('array' => array('foo', 'bar'))));
$this->assertSame('/app.php/test?array%5B0%5D=bar&array%5B1%5D=foo', $this->getGenerator($routes)->generate('test', array('array' => array('bar', 'foo'))));
$this->assertSame('/app.php/test?array%5Ba%5D=foo&array%5Bb%5D=bar', $this->getGenerator($routes)->generate('test', array('array' => array('a' => 'foo', 'b' => 'bar'))));
$this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test', array('array' => array('foo', 'bar'))));
$this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test', array('array' => array(1 => 'bar', 0 => 'foo'))));
$this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test'));
}
public function testGenerateWithSpecialRouteName()
{
$routes = $this->getRoutes('$péß^a|', new Route('/bar'));
$this->assertSame('/app.php/bar', $this->getGenerator($routes)->generate('$péß^a|'));
}
public function testUrlEncoding()
{
$expectedPath = '/app.php/@:%5B%5D/%28%29*%27%22%20+,;-._~%26%24%3C%3E|%7B%7D%25%5C%5E%60!%3Ffoo=bar%23id'
.'/@:%5B%5D/%28%29*%27%22%20+,;-._~%26%24%3C%3E|%7B%7D%25%5C%5E%60!%3Ffoo=bar%23id'
.'?query=%40%3A%5B%5D/%28%29%2A%27%22%20%2B%2C%3B-._~%26%24%3C%3E%7C%7B%7D%25%5C%5E%60%21%3Ffoo%3Dbar%23id';
// This tests the encoding of reserved characters that are used for delimiting of URI components (defined in RFC 3986)
// and other special ASCII chars. These chars are tested as static text path, variable path and query param.
$chars = '@:[]/()*\'" +,;-._~&$<>|{}%\\^`!?foo=bar#id';
$routes = $this->getRoutes('test', new Route("/$chars/{varpath}", array(), array('varpath' => '.+')));
$this->assertSame($expectedPath, $this->getGenerator($routes)->generate('test', array(
'varpath' => $chars,
'query' => $chars,
)));
}
public function testEncodingOfRelativePathSegments()
{
$routes = $this->getRoutes('test', new Route('/dir/../dir/..'));
$this->assertSame('/app.php/dir/%2E%2E/dir/%2E%2E', $this->getGenerator($routes)->generate('test'));
$routes = $this->getRoutes('test', new Route('/dir/./dir/.'));
$this->assertSame('/app.php/dir/%2E/dir/%2E', $this->getGenerator($routes)->generate('test'));
$routes = $this->getRoutes('test', new Route('/a./.a/a../..a/...'));
$this->assertSame('/app.php/a./.a/a../..a/...', $this->getGenerator($routes)->generate('test'));
}
public function testAdjacentVariables()
{
$routes = $this->getRoutes('test', new Route('/{x}{y}{z}.{_format}', array('z' => 'default-z', '_format' => 'html'), array('y' => '\d+')));
$generator = $this->getGenerator($routes);
$this->assertSame('/app.php/foo123', $generator->generate('test', array('x' => 'foo', 'y' => '123')));
$this->assertSame('/app.php/foo123bar.xml', $generator->generate('test', array('x' => 'foo', 'y' => '123', 'z' => 'bar', '_format' => 'xml')));
// The default requirement for 'x' should not allow the separator '.' in this case because it would otherwise match everything
// and following optional variables like _format could never match.
$this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Routing\Exception\InvalidParameterException');
$generator->generate('test', array('x' => 'do.t', 'y' => '123', 'z' => 'bar', '_format' => 'xml'));
}
public function testOptionalVariableWithNoRealSeparator()
{
$routes = $this->getRoutes('test', new Route('/get{what}', array('what' => 'All')));
$generator = $this->getGenerator($routes);
$this->assertSame('/app.php/get', $generator->generate('test'));
$this->assertSame('/app.php/getSites', $generator->generate('test', array('what' => 'Sites')));
}
public function testRequiredVariableWithNoRealSeparator()
{
$routes = $this->getRoutes('test', new Route('/get{what}Suffix'));
$generator = $this->getGenerator($routes);
$this->assertSame('/app.php/getSitesSuffix', $generator->generate('test', array('what' => 'Sites')));
}
public function testDefaultRequirementOfVariable()
{
$routes = $this->getRoutes('test', new Route('/{page}.{_format}'));
$generator = $this->getGenerator($routes);
$this->assertSame('/app.php/index.mobile.html', $generator->generate('test', array('page' => 'index', '_format' => 'mobile.html')));
}
/**
* @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
*/
public function testDefaultRequirementOfVariableDisallowsSlash()
{
$routes = $this->getRoutes('test', new Route('/{page}.{_format}'));
$this->getGenerator($routes)->generate('test', array('page' => 'index', '_format' => 'sl/ash'));
}
/**
* @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
*/
public function testDefaultRequirementOfVariableDisallowsNextSeparator()
{
$routes = $this->getRoutes('test', new Route('/{page}.{_format}'));
$this->getGenerator($routes)->generate('test', array('page' => 'do.t', '_format' => 'html'));
}
public function testWithHostDifferentFromContext()
{
$routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com'));
$this->assertEquals('//fr.example.com/app.php/Fabien', $this->getGenerator($routes)->generate('test', array('name' => 'Fabien', 'locale' => 'fr')));
}
public function testWithHostSameAsContext()
{
$routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com'));
$this->assertEquals('/app.php/Fabien', $this->getGenerator($routes, array('host' => 'fr.example.com'))->generate('test', array('name' => 'Fabien', 'locale' => 'fr')));
}
public function testWithHostSameAsContextAndAbsolute()
{
$routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com'));
$this->assertEquals('http://fr.example.com/app.php/Fabien', $this->getGenerator($routes, array('host' => 'fr.example.com'))->generate('test', array('name' => 'Fabien', 'locale' => 'fr'), UrlGeneratorInterface::ABSOLUTE_URL));
}
/**
* @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
*/
public function testUrlWithInvalidParameterInHost()
{
$routes = $this->getRoutes('test', new Route('/', array(), array('foo' => 'bar'), array(), '{foo}.example.com'));
$this->getGenerator($routes)->generate('test', array('foo' => 'baz'), UrlGeneratorInterface::ABSOLUTE_PATH);
}
/**
* @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
*/
public function testUrlWithInvalidParameterInHostWhenParamHasADefaultValue()
{
$routes = $this->getRoutes('test', new Route('/', array('foo' => 'bar'), array('foo' => 'bar'), array(), '{foo}.example.com'));
$this->getGenerator($routes)->generate('test', array('foo' => 'baz'), UrlGeneratorInterface::ABSOLUTE_PATH);
}
/**
* @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException
*/
public function testUrlWithInvalidParameterEqualsDefaultValueInHost()
{
$routes = $this->getRoutes('test', new Route('/', array('foo' => 'baz'), array('foo' => 'bar'), array(), '{foo}.example.com'));
$this->getGenerator($routes)->generate('test', array('foo' => 'baz'), UrlGeneratorInterface::ABSOLUTE_PATH);
}
public function testUrlWithInvalidParameterInHostInNonStrictMode()
{
$routes = $this->getRoutes('test', new Route('/', array(), array('foo' => 'bar'), array(), '{foo}.example.com'));
$generator = $this->getGenerator($routes);
$generator->setStrictRequirements(false);
$this->assertNull($generator->generate('test', array('foo' => 'baz'), UrlGeneratorInterface::ABSOLUTE_PATH));
}
public function testHostIsCaseInsensitive()
{
$routes = $this->getRoutes('test', new Route('/', array(), array('locale' => 'en|de|fr'), array(), '{locale}.FooBar.com'));
$generator = $this->getGenerator($routes);
$this->assertSame('//EN.FooBar.com/app.php/', $generator->generate('test', array('locale' => 'EN'), UrlGeneratorInterface::NETWORK_PATH));
}
public function testDefaultHostIsUsedWhenContextHostIsEmpty()
{
$routes = $this->getRoutes('test', new Route('/route', array('domain' => 'my.fallback.host'), array('domain' => '.+'), array(), '{domain}', array('http')));
$generator = $this->getGenerator($routes);
$generator->getContext()->setHost('');
$this->assertSame('http://my.fallback.host/app.php/route', $generator->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL));
}
public function testDefaultHostIsUsedWhenContextHostIsEmptyAndSchemeIsNot()
{
$routes = $this->getRoutes('test', new Route('/route', array('domain' => 'my.fallback.host'), array('domain' => '.+'), array(), '{domain}', array('http', 'https')));
$generator = $this->getGenerator($routes);
$generator->getContext()->setHost('');
$generator->getContext()->setScheme('https');
$this->assertSame('https://my.fallback.host/app.php/route', $generator->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL));
}
public function testAbsoluteUrlFallbackToRelativeIfHostIsEmptyAndSchemeIsNot()
{
$routes = $this->getRoutes('test', new Route('/route', array(), array(), array(), '', array('http', 'https')));
$generator = $this->getGenerator($routes);
$generator->getContext()->setHost('');
$generator->getContext()->setScheme('https');
$this->assertSame('/app.php/route', $generator->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL));
}
public function testGenerateNetworkPath()
{
$routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com', array('http')));
$this->assertSame('//fr.example.com/app.php/Fabien', $this->getGenerator($routes)->generate('test',
array('name' => 'Fabien', 'locale' => 'fr'), UrlGeneratorInterface::NETWORK_PATH), 'network path with different host'
);
$this->assertSame('//fr.example.com/app.php/Fabien?query=string', $this->getGenerator($routes, array('host' => 'fr.example.com'))->generate('test',
array('name' => 'Fabien', 'locale' => 'fr', 'query' => 'string'), UrlGeneratorInterface::NETWORK_PATH), 'network path although host same as context'
);
$this->assertSame('http://fr.example.com/app.php/Fabien', $this->getGenerator($routes, array('scheme' => 'https'))->generate('test',
array('name' => 'Fabien', 'locale' => 'fr'), UrlGeneratorInterface::NETWORK_PATH), 'absolute URL because scheme requirement does not match context'
);
$this->assertSame('http://fr.example.com/app.php/Fabien', $this->getGenerator($routes)->generate('test',
array('name' => 'Fabien', 'locale' => 'fr'), UrlGeneratorInterface::ABSOLUTE_URL), 'absolute URL with same scheme because it is requested'
);
}
public function testGenerateRelativePath()
{
$routes = new RouteCollection();
$routes->add('article', new Route('/{author}/{article}/'));
$routes->add('comments', new Route('/{author}/{article}/comments'));
$routes->add('host', new Route('/{article}', array(), array(), array(), '{author}.example.com'));
$routes->add('scheme', new Route('/{author}/blog', array(), array(), array(), '', array('https')));
$routes->add('unrelated', new Route('/about'));
$generator = $this->getGenerator($routes, array('host' => 'example.com', 'pathInfo' => '/fabien/symfony-is-great/'));
$this->assertSame('comments', $generator->generate('comments',
array('author' => 'fabien', 'article' => 'symfony-is-great'), UrlGeneratorInterface::RELATIVE_PATH)
);
$this->assertSame('comments?page=2', $generator->generate('comments',
array('author' => 'fabien', 'article' => 'symfony-is-great', 'page' => 2), UrlGeneratorInterface::RELATIVE_PATH)
);
$this->assertSame('../twig-is-great/', $generator->generate('article',
array('author' => 'fabien', 'article' => 'twig-is-great'), UrlGeneratorInterface::RELATIVE_PATH)
);
$this->assertSame('../../bernhard/forms-are-great/', $generator->generate('article',
array('author' => 'bernhard', 'article' => 'forms-are-great'), UrlGeneratorInterface::RELATIVE_PATH)
);
$this->assertSame('//bernhard.example.com/app.php/forms-are-great', $generator->generate('host',
array('author' => 'bernhard', 'article' => 'forms-are-great'), UrlGeneratorInterface::RELATIVE_PATH)
);
$this->assertSame('https://example.com/app.php/bernhard/blog', $generator->generate('scheme',
array('author' => 'bernhard'), UrlGeneratorInterface::RELATIVE_PATH)
);
$this->assertSame('../../about', $generator->generate('unrelated',
array(), UrlGeneratorInterface::RELATIVE_PATH)
);
}
/**
* @dataProvider provideRelativePaths
*/
public function testGetRelativePath($sourcePath, $targetPath, $expectedPath)
{
$this->assertSame($expectedPath, UrlGenerator::getRelativePath($sourcePath, $targetPath));
}
public function provideRelativePaths()
{
return array(
array(
'/same/dir/',
'/same/dir/',
'',
),
array(
'/same/file',
'/same/file',
'',
),
array(
'/',
'/file',
'file',
),
array(
'/',
'/dir/file',
'dir/file',
),
array(
'/dir/file.html',
'/dir/different-file.html',
'different-file.html',
),
array(
'/same/dir/extra-file',
'/same/dir/',
'./',
),
array(
'/parent/dir/',
'/parent/',
'../',
),
array(
'/parent/dir/extra-file',
'/parent/',
'../',
),
array(
'/a/b/',
'/x/y/z/',
'../../x/y/z/',
),
array(
'/a/b/c/d/e',
'/a/c/d',
'../../../c/d',
),
array(
'/a/b/c//',
'/a/b/c/',
'../',
),
array(
'/a/b/c/',
'/a/b/c//',
'.//',
),
array(
'/root/a/b/c/',
'/root/x/b/c/',
'../../../x/b/c/',
),
array(
'/a/b/c/d/',
'/a',
'../../../../a',
),
array(
'/special-chars/sp%20ce/1€/mäh/e=mc²',
'/special-chars/sp%20ce/1€/<µ>/e=mc²',
'../<µ>/e=mc²',
),
array(
'not-rooted',
'dir/file',
'dir/file',
),
array(
'//dir/',
'',
'../../',
),
array(
'/dir/',
'/dir/file:with-colon',
'./file:with-colon',
),
array(
'/dir/',
'/dir/subdir/file:with-colon',
'subdir/file:with-colon',
),
array(
'/dir/',
'/dir/:subdir/',
'./:subdir/',
),
);
}
public function testFragmentsCanBeAppendedToUrls()
{
$routes = $this->getRoutes('test', new Route('/testing'));
$url = $this->getGenerator($routes)->generate('test', array('_fragment' => 'frag ment'), UrlGeneratorInterface::ABSOLUTE_PATH);
$this->assertEquals('/app.php/testing#frag%20ment', $url);
$url = $this->getGenerator($routes)->generate('test', array('_fragment' => '0'), UrlGeneratorInterface::ABSOLUTE_PATH);
$this->assertEquals('/app.php/testing#0', $url);
}
public function testFragmentsDoNotEscapeValidCharacters()
{
$routes = $this->getRoutes('test', new Route('/testing'));
$url = $this->getGenerator($routes)->generate('test', array('_fragment' => '?/'), UrlGeneratorInterface::ABSOLUTE_PATH);
$this->assertEquals('/app.php/testing#?/', $url);
}
public function testFragmentsCanBeDefinedAsDefaults()
{
$routes = $this->getRoutes('test', new Route('/testing', array('_fragment' => 'fragment')));
$url = $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_PATH);
$this->assertEquals('/app.php/testing#fragment', $url);
}
protected function getGenerator(RouteCollection $routes, array $parameters = array(), $logger = null)
{
$context = new RequestContext('/app.php');
foreach ($parameters as $key => $value) {
$method = 'set'.$key;
$context->$method($value);
}
return new UrlGenerator($routes, $context, $logger);
}
protected function getRoutes($name, Route $route)
{
$routes = new RouteCollection();
$routes->add($name, $route);
return $routes;
}
}

View file

@ -1,33 +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\Loader;
use PHPUnit\Framework\TestCase;
abstract class AbstractAnnotationLoaderTest extends TestCase
{
public function getReader()
{
return $this->getMockBuilder('Doctrine\Common\Annotations\Reader')
->disableOriginalConstructor()
->getMock()
;
}
public function getClassLoader($reader)
{
return $this->getMockBuilder('Symfony\Component\Routing\Loader\AnnotationClassLoader')
->setConstructorArgs(array($reader))
->getMockForAbstractClass()
;
}
}

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