Laravel 5.6 updates

Travis config update

Removed HHVM script as Laravel no longer support HHVM after releasing 5.3
This commit is contained in:
Manish Verma
2018-08-06 20:08:55 +05:30
parent 126fbb0255
commit 1ac0f42a58
2464 changed files with 65239 additions and 46734 deletions

View File

@@ -22,6 +22,7 @@ namespace Symfony\Component\Routing\Annotation;
class Route
{
private $path;
private $localizedPaths = array();
private $name;
private $requirements = array();
private $options = array();
@@ -38,11 +39,20 @@ 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']);
}
foreach ($data as $key => $value) {
$method = 'set'.str_replace('_', '', $key);
if (!method_exists($this, $method)) {
@@ -62,6 +72,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;

View File

@@ -1,6 +1,12 @@
CHANGELOG
=========
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
-----

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(), array $hostVariables = array(), array $variables = array())
{
$this->staticPrefix = (string) $staticPrefix;
$this->staticPrefix = $staticPrefix;
$this->regex = $regex;
$this->tokens = $tokens;
$this->pathVariables = $pathVariables;
@@ -71,11 +71,7 @@ class CompiledRoute implements \Serializable
*/
public function unserialize($serialized)
{
if (\PHP_VERSION_ID >= 70000) {
$data = unserialize($serialized, array('allowed_classes' => false));
} else {
$data = unserialize($serialized);
}
$data = unserialize($serialized, array('allowed_classes' => false));
$this->variables = $data['vars'];
$this->staticPrefix = $data['path_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;

View File

@@ -22,7 +22,7 @@ class MethodNotAllowedException extends \RuntimeException implements ExceptionIn
{
protected $allowedMethods = array();
public function __construct(array $allowedMethods, $message = null, $code = 0, \Exception $previous = null)
public function __construct(array $allowedMethods, string $message = null, int $code = 0, \Exception $previous = null)
{
$this->allowedMethods = array_map('strtoupper', $allowedMethods);

View File

@@ -11,6 +11,8 @@
namespace Symfony\Component\Routing\Generator\Dumper;
use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper;
/**
* PhpGeneratorDumper creates a PHP class able to generate URLs for a given set of routes.
*
@@ -52,11 +54,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()};
}
@@ -88,7 +92,7 @@ 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, PhpMatcherDumper::export($properties));
}
$routes .= ' )';
@@ -105,7 +109,14 @@ EOF;
return <<<'EOF'
public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH)
{
if (!isset(self::$declaredRoutes[$name])) {
$locale = $parameters['_locale']
?? $this->context->getParameter('_locale')
?: $this->defaultLocale;
if (null !== $locale && (self::$declaredRoutes[$name.'.'.$locale][1]['_canonical_route'] ?? null) === $name) {
unset($parameters['_locale']);
$name .= '.'.$locale;
} elseif (!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

@@ -37,6 +37,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.
*
@@ -65,11 +67,12 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
'%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;
}
/**
@@ -109,7 +112,13 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
*/
public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH)
{
if (null === $route = $this->routes->get($name)) {
$locale = $parameters['_locale']
?? $this->context->getParameter('_locale')
?: $this->defaultLocale;
if (null !== $locale && null !== ($route = $this->routes->get($name.'.'.$locale)) && $route->getDefault('_canonical_route') === $name) {
unset($parameters['_locale']);
} elseif (null === $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));
}

View File

@@ -124,6 +124,7 @@ abstract class AnnotationClassLoader implements LoaderInterface
if ($annot instanceof $this->routeAnnotationClass) {
$globals['path'] = '';
$globals['name'] = '';
$globals['localized_paths'] = array();
$this->addRoute($collection, $annot, $globals, $class, $class->getMethod('__invoke'));
}
@@ -142,11 +143,6 @@ 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 = array_replace($globals['requirements'], $annot->getRequirements());
$options = array_replace($globals['options'], $annot->getOptions());
$schemes = array_merge($globals['schemes'], $annot->getSchemes());
@@ -162,11 +158,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 = array();
$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 (false !== strpos($path, sprintf('{%s}', $param->name))) {
$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);
}
}
}
/**
@@ -213,7 +255,8 @@ abstract class AnnotationClassLoader implements LoaderInterface
protected function getGlobals(\ReflectionClass $class)
{
$globals = array(
'path' => '',
'path' => null,
'localized_paths' => array(),
'requirements' => array(),
'options' => array(),
'defaults' => array(),
@@ -233,6 +276,8 @@ abstract class AnnotationClassLoader implements LoaderInterface
$globals['path'] = $annot->getPath();
}
$globals['localized_paths'] = $annot->getLocalizedPaths();
if (null !== $annot->getRequirements()) {
$globals['requirements'] = $annot->getRequirements();
}

View File

@@ -59,10 +59,9 @@ class AnnotationFileLoader extends FileLoader
$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();
}
// PHP 7 memory manager will not release after token_get_all(), see https://bugs.php.net/70098
gc_mem_caches();
return $collection;
}

View File

@@ -24,37 +24,27 @@ class CollectionConfigurator
private $parent;
private $parentConfigurator;
private $parentPrefixes;
public function __construct(RouteCollection $parent, $name, self $parentConfigurator = null)
public function __construct(RouteCollection $parent, string $name, self $parentConfigurator = null, array $parentPrefixes = null)
{
$this->parent = $parent;
$this->name = $name;
$this->collection = new RouteCollection();
$this->route = new Route('');
$this->parentConfigurator = $parentConfigurator; // for GC control
$this->parentPrefixes = $parentPrefixes;
}
public function __destruct()
{
$this->collection->addPrefix(rtrim($this->route->getPath(), '/'));
if (null === $this->prefixes) {
$this->collection->addPrefix($this->route->getPath());
}
$this->parent->addCollection($this->collection);
}
/**
* Adds a route.
*
* @param string $name
* @param string $path
*
* @return RouteConfigurator
*/
final public function add($name, $path)
{
$this->collection->add($this->name.$name, $route = clone $this->route);
return new RouteConfigurator($this->collection, $route->setPath($path), $this->name, $this);
}
/**
* Creates a sub-collection.
*
@@ -62,20 +52,44 @@ class CollectionConfigurator
*/
final public function collection($name = '')
{
return new self($this->collection, $this->name.$name, $this);
return new self($this->collection, $this->name.$name, $this, $this->prefixes);
}
/**
* Sets the prefix to add to the path of all child routes.
*
* @param string $prefix
* @param string|array $prefix the prefix, or the localized prefixes
*
* @return $this
*/
final public function prefix($prefix)
{
$this->route->setPath($prefix);
if (\is_array($prefix)) {
if (null === $this->parentPrefixes) {
// no-op
} elseif ($missing = array_diff_key($this->parentPrefixes, $prefix)) {
throw new \LogicException(sprintf('Collection "%s" is missing prefixes for locale(s) "%s".', $this->name, implode('", "', array_keys($missing))));
} else {
foreach ($prefix as $locale => $localePrefix) {
if (!isset($this->parentPrefixes[$locale])) {
throw new \LogicException(sprintf('Collection "%s" with locale "%s" is missing a corresponding prefix in its parent collection.', $this->name, $locale));
}
$prefix[$locale] = $this->parentPrefixes[$locale].$localePrefix;
}
}
$this->prefixes = $prefix;
$this->route->setPath('/');
} else {
$this->prefixes = null;
$this->route->setPath($prefix);
}
return $this;
}
private function createRoute($path): Route
{
return (clone $this->route)->setPath($path);
}
}

View File

@@ -11,6 +11,7 @@
namespace Symfony\Component\Routing\Loader\Configurator;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
@@ -36,13 +37,56 @@ class ImportConfigurator
/**
* Sets the prefix to add to the path of all child routes.
*
* @param string $prefix
* @param string|array $prefix the prefix, or the localized prefixes
*
* @return $this
*/
final public function prefix($prefix)
final public function prefix($prefix, bool $trailingSlashOnRoot = true)
{
$this->route->addPrefix($prefix);
if (!\is_array($prefix)) {
$this->route->addPrefix($prefix);
if (!$trailingSlashOnRoot) {
$rootPath = (new Route(trim(trim($prefix), '/').'/'))->getPath();
foreach ($this->route->all() as $route) {
if ($route->getPath() === $rootPath) {
$route->setPath(rtrim($rootPath, '/'));
}
}
}
} else {
foreach ($prefix as $locale => $localePrefix) {
$prefix[$locale] = trim(trim($localePrefix), '/');
}
foreach ($this->route->all() as $name => $route) {
if (null === $locale = $route->getDefault('_locale')) {
$this->route->remove($name);
foreach ($prefix as $locale => $localePrefix) {
$localizedRoute = clone $route;
$localizedRoute->setDefault('_locale', $locale);
$localizedRoute->setDefault('_canonical_route', $name);
$localizedRoute->setPath($localePrefix.(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath()));
$this->route->add($name.'.'.$locale, $localizedRoute);
}
} elseif (!isset($prefix[$locale])) {
throw new \InvalidArgumentException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix in its parent collection.', $name, $locale));
} else {
$route->setPath($prefix[$locale].(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath()));
$this->route->add($name, $route);
}
}
}
return $this;
}
/**
* Sets the prefix to add to the name of all child routes.
*
* @return $this
*/
final public function namePrefix(string $namePrefix)
{
$this->route->addNamePrefix($namePrefix);
return $this;
}

View File

@@ -11,7 +11,6 @@
namespace Symfony\Component\Routing\Loader\Configurator;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
@@ -24,11 +23,12 @@ class RouteConfigurator
private $parentConfigurator;
public function __construct(RouteCollection $collection, Route $route, $name = '', CollectionConfigurator $parentConfigurator = null)
public function __construct(RouteCollection $collection, $route, string $name = '', CollectionConfigurator $parentConfigurator = null, array $prefixes = null)
{
$this->collection = $collection;
$this->route = $route;
$this->name = $name;
$this->parentConfigurator = $parentConfigurator; // for GC control
$this->prefixes = $prefixes;
}
}

View File

@@ -25,7 +25,7 @@ class RoutingConfigurator
private $path;
private $file;
public function __construct(RouteCollection $collection, PhpFileLoader $loader, $path, $file)
public function __construct(RouteCollection $collection, PhpFileLoader $loader, string $path, string $file)
{
$this->collection = $collection;
$this->loader = $loader;

View File

@@ -11,6 +11,7 @@
namespace Symfony\Component\Routing\Loader\Configurator\Traits;
use Symfony\Component\Routing\Loader\Configurator\CollectionConfigurator;
use Symfony\Component\Routing\Loader\Configurator\RouteConfigurator;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
@@ -24,32 +25,66 @@ trait AddTrait
private $name = '';
private $prefixes;
/**
* Adds a route.
*
* @param string $name
* @param string $path
*
* @return RouteConfigurator
* @param string|array $path the path, or the localized paths of the route
*/
final public function add($name, $path)
final public function add(string $name, $path): RouteConfigurator
{
$parentConfigurator = $this instanceof RouteConfigurator ? $this->parentConfigurator : null;
$this->collection->add($this->name.$name, $route = new Route($path));
$paths = array();
$parentConfigurator = $this instanceof CollectionConfigurator ? $this : ($this instanceof RouteConfigurator ? $this->parentConfigurator : null);
return new RouteConfigurator($this->collection, $route, '', $parentConfigurator);
if (\is_array($path)) {
if (null === $this->prefixes) {
$paths = $path;
} elseif ($missing = array_diff_key($this->prefixes, $path)) {
throw new \LogicException(sprintf('Route "%s" is missing routes for locale(s) "%s".', $name, implode('", "', array_keys($missing))));
} else {
foreach ($path as $locale => $localePath) {
if (!isset($this->prefixes[$locale])) {
throw new \LogicException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix in its parent collection.', $name, $locale));
}
$paths[$locale] = $this->prefixes[$locale].$localePath;
}
}
} elseif (null !== $this->prefixes) {
foreach ($this->prefixes as $locale => $prefix) {
$paths[$locale] = $prefix.$path;
}
} else {
$this->collection->add($this->name.$name, $route = $this->createRoute($path));
return new RouteConfigurator($this->collection, $route, $this->name, $parentConfigurator, $this->prefixes);
}
$routes = new RouteCollection();
foreach ($paths as $locale => $path) {
$routes->add($name.'.'.$locale, $route = $this->createRoute($path));
$this->collection->add($this->name.$name.'.'.$locale, $route);
$route->setDefault('_locale', $locale);
$route->setDefault('_canonical_route', $this->name.$name);
}
return new RouteConfigurator($this->collection, $routes, $this->name, $parentConfigurator, $this->prefixes);
}
/**
* Adds a route.
*
* @param string $name
* @param string $path
*
* @return RouteConfigurator
* @param string|array $path the path, or the localized paths of the route
*/
final public function __invoke($name, $path)
final public function __invoke(string $name, $path): RouteConfigurator
{
return $this->add($name, $path);
}
private function createRoute($path): Route
{
return new Route($path);
}
}

View File

@@ -60,11 +60,9 @@ trait RouteTrait
/**
* Sets the condition.
*
* @param string $condition
*
* @return $this
*/
final public function condition($condition)
final public function condition(string $condition)
{
$this->route->setCondition($condition);
@@ -74,11 +72,9 @@ trait RouteTrait
/**
* Sets the pattern for the host.
*
* @param string $pattern
*
* @return $this
*/
final public function host($pattern)
final public function host(string $pattern)
{
$this->route->setHost($pattern);

View File

@@ -44,9 +44,14 @@ abstract class ObjectRouteLoader extends Loader
*/
public function load($resource, $type = null)
{
$parts = explode(':', $resource);
if (1 === substr_count($resource, ':')) {
$resource = str_replace(':', '::', $resource);
@trigger_error(sprintf('Referencing service route loaders with a single colon is deprecated since Symfony 4.1. Use %s instead.', $resource), E_USER_DEPRECATED);
}
$parts = explode('::', $resource);
if (2 != \count($parts)) {
throw new \InvalidArgumentException(sprintf('Invalid resource "%s" passed to the "service" route loader: use the format "service_name:methodName"', $resource));
throw new \InvalidArgumentException(sprintf('Invalid resource "%s" passed to the "service" route loader: use the format "service::method"', $resource));
}
$serviceString = $parts[0];
@@ -58,7 +63,7 @@ abstract class ObjectRouteLoader extends Loader
throw new \LogicException(sprintf('%s:getServiceObject() must return an object: %s returned', \get_class($this), \gettype($loaderObject)));
}
if (!method_exists($loaderObject, $method)) {
if (!\is_callable(array($loaderObject, $method))) {
throw new \BadMethodCallException(sprintf('Method "%s" not found on "%s" when importing routing resource "%s"', $method, \get_class($loaderObject), $resource));
}

View File

@@ -46,7 +46,7 @@ class PhpFileLoader extends FileLoader
$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);
} else {

View File

@@ -107,17 +107,34 @@ class XmlFileLoader extends FileLoader
*/
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);
}
}
}
/**
@@ -141,8 +158,13 @@ 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));
}
$this->setCurrentDir(\dirname($path));
@@ -154,7 +176,39 @@ class XmlFileLoader extends FileLoader
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 +225,10 @@ class XmlFileLoader extends FileLoader
$subCollection->addRequirements($requirements);
$subCollection->addOptions($options);
if ($namePrefix = $node->getAttribute('name-prefix')) {
$subCollection->addNamePrefix($namePrefix);
}
$collection->addCollection($subCollection);
}
}
@@ -207,6 +265,8 @@ class XmlFileLoader extends FileLoader
$requirements = array();
$options = array();
$condition = null;
$prefixes = array();
$paths = array();
foreach ($node->getElementsByTagNameNS(self::NAMESPACE_URI, '*') as $n) {
if ($node !== $n->parentNode) {
@@ -214,6 +274,12 @@ class XmlFileLoader extends FileLoader
}
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;
@@ -246,7 +312,7 @@ class XmlFileLoader extends FileLoader
$defaults['_controller'] = $controller;
}
return array($defaults, $requirements, $options, $condition);
return array($defaults, $requirements, $options, $condition, $paths, $prefixes);
}
/**

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.
@@ -27,7 +28,7 @@ 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',
'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', 'controller', 'name_prefix', 'trailing_slash_on_root',
);
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();
@@ -127,9 +120,20 @@ class YamlFileLoader extends FileLoader
$defaults['_controller'] = $config['controller'];
}
$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);
}
}
/**
@@ -151,6 +155,7 @@ class YamlFileLoader extends FileLoader
$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;
if (isset($config['controller'])) {
$defaults['_controller'] = $config['controller'];
@@ -166,7 +171,39 @@ class YamlFileLoader extends FileLoader
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 +220,10 @@ class YamlFileLoader extends FileLoader
$subCollection->addRequirements($requirements);
$subCollection->addOptions($options);
if (isset($config['name_prefix'])) {
$subCollection->addNamePrefix($config['name_prefix']);
}
$collection->addCollection($subCollection);
}
}

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,10 +42,12 @@
</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" />
@@ -45,15 +55,19 @@
</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:sequence>
<xsd:attribute name="resource" type="xsd:string" use="required" />
<xsd:attribute name="type" 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="trailing-slash-on-root" 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

@@ -13,6 +13,7 @@ namespace Symfony\Component\Routing\Matcher\Dumper;
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
@@ -22,10 +23,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>
*/
class PhpMatcherDumper extends MatcherDumper
{
private $expressionLanguage;
private $signalingException;
/**
* @var ExpressionFunctionProviderInterface[]
@@ -53,7 +56,7 @@ class PhpMatcherDumper extends MatcherDumper
// 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']);
$supportsRedirections = isset($interfaces[RedirectableUrlMatcherInterface::class]);
return <<<EOF
<?php
@@ -86,70 +89,107 @@ EOF;
/**
* 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)
private function generateMatchMethod(bool $supportsRedirections): string
{
$code = rtrim($this->compileRoutes($this->getRoutes(), $supportsRedirections), "\n");
// Group hosts by same-suffix, re-order when possible
$matchHost = false;
$routes = new StaticPrefixCollection();
foreach ($this->getRoutes()->all() as $name => $route) {
if ($host = $route->getHost()) {
$matchHost = true;
$host = '/'.strtr(strrev($host), '}.{', '(/)');
}
return <<<EOF
public function match(\$rawPathinfo)
$routes->addRoute($host ?: '/(.*)', array($name, $route));
}
$routes = $matchHost ? $routes->populateCollection(new RouteCollection()) : $this->getRoutes();
$code = rtrim($this->compileRoutes($routes, $matchHost), "\n");
$fetchHost = $matchHost ? " \$host = strtolower(\$context->getHost());\n" : '';
$code = <<<EOF
{
\$allow = array();
\$allow = \$allowSchemes = array();
\$pathinfo = rawurldecode(\$rawPathinfo);
\$trimmedPathinfo = rtrim(\$pathinfo, '/');
\$context = \$this->context;
\$request = \$this->request ?: \$this->createRequest(\$pathinfo);
\$requestMethod = \$canonicalMethod = \$context->getMethod();
{$fetchHost}
if ('HEAD' === \$requestMethod) {
\$canonicalMethod = 'GET';
}
$code
throw 0 < count(\$allow) ? new MethodNotAllowedException(array_unique(\$allow)) : new ResourceNotFoundException();
}
EOF;
if ($supportsRedirections) {
return <<<'EOF'
public function match($pathinfo)
{
$allow = $allowSchemes = array();
if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) {
return $ret;
}
if ($allow) {
throw new MethodNotAllowedException(array_keys($allow));
}
if (!in_array($this->context->getMethod(), array('HEAD', 'GET'), true)) {
// no-op
} elseif ($allowSchemes) {
redirect_scheme:
$scheme = $this->context->getScheme();
$this->context->setScheme(key($allowSchemes));
try {
if ($ret = $this->doMatch($pathinfo)) {
return $this->redirect($pathinfo, $ret['_route'], $this->context->getScheme()) + $ret;
}
} finally {
$this->context->setScheme($scheme);
}
} elseif ('/' !== $pathinfo) {
$pathinfo = '/' !== $pathinfo[-1] ? $pathinfo.'/' : substr($pathinfo, 0, -1);
if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) {
return $this->redirect($pathinfo, $ret['_route']) + $ret;
}
if ($allowSchemes) {
goto redirect_scheme;
}
}
throw new ResourceNotFoundException();
}
private function doMatch(string $rawPathinfo, array &$allow = array(), array &$allowSchemes = array()): ?array
EOF
.$code."\n return null;\n }";
}
return " public function match(\$rawPathinfo)\n".$code."\n throw \$allow ? new MethodNotAllowedException(array_keys(\$allow)) : new ResourceNotFoundException();\n }";
}
/**
* 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)
private function compileRoutes(RouteCollection $routes, bool $matchHost): string
{
$fetchedHost = false;
$groups = $this->groupRoutesByHostRegex($routes);
$code = '';
list($staticRoutes, $dynamicRoutes) = $this->groupStaticRoutes($routes);
foreach ($groups as $collection) {
if (null !== $regex = $collection->getAttribute('host_regex')) {
if (!$fetchedHost) {
$code .= " \$host = \$context->getHost();\n\n";
$fetchedHost = true;
$code = $this->compileStaticRoutes($staticRoutes, $matchHost);
$chunkLimit = \count($dynamicRoutes);
while (true) {
try {
$this->signalingException = new \RuntimeException('preg_match(): Compilation failed: regular expression is too large');
$code .= $this->compileDynamicRoutes($dynamicRoutes, $matchHost, $chunkLimit);
break;
} catch (\Exception $e) {
if (1 < $chunkLimit && $this->signalingException === $e) {
$chunkLimit = 1 + ($chunkLimit >> 1);
continue;
}
$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;
throw $e;
}
}
@@ -161,55 +201,383 @@ EOF;
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
* Splits static routes from dynamic routes, so that they can be matched first, using a simple switch.
*/
private function compileStaticPrefixRoutes(StaticPrefixCollection $collection, $supportsRedirections, $ifOrElseIf = 'if')
private function groupStaticRoutes(RouteCollection $collection): array
{
$code = '';
$prefix = $collection->getPrefix();
$staticRoutes = $dynamicRegex = array();
$dynamicRoutes = new RouteCollection();
if (!empty($prefix) && '/' !== $prefix) {
$code .= sprintf(" %s (0 === strpos(\$pathinfo, %s)) {\n", $ifOrElseIf, var_export($prefix, true));
}
foreach ($collection->all() as $name => $route) {
$compiledRoute = $route->compile();
$hostRegex = $compiledRoute->getHostRegex();
$regex = $compiledRoute->getRegex();
if (!$compiledRoute->getPathVariables()) {
$host = !$compiledRoute->getHostVariables() ? $route->getHost() : '';
$url = $route->getPath();
foreach ($dynamicRegex as list($hostRx, $rx)) {
if (preg_match($rx, $url) && (!$host || !$hostRx || preg_match($hostRx, $host))) {
$dynamicRegex[] = array($hostRegex, $regex);
$dynamicRoutes->add($name, $route);
continue 2;
}
}
$ifOrElseIf = 'if';
foreach ($collection->getItems() as $route) {
if ($route instanceof StaticPrefixCollection) {
$code .= $this->compileStaticPrefixRoutes($route, $supportsRedirections, $ifOrElseIf);
$ifOrElseIf = 'elseif';
$staticRoutes[$url][$name] = $route;
} else {
$code .= $this->compileRoute($route[1]->getRoute(), $route[1]->getName(), $supportsRedirections, $prefix)."\n";
$ifOrElseIf = 'if';
$dynamicRegex[] = array($hostRegex, $regex);
$dynamicRoutes->add($name, $route);
}
}
if (!empty($prefix) && '/' !== $prefix) {
$code .= " }\n\n";
// apply extra indention at each line (except empty ones)
$code = preg_replace('/^.{2,}$/m', ' $0', $code);
return array($staticRoutes, $dynamicRoutes);
}
/**
* Compiles static routes in a switch statement.
*
* Condition-less paths are put in a static array in the switch's default, with generic matching logic.
* Paths that can match two or more routes, or have user-specified conditions are put in separate switch's cases.
*
* @throws \LogicException
*/
private function compileStaticRoutes(array $staticRoutes, bool $matchHost): string
{
if (!$staticRoutes) {
return '';
}
$code = $default = '';
foreach ($staticRoutes as $url => $routes) {
if (1 === \count($routes)) {
foreach ($routes as $name => $route) {
}
if (!$route->getCondition()) {
$defaults = $route->getDefaults();
if (isset($defaults['_canonical_route'])) {
$name = $defaults['_canonical_route'];
unset($defaults['_canonical_route']);
}
$default .= sprintf(
"%s => array(%s, %s, %s, %s),\n",
self::export($url),
self::export(array('_route' => $name) + $defaults),
self::export(!$route->compile()->getHostVariables() ? $route->getHost() : $route->compile()->getHostRegex() ?: null),
self::export(array_flip($route->getMethods()) ?: null),
self::export(array_flip($route->getSchemes()) ?: null)
);
continue;
}
}
$code .= sprintf(" case %s:\n", self::export($url));
foreach ($routes as $name => $route) {
$code .= $this->compileRoute($route, $name, true);
}
$code .= " break;\n";
}
if ($default) {
$code .= <<<EOF
default:
\$routes = array(
{$this->indent($default, 4)} );
if (!isset(\$routes[\$pathinfo])) {
break;
}
list(\$ret, \$requiredHost, \$requiredMethods, \$requiredSchemes) = \$routes[\$pathinfo];
{$this->compileSwitchDefault(false, $matchHost)}
EOF;
}
return sprintf(" switch (\$pathinfo) {\n%s }\n\n", $this->indent($code));
}
/**
* Compiles a regular expression followed by a switch statement to match dynamic routes.
*
* The regular expression matches both the host and the pathinfo at the same time. For stellar performance,
* it is built as a tree of patterns, with re-ordering logic to group same-prefix routes together when possible.
*
* Patterns are named so that we know which one matched (https://pcre.org/current/doc/html/pcre2syntax.html#SEC23).
* This name is used to "switch" to the additional logic required to match the final route.
*
* Condition-less paths are put in a static array in the switch's default, with generic matching logic.
* Paths that can match two or more routes, or have user-specified conditions are put in separate switch's cases.
*
* Last but not least:
* - Because it is not possibe to mix unicode/non-unicode patterns in a single regexp, several of them can be generated.
* - The same regexp can be used several times when the logic in the switch rejects the match. When this happens, the
* matching-but-failing subpattern is blacklisted by replacing its name by "(*F)", which forces a failure-to-match.
* To ease this backlisting operation, the name of subpatterns is also the string offset where the replacement should occur.
*/
private function compileDynamicRoutes(RouteCollection $collection, bool $matchHost, int $chunkLimit): string
{
if (!$collection->all()) {
return '';
}
$code = '';
$state = (object) array(
'regex' => '',
'switch' => '',
'default' => '',
'mark' => 0,
'markTail' => 0,
'hostVars' => array(),
'vars' => array(),
);
$state->getVars = static function ($m) use ($state) {
if ('_route' === $m[1]) {
return '?:';
}
$state->vars[] = $m[1];
return '';
};
$chunkSize = 0;
$prev = null;
$perModifiers = array();
foreach ($collection->all() as $name => $route) {
preg_match('#[a-zA-Z]*$#', $route->compile()->getRegex(), $rx);
if ($chunkLimit < ++$chunkSize || $prev !== $rx[0] && $route->compile()->getPathVariables()) {
$chunkSize = 1;
$routes = new RouteCollection();
$perModifiers[] = array($rx[0], $routes);
$prev = $rx[0];
}
$routes->add($name, $route);
}
foreach ($perModifiers as list($modifiers, $routes)) {
$prev = false;
$perHost = array();
foreach ($routes->all() as $name => $route) {
$regex = $route->compile()->getHostRegex();
if ($prev !== $regex) {
$routes = new RouteCollection();
$perHost[] = array($regex, $routes);
$prev = $regex;
}
$routes->add($name, $route);
}
$prev = false;
$rx = '{^(?';
$code .= "\n {$state->mark} => ".self::export($rx);
$state->mark += \strlen($rx);
$state->regex = $rx;
foreach ($perHost as list($hostRegex, $routes)) {
if ($matchHost) {
if ($hostRegex) {
preg_match('#^.\^(.*)\$.[a-zA-Z]*$#', $hostRegex, $rx);
$state->vars = array();
$hostRegex = '(?i:'.preg_replace_callback('#\?P<([^>]++)>#', $state->getVars, $rx[1]).')\.';
$state->hostVars = $state->vars;
} else {
$hostRegex = '(?:(?:[^./]*+\.)++)';
$state->hostVars = array();
}
$state->mark += \strlen($rx = ($prev ? ')' : '')."|{$hostRegex}(?");
$code .= "\n .".self::export($rx);
$state->regex .= $rx;
$prev = true;
}
$tree = new StaticPrefixCollection();
foreach ($routes->all() as $name => $route) {
preg_match('#^.\^(.*)\$.[a-zA-Z]*$#', $route->compile()->getRegex(), $rx);
$state->vars = array();
$regex = preg_replace_callback('#\?P<([^>]++)>#', $state->getVars, $rx[1]);
$tree->addRoute($regex, array($name, $regex, $state->vars, $route));
}
$code .= $this->compileStaticPrefixCollection($tree, $state);
}
if ($matchHost) {
$code .= "\n .')'";
$state->regex .= ')';
}
$rx = ")$}{$modifiers}";
$code .= "\n .'{$rx}',";
$state->regex .= $rx;
$state->markTail = 0;
// if the regex is too large, throw a signaling exception to recompute with smaller chunk size
set_error_handler(function ($type, $message) { throw 0 === strpos($message, $this->signalingException->getMessage()) ? $this->signalingException : new \ErrorException($message); });
try {
preg_match($state->regex, '');
} finally {
restore_error_handler();
}
}
if ($state->default) {
$state->switch .= <<<EOF
default:
\$routes = array(
{$this->indent($state->default, 4)} );
list(\$ret, \$vars, \$requiredMethods, \$requiredSchemes) = \$routes[\$m];
{$this->compileSwitchDefault(true, $matchHost)}
EOF;
}
$matchedPathinfo = $matchHost ? '$host.\'.\'.$pathinfo' : '$pathinfo';
unset($state->getVars);
return <<<EOF
\$matchedPathinfo = {$matchedPathinfo};
\$regexList = array({$code}
);
foreach (\$regexList as \$offset => \$regex) {
while (preg_match(\$regex, \$matchedPathinfo, \$matches)) {
switch (\$m = (int) \$matches['MARK']) {
{$this->indent($state->switch, 3)} }
if ({$state->mark} === \$m) {
break;
}
\$regex = substr_replace(\$regex, 'F', \$m - \$offset, 1 + strlen(\$m));
\$offset += strlen(\$m);
}
}
EOF;
}
/**
* Compiles a regexp tree of subpatterns that matches nested same-prefix routes.
*
* @param \stdClass $state A simple state object that keeps track of the progress of the compilation,
* and gathers the generated switch's "case" and "default" statements
*/
private function compileStaticPrefixCollection(StaticPrefixCollection $tree, \stdClass $state, int $prefixLen = 0): string
{
$code = '';
$prevRegex = null;
$routes = $tree->getRoutes();
foreach ($routes as $i => $route) {
if ($route instanceof StaticPrefixCollection) {
$prevRegex = null;
$prefix = substr($route->getPrefix(), $prefixLen);
$state->mark += \strlen($rx = "|{$prefix}(?");
$code .= "\n .".self::export($rx);
$state->regex .= $rx;
$code .= $this->indent($this->compileStaticPrefixCollection($route, $state, $prefixLen + \strlen($prefix)));
$code .= "\n .')'";
$state->regex .= ')';
++$state->markTail;
continue;
}
list($name, $regex, $vars, $route) = $route;
$compiledRoute = $route->compile();
if ($compiledRoute->getRegex() === $prevRegex) {
$state->switch = substr_replace($state->switch, $this->compileRoute($route, $name, false)."\n", -19, 0);
continue;
}
$state->mark += 3 + $state->markTail + \strlen($regex) - $prefixLen;
$state->markTail = 2 + \strlen($state->mark);
$rx = sprintf('|%s(*:%s)', substr($regex, $prefixLen), $state->mark);
$code .= "\n .".self::export($rx);
$state->regex .= $rx;
$vars = array_merge($state->hostVars, $vars);
if (!$route->getCondition() && (!\is_array($next = $routes[1 + $i] ?? null) || $regex !== $next[1])) {
$prevRegex = null;
$defaults = $route->getDefaults();
if (isset($defaults['_canonical_route'])) {
$name = $defaults['_canonical_route'];
unset($defaults['_canonical_route']);
}
$state->default .= sprintf(
"%s => array(%s, %s, %s, %s),\n",
$state->mark,
self::export(array('_route' => $name) + $defaults),
self::export($vars),
self::export(array_flip($route->getMethods()) ?: null),
self::export(array_flip($route->getSchemes()) ?: null)
);
} else {
$prevRegex = $compiledRoute->getRegex();
$combine = ' $matches = array(';
foreach ($vars as $j => $m) {
$combine .= sprintf('%s => $matches[%d] ?? null, ', self::export($m), 1 + $j);
}
$combine = $vars ? substr_replace($combine, ");\n\n", -2) : '';
$state->switch .= <<<EOF
case {$state->mark}:
{$combine}{$this->compileRoute($route, $name, false)}
break;
EOF;
}
}
return $code;
}
/**
* A simple helper to compiles the switch's "default" for both static and dynamic routes.
*/
private function compileSwitchDefault(bool $hasVars, bool $matchHost): string
{
if ($hasVars) {
$code = <<<EOF
foreach (\$vars as \$i => \$v) {
if (isset(\$matches[1 + \$i])) {
\$ret[\$v] = \$matches[1 + \$i];
}
}
EOF;
} elseif ($matchHost) {
$code = <<<EOF
if (\$requiredHost) {
if ('#' !== \$requiredHost[0] ? \$requiredHost !== \$host : !preg_match(\$requiredHost, \$host, \$hostMatches)) {
break;
}
if ('#' === \$requiredHost[0] && \$hostMatches) {
\$hostMatches['_route'] = \$ret['_route'];
\$ret = \$this->mergeDefaults(\$hostMatches, \$ret);
}
}
EOF;
} else {
$code = '';
}
$code .= <<<EOF
\$hasRequiredScheme = !\$requiredSchemes || isset(\$requiredSchemes[\$context->getScheme()]);
if (\$requiredMethods && !isset(\$requiredMethods[\$canonicalMethod]) && !isset(\$requiredMethods[\$requestMethod])) {
if (\$hasRequiredScheme) {
\$allow += \$requiredMethods;
}
break;
}
if (!\$hasRequiredScheme) {
\$allowSchemes += \$requiredSchemes;
break;
}
return \$ret;
EOF;
return $code;
}
@@ -217,132 +585,97 @@ EOF;
/**
* 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)
private function compileRoute(Route $route, string $name, bool $checkHost): string
{
$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;
}
$matches = (bool) $compiledRoute->getPathVariables();
$hostMatches = (bool) $compiledRoute->getHostVariables();
$methods = array_flip($route->getMethods());
if ($route->getCondition()) {
$conditions[] = $this->getExpressionLanguage()->compile($route->getCondition(), array('context', 'request'));
$expression = $this->getExpressionLanguage()->compile($route->getCondition(), array('context', 'request'));
if (false !== strpos($expression, '$request')) {
$conditions[] = '($request = $request ?? $this->request ?: $this->createRequest($pathinfo))';
}
$conditions[] = $expression;
}
if (!$checkHost || !$compiledRoute->getHostRegex()) {
// no-op
} elseif ($hostMatches) {
$conditions[] = sprintf('preg_match(%s, $host, $hostMatches)', self::export($compiledRoute->getHostRegex()));
} else {
$conditions[] = sprintf('%s === $host', self::export($route->getHost()));
}
$conditions = implode(' && ', $conditions);
$code .= <<<EOF
if ($conditions) {
$code .= <<<EOF
// $name
if ($conditions) {
EOF;
} else {
$code .= " // {$name}\n";
}
$gotoname = 'not_'.preg_replace('/[^A-Za-z0-9_]/', '', $name);
// the offset where the return value is appended below, with indendation
$retOffset = 12 + \strlen($code);
$defaults = $route->getDefaults();
if (isset($defaults['_canonical_route'])) {
$name = $defaults['_canonical_route'];
unset($defaults['_canonical_route']);
}
// optimize parameters array
if ($matches || $hostMatches) {
$vars = array();
if ($hostMatches) {
$vars[] = '$hostMatches';
}
if ($matches) {
$vars = array("array('_route' => '$name')");
if ($matches || ($hostMatches && !$checkHost)) {
$vars[] = '$matches';
}
$vars[] = "array('_route' => '$name')";
if ($hostMatches && $checkHost) {
$vars[] = '$hostMatches';
}
$code .= sprintf(
" \$ret = \$this->mergeDefaults(array_replace(%s), %s);\n",
implode(', ', $vars),
str_replace("\n", '', var_export($route->getDefaults(), true))
" \$ret = \$this->mergeDefaults(%s, %s);\n",
implode(' + ', $vars),
self::export($defaults)
);
} elseif ($route->getDefaults()) {
$code .= sprintf(" \$ret = %s;\n", str_replace("\n", '', var_export(array_replace($route->getDefaults(), array('_route' => $name)), true)));
} elseif ($defaults) {
$code .= sprintf(" \$ret = %s;\n", self::export(array('_route' => $name) + $defaults));
} 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);
$methodVariable = isset($methods['GET']) ? '$canonicalMethod' : '$requestMethod';
$methods = self::export($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));
$schemes = self::export(array_flip($schemes));
if ($methods) {
$code .= <<<EOF
\$requiredSchemes = $schemes;
\$hasRequiredScheme = isset(\$requiredSchemes[\$context->getScheme()]);
if (!in_array($methodVariable, array('$methods'))) {
if (!isset((\$a = {$methods})[{$methodVariable}])) {
if (\$hasRequiredScheme) {
\$allow = array_merge(\$allow, array('$methods'));
\$allow += \$a;
}
goto $gotoname;
}
if (!\$hasRequiredScheme) {
if ('GET' !== \$canonicalMethod) {
goto $gotoname;
}
return array_replace(\$ret, \$this->redirect(\$rawPathinfo, '$name', key(\$requiredSchemes)));
\$allowSchemes += \$requiredSchemes;
goto $gotoname;
}
@@ -351,11 +684,8 @@ EOF;
$code .= <<<EOF
\$requiredSchemes = $schemes;
if (!isset(\$requiredSchemes[\$context->getScheme()])) {
if ('GET' !== \$canonicalMethod) {
goto $gotoname;
}
return array_replace(\$ret, \$this->redirect(\$rawPathinfo, '$name', key(\$requiredSchemes)));
\$allowSchemes += \$requiredSchemes;
goto $gotoname;
}
@@ -363,8 +693,8 @@ EOF;
}
} elseif ($methods) {
$code .= <<<EOF
if (!in_array($methodVariable, array('$methods'))) {
\$allow = array_merge(\$allow, array('$methods'));
if (!isset((\$a = {$methods})[{$methodVariable}])) {
\$allow += \$a;
goto $gotoname;
}
@@ -372,47 +702,22 @@ EOF;
EOF;
}
if ($hasTrailingSlash || $schemes || $methods) {
if ($schemes || $methods) {
$code .= " return \$ret;\n";
} else {
$code = substr_replace($code, 'return', $retOffset, 6);
}
$code .= " }\n";
if ($conditions) {
$code .= " }\n";
} elseif ($schemes || $methods) {
$code .= ' ';
}
if ($hasTrailingSlash || $schemes || $methods) {
if ($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;
return $conditions ? $this->indent($code) : $code;
}
private function getExpressionLanguage()
@@ -426,4 +731,48 @@ EOF;
return $this->expressionLanguage;
}
private function indent($code, $level = 1)
{
return preg_replace('/^./m', str_repeat(' ', $level).'$0', $code);
}
/**
* @internal
*/
public static function export($value): string
{
if (null === $value) {
return 'null';
}
if (!\is_array($value)) {
if (\is_object($value)) {
throw new \InvalidArgumentException('Symfony\Component\Routing\Route cannot contain objects.');
}
return str_replace("\n", '\'."\n".\'', var_export($value, true));
}
if (!$value) {
return 'array()';
}
$i = 0;
$export = 'array(';
foreach ($value as $k => $v) {
if ($i === $k) {
++$i;
} else {
$export .= self::export($k).' => ';
if (\is_int($k) && $i < $k) {
$i = 1 + $k;
}
}
$export .= self::export($v).', ';
}
return substr_replace($export, ')', -2);
}
}

View File

@@ -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 $staticPrefixes = array();
/**
* @var string[]
*/
private $prefixes = array();
/**
* @var array[]|self[]
*/
private $items = array();
/**
* @var int
*/
private $matchStart = 0;
public function __construct($prefix = '')
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 = array($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(array(__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 array(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 0 === strpos($msg, 'preg_match(): 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(), array('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 ('/' === $pathinfo) {
throw $e;
} else {
try {
$pathinfo = '/' !== $pathinfo[-1] ? $pathinfo.'/' : substr($pathinfo, 0, -1);
$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

@@ -33,7 +33,19 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
const ROUTE_MATCH = 2;
protected $context;
/**
* Collects HTTP methods that would be allowed for the request.
*/
protected $allow = array();
/**
* Collects URI schemes that would be allowed for the request.
*
* @internal
*/
protected $allowSchemes = array();
protected $routes;
protected $request;
protected $expressionLanguage;
@@ -70,7 +82,7 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
*/
public function match($pathinfo)
{
$this->allow = array();
$this->allow = $this->allowSchemes = array();
if ($ret = $this->matchCollection(rawurldecode($pathinfo), $this->routes)) {
return $ret;
@@ -141,7 +153,7 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
continue;
}
// check HTTP method requirement
$hasRequiredScheme = !$route->getSchemes() || $route->hasScheme($this->context->getScheme());
if ($requiredMethods = $route->getMethods()) {
// HEAD and GET are equivalent as per RFC
if ('HEAD' === $method = $this->context->getMethod()) {
@@ -149,7 +161,7 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
}
if (!\in_array($method, $requiredMethods)) {
if (self::REQUIREMENT_MATCH === $status[0]) {
if ($hasRequiredScheme) {
$this->allow = array_merge($this->allow, $requiredMethods);
}
@@ -157,6 +169,12 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
}
}
if (!$hasRequiredScheme) {
$this->allowSchemes = array_merge($this->allowSchemes, $route->getSchemes());
continue;
}
return $this->getAttributes($route, $name, array_replace($matches, $hostMatches, isset($status[1]) ? $status[1] : array()));
}
}
@@ -176,9 +194,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);
}
/**
@@ -197,11 +220,7 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
return array(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 array(self::REQUIREMENT_MATCH, null);
}
/**
@@ -215,7 +234,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;
}
}

View File

@@ -33,17 +33,7 @@ class RequestContext
private $queryString;
private $parameters = array();
/**
* @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);

View File

@@ -50,11 +50,11 @@ class Route implements \Serializable
* @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
*/
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(), array $requirements = array(), array $options = array(), ?string $host = '', $schemes = array(), $methods = array(), ?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);
@@ -123,6 +123,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), '/');

View File

@@ -153,6 +153,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 = array();
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.
*

View File

@@ -58,7 +58,7 @@ class RouteCollectionBuilder
*/
public function import($resource, $prefix = '/', $type = null)
{
/** @var RouteCollection[] $collection */
/** @var RouteCollection[] $collections */
$collections = $this->load($resource, $type);
// create a builder from the RouteCollection
@@ -253,7 +253,7 @@ class RouteCollectionBuilder
*
* @return $this
*/
private function addResource(ResourceInterface $resource)
private function addResource(ResourceInterface $resource): self
{
$this->resources[] = $resource;
@@ -324,10 +324,8 @@ 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()).'_';
@@ -351,7 +349,7 @@ class RouteCollectionBuilder
*
* @throws FileLoaderLoadException 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.');

View File

@@ -103,8 +103,7 @@ class RouteCompiler implements RouteCompilerInterface
$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));
@@ -176,12 +175,12 @@ 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);
@@ -232,10 +231,8 @@ class RouteCompiler implements RouteCompilerInterface
/**
* 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 +248,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 +276,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 +305,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

@@ -24,6 +24,14 @@ class RouteTest extends TestCase
$route = new Route(array('foo' => 'bar'));
}
/**
* @expectedException \BadMethodCallException
*/
public function testTryingToSetLocalesDirectly()
{
$route = new Route(array('locales' => array('nl' => 'bar')));
}
/**
* @dataProvider getValidParameters
*/
@@ -45,6 +53,7 @@ class RouteTest extends TestCase
array('methods', array('GET', 'POST'), 'getMethods'),
array('host', '{locale}.example.com', 'getHost'),
array('condition', 'context.getMethod() == "GET"', 'getCondition'),
array('value', array('nl' => '/hier', 'en' => '/here'), 'getLocalizedPaths'),
);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,11 +17,9 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
public function match($rawPathinfo)
{
$allow = array();
$allow = $allowSchemes = array();
$pathinfo = rawurldecode($rawPathinfo);
$trimmedPathinfo = rtrim($pathinfo, '/');
$context = $this->context;
$request = $this->request ?: $this->createRequest($pathinfo);
$requestMethod = $canonicalMethod = $context->getMethod();
if ('HEAD' === $requestMethod) {
@@ -32,6 +30,6 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
throw new Symfony\Component\Routing\Exception\NoConfigurationException();
}
throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException();
throw $allow ? new MethodNotAllowedException(array_keys($allow)) : new ResourceNotFoundException();
}
}

View File

@@ -17,302 +17,231 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
public function match($rawPathinfo)
{
$allow = array();
$allow = $allowSchemes = array();
$pathinfo = rawurldecode($rawPathinfo);
$trimmedPathinfo = rtrim($pathinfo, '/');
$context = $this->context;
$request = $this->request ?: $this->createRequest($pathinfo);
$requestMethod = $canonicalMethod = $context->getMethod();
$host = strtolower($context->getHost());
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',));
}
switch ($pathinfo) {
default:
$routes = array(
'/test/baz' => array(array('_route' => 'baz'), null, null, null),
'/test/baz.html' => array(array('_route' => 'baz2'), null, null, null),
'/test/baz3/' => array(array('_route' => 'baz3'), null, null, null),
'/foofoo' => array(array('_route' => 'foofoo', 'def' => 'test'), null, null, null),
'/spa ce' => array(array('_route' => 'space'), null, null, null),
'/multi/new' => array(array('_route' => 'overridden2'), null, null, null),
'/multi/hey/' => array(array('_route' => 'hey'), null, null, null),
'/ababa' => array(array('_route' => 'ababa'), null, null, null),
'/route1' => array(array('_route' => 'route1'), 'a.example.com', null, null),
'/c2/route2' => array(array('_route' => 'route2'), 'a.example.com', null, null),
'/route4' => array(array('_route' => 'route4'), 'a.example.com', null, null),
'/c2/route3' => array(array('_route' => 'route3'), 'b.example.com', null, null),
'/route5' => array(array('_route' => 'route5'), 'c.example.com', null, null),
'/route6' => array(array('_route' => 'route6'), null, null, null),
'/route11' => array(array('_route' => 'route11'), '#^(?P<var1>[^\\.]++)\\.example\\.com$#sDi', null, null),
'/route12' => array(array('_route' => 'route12', 'var1' => 'val'), '#^(?P<var1>[^\\.]++)\\.example\\.com$#sDi', null, null),
'/route17' => array(array('_route' => 'route17'), null, null, null),
);
// foofoo
if ('/foofoo' === $pathinfo) {
return array ( 'def' => 'test', '_route' => 'foofoo',);
}
if (!isset($routes[$pathinfo])) {
break;
}
list($ret, $requiredHost, $requiredMethods, $requiredSchemes) = $routes[$pathinfo];
}
if ($requiredHost) {
if ('#' !== $requiredHost[0] ? $requiredHost !== $host : !preg_match($requiredHost, $host, $hostMatches)) {
break;
}
if ('#' === $requiredHost[0] && $hostMatches) {
$hostMatches['_route'] = $ret['_route'];
$ret = $this->mergeDefaults($hostMatches, $ret);
}
}
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;
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
if ($hasRequiredScheme) {
$allow += $requiredMethods;
}
break;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
break;
}
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;
$matchedPathinfo = $host.'.'.$pathinfo;
$regexList = array(
0 => '{^(?'
.'|(?:(?:[^./]*+\\.)++)(?'
.'|/foo/(baz|symfony)(*:47)'
.'|/bar(?'
.'|/([^/]++)(*:70)'
.'|head/([^/]++)(*:90)'
.')'
.'|/test/([^/]++)/(?'
.'|(*:116)'
.')'
.'|/([\']+)(*:132)'
.'|/a/(?'
.'|b\'b/([^/]++)(?'
.'|(*:161)'
.'|(*:169)'
.')'
.'|(.*)(*:182)'
.'|b\'b/([^/]++)(?'
.'|(*:205)'
.'|(*:213)'
.')'
.')'
.'|/multi/hello(?:/([^/]++))?(*:249)'
.'|/([^/]++)/b/([^/]++)(?'
.'|(*:280)'
.'|(*:288)'
.')'
.'|/aba/([^/]++)(*:310)'
.')|(?i:([^\\.]++)\\.example\\.com)\\.(?'
.'|/route1(?'
.'|3/([^/]++)(*:372)'
.'|4/([^/]++)(*:390)'
.')'
.')|(?i:c\\.example\\.com)\\.(?'
.'|/route15/([^/]++)(*:442)'
.')|(?:(?:[^./]*+\\.)++)(?'
.'|/route16/([^/]++)(*:490)'
.'|/a/(?'
.'|a\\.\\.\\.(*:511)'
.'|b/(?'
.'|([^/]++)(*:532)'
.'|c/([^/]++)(*:550)'
.')'
.')'
.')'
.')$}sD',
);
foreach ($regexList as $offset => $regex) {
while (preg_match($regex, $matchedPathinfo, $matches)) {
switch ($m = (int) $matches['MARK']) {
case 116:
$matches = array('foo' => $matches[1] ?? null);
// baz4
return $this->mergeDefaults(array('_route' => 'baz4') + $matches, array());
// baz5
$ret = $this->mergeDefaults(array('_route' => 'baz5') + $matches, array());
if (!isset(($a = array('POST' => 0))[$requestMethod])) {
$allow += $a;
goto not_baz5;
}
return $ret;
not_baz5:
// baz.baz6
$ret = $this->mergeDefaults(array('_route' => 'baz.baz6') + $matches, array());
if (!isset(($a = array('PUT' => 0))[$requestMethod])) {
$allow += $a;
goto not_bazbaz6;
}
return $ret;
not_bazbaz6:
break;
case 161:
$matches = array('foo' => $matches[1] ?? null);
// foo1
$ret = $this->mergeDefaults(array('_route' => 'foo1') + $matches, array());
if (!isset(($a = array('PUT' => 0))[$requestMethod])) {
$allow += $a;
goto not_foo1;
}
return $ret;
not_foo1:
break;
case 205:
$matches = array('foo1' => $matches[1] ?? null);
// foo2
return $this->mergeDefaults(array('_route' => 'foo2') + $matches, array());
break;
case 280:
$matches = array('_locale' => $matches[1] ?? null, 'foo' => $matches[2] ?? null);
// foo3
return $this->mergeDefaults(array('_route' => 'foo3') + $matches, array());
break;
default:
$routes = array(
47 => array(array('_route' => 'foo', 'def' => 'test'), array('bar'), null, null),
70 => array(array('_route' => 'bar'), array('foo'), array('GET' => 0, 'HEAD' => 1), null),
90 => array(array('_route' => 'barhead'), array('foo'), array('GET' => 0), null),
132 => array(array('_route' => 'quoter'), array('quoter'), null, null),
169 => array(array('_route' => 'bar1'), array('bar'), null, null),
182 => array(array('_route' => 'overridden'), array('var'), null, null),
213 => array(array('_route' => 'bar2'), array('bar1'), null, null),
249 => array(array('_route' => 'helloWorld', 'who' => 'World!'), array('who'), null, null),
288 => array(array('_route' => 'bar3'), array('_locale', 'bar'), null, null),
310 => array(array('_route' => 'foo4'), array('foo'), null, null),
372 => array(array('_route' => 'route13'), array('var1', 'name'), null, null),
390 => array(array('_route' => 'route14', 'var1' => 'val'), array('var1', 'name'), null, null),
442 => array(array('_route' => 'route15'), array('name'), null, null),
490 => array(array('_route' => 'route16', 'var1' => 'val'), array('name'), null, null),
511 => array(array('_route' => 'a'), array(), null, null),
532 => array(array('_route' => 'b'), array('var'), null, null),
550 => array(array('_route' => 'c'), array('var'), null, null),
);
list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m];
foreach ($vars as $i => $v) {
if (isset($matches[1 + $i])) {
$ret[$v] = $matches[1 + $i];
}
}
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
if ($hasRequiredScheme) {
$allow += $requiredMethods;
}
break;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
break;
}
return $ret;
}
return $ret;
}
not_barhead:
}
elseif (0 === strpos($pathinfo, '/test')) {
if (0 === strpos($pathinfo, '/test/baz')) {
// baz
if ('/test/baz' === $pathinfo) {
return array('_route' => 'baz');
if (550 === $m) {
break;
}
// baz2
if ('/test/baz.html' === $pathinfo) {
return array('_route' => 'baz2');
}
// baz3
if ('/test/baz3/' === $pathinfo) {
return array('_route' => 'baz3');
}
$regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m));
$offset += strlen($m);
}
// 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();
throw $allow ? new MethodNotAllowedException(array_keys($allow)) : new ResourceNotFoundException();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,151 @@
<?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($pathinfo)
{
$allow = $allowSchemes = array();
if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) {
return $ret;
}
if ($allow) {
throw new MethodNotAllowedException(array_keys($allow));
}
if (!in_array($this->context->getMethod(), array('HEAD', 'GET'), true)) {
// no-op
} elseif ($allowSchemes) {
redirect_scheme:
$scheme = $this->context->getScheme();
$this->context->setScheme(key($allowSchemes));
try {
if ($ret = $this->doMatch($pathinfo)) {
return $this->redirect($pathinfo, $ret['_route'], $this->context->getScheme()) + $ret;
}
} finally {
$this->context->setScheme($scheme);
}
} elseif ('/' !== $pathinfo) {
$pathinfo = '/' !== $pathinfo[-1] ? $pathinfo.'/' : substr($pathinfo, 0, -1);
if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) {
return $this->redirect($pathinfo, $ret['_route']) + $ret;
}
if ($allowSchemes) {
goto redirect_scheme;
}
}
throw new ResourceNotFoundException();
}
private function doMatch(string $rawPathinfo, array &$allow = array(), array &$allowSchemes = array()): ?array
{
$allow = $allowSchemes = array();
$pathinfo = rawurldecode($rawPathinfo);
$context = $this->context;
$requestMethod = $canonicalMethod = $context->getMethod();
if ('HEAD' === $requestMethod) {
$canonicalMethod = 'GET';
}
$matchedPathinfo = $pathinfo;
$regexList = array(
0 => '{^(?'
.'|/(en|fr)/(?'
.'|admin/post/(?'
.'|(*:33)'
.'|new(*:43)'
.'|(\\d+)(*:55)'
.'|(\\d+)/edit(*:72)'
.'|(\\d+)/delete(*:91)'
.')'
.'|blog/(?'
.'|(*:107)'
.'|rss\\.xml(*:123)'
.'|p(?'
.'|age/([^/]++)(*:147)'
.'|osts/([^/]++)(*:168)'
.')'
.'|comments/(\\d+)/new(*:195)'
.'|search(*:209)'
.')'
.'|log(?'
.'|in(*:226)'
.'|out(*:237)'
.')'
.')'
.'|/(en|fr)?(*:256)'
.')$}sD',
);
foreach ($regexList as $offset => $regex) {
while (preg_match($regex, $matchedPathinfo, $matches)) {
switch ($m = (int) $matches['MARK']) {
default:
$routes = array(
33 => array(array('_route' => 'a', '_locale' => 'en'), array('_locale'), null, null),
43 => array(array('_route' => 'b', '_locale' => 'en'), array('_locale'), null, null),
55 => array(array('_route' => 'c', '_locale' => 'en'), array('_locale', 'id'), null, null),
72 => array(array('_route' => 'd', '_locale' => 'en'), array('_locale', 'id'), null, null),
91 => array(array('_route' => 'e', '_locale' => 'en'), array('_locale', 'id'), null, null),
107 => array(array('_route' => 'f', '_locale' => 'en'), array('_locale'), null, null),
123 => array(array('_route' => 'g', '_locale' => 'en'), array('_locale'), null, null),
147 => array(array('_route' => 'h', '_locale' => 'en'), array('_locale', 'page'), null, null),
168 => array(array('_route' => 'i', '_locale' => 'en'), array('_locale', 'page'), null, null),
195 => array(array('_route' => 'j', '_locale' => 'en'), array('_locale', 'id'), null, null),
209 => array(array('_route' => 'k', '_locale' => 'en'), array('_locale'), null, null),
226 => array(array('_route' => 'l', '_locale' => 'en'), array('_locale'), null, null),
237 => array(array('_route' => 'm', '_locale' => 'en'), array('_locale'), null, null),
256 => array(array('_route' => 'n', '_locale' => 'en'), array('_locale'), null, null),
);
list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m];
foreach ($vars as $i => $v) {
if (isset($matches[1 + $i])) {
$ret[$v] = $matches[1 + $i];
}
}
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
if ($hasRequiredScheme) {
$allow += $requiredMethods;
}
break;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
break;
}
return $ret;
}
if (256 === $m) {
break;
}
$regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m));
$offset += strlen($m);
}
}
if ('/' === $pathinfo && !$allow) {
throw new Symfony\Component\Routing\Exception\NoConfigurationException();
}
return null;
}
}

View File

@@ -0,0 +1,100 @@
<?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 = $allowSchemes = array();
$pathinfo = rawurldecode($rawPathinfo);
$context = $this->context;
$requestMethod = $canonicalMethod = $context->getMethod();
if ('HEAD' === $requestMethod) {
$canonicalMethod = 'GET';
}
$matchedPathinfo = $pathinfo;
$regexList = array(
0 => '{^(?'
.'|/abc([^/]++)/(?'
.'|1(?'
.'|(*:27)'
.'|0(?'
.'|(*:38)'
.'|0(*:46)'
.')'
.')'
.'|2(?'
.'|(*:59)'
.'|0(?'
.'|(*:70)'
.'|0(*:78)'
.')'
.')'
.')'
.')$}sD',
);
foreach ($regexList as $offset => $regex) {
while (preg_match($regex, $matchedPathinfo, $matches)) {
switch ($m = (int) $matches['MARK']) {
default:
$routes = array(
27 => array(array('_route' => 'r1'), array('foo'), null, null),
38 => array(array('_route' => 'r10'), array('foo'), null, null),
46 => array(array('_route' => 'r100'), array('foo'), null, null),
59 => array(array('_route' => 'r2'), array('foo'), null, null),
70 => array(array('_route' => 'r20'), array('foo'), null, null),
78 => array(array('_route' => 'r200'), array('foo'), null, null),
);
list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m];
foreach ($vars as $i => $v) {
if (isset($matches[1 + $i])) {
$ret[$v] = $matches[1 + $i];
}
}
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
if ($hasRequiredScheme) {
$allow += $requiredMethods;
}
break;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
break;
}
return $ret;
}
if (78 === $m) {
break;
}
$regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m));
$offset += strlen($m);
}
}
if ('/' === $pathinfo && !$allow) {
throw new Symfony\Component\Routing\Exception\NoConfigurationException();
}
throw $allow ? new MethodNotAllowedException(array_keys($allow)) : new ResourceNotFoundException();
}
}

View File

@@ -0,0 +1,69 @@
<?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 = $allowSchemes = array();
$pathinfo = rawurldecode($rawPathinfo);
$context = $this->context;
$requestMethod = $canonicalMethod = $context->getMethod();
$host = strtolower($context->getHost());
if ('HEAD' === $requestMethod) {
$canonicalMethod = 'GET';
}
$matchedPathinfo = $host.'.'.$pathinfo;
$regexList = array(
0 => '{^(?'
.'|(?i:([^\\.]++)\\.exampple\\.com)\\.(?'
.'|/abc([^/]++)(?'
.'|(*:56)'
.')'
.')'
.')$}sD',
);
foreach ($regexList as $offset => $regex) {
while (preg_match($regex, $matchedPathinfo, $matches)) {
switch ($m = (int) $matches['MARK']) {
case 56:
$matches = array('foo' => $matches[1] ?? null, 'foo' => $matches[2] ?? null);
// r1
return $this->mergeDefaults(array('_route' => 'r1') + $matches, array());
// r2
return $this->mergeDefaults(array('_route' => 'r2') + $matches, array());
break;
}
if (56 === $m) {
break;
}
$regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m));
$offset += strlen($m);
}
}
if ('/' === $pathinfo && !$allow) {
throw new Symfony\Component\Routing\Exception\NoConfigurationException();
}
throw $allow ? new MethodNotAllowedException(array_keys($allow)) : new ResourceNotFoundException();
}
}

View File

@@ -15,366 +15,270 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
$this->context = $context;
}
public function match($rawPathinfo)
public function match($pathinfo)
{
$allow = array();
$allow = $allowSchemes = array();
if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) {
return $ret;
}
if ($allow) {
throw new MethodNotAllowedException(array_keys($allow));
}
if (!in_array($this->context->getMethod(), array('HEAD', 'GET'), true)) {
// no-op
} elseif ($allowSchemes) {
redirect_scheme:
$scheme = $this->context->getScheme();
$this->context->setScheme(key($allowSchemes));
try {
if ($ret = $this->doMatch($pathinfo)) {
return $this->redirect($pathinfo, $ret['_route'], $this->context->getScheme()) + $ret;
}
} finally {
$this->context->setScheme($scheme);
}
} elseif ('/' !== $pathinfo) {
$pathinfo = '/' !== $pathinfo[-1] ? $pathinfo.'/' : substr($pathinfo, 0, -1);
if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) {
return $this->redirect($pathinfo, $ret['_route']) + $ret;
}
if ($allowSchemes) {
goto redirect_scheme;
}
}
throw new ResourceNotFoundException();
}
private function doMatch(string $rawPathinfo, array &$allow = array(), array &$allowSchemes = array()): ?array
{
$allow = $allowSchemes = array();
$pathinfo = rawurldecode($rawPathinfo);
$trimmedPathinfo = rtrim($pathinfo, '/');
$context = $this->context;
$request = $this->request ?: $this->createRequest($pathinfo);
$requestMethod = $canonicalMethod = $context->getMethod();
$host = strtolower($context->getHost());
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',));
}
switch ($pathinfo) {
default:
$routes = array(
'/test/baz' => array(array('_route' => 'baz'), null, null, null),
'/test/baz.html' => array(array('_route' => 'baz2'), null, null, null),
'/test/baz3/' => array(array('_route' => 'baz3'), null, null, null),
'/foofoo' => array(array('_route' => 'foofoo', 'def' => 'test'), null, null, null),
'/spa ce' => array(array('_route' => 'space'), null, null, null),
'/multi/new' => array(array('_route' => 'overridden2'), null, null, null),
'/multi/hey/' => array(array('_route' => 'hey'), null, null, null),
'/ababa' => array(array('_route' => 'ababa'), null, null, null),
'/route1' => array(array('_route' => 'route1'), 'a.example.com', null, null),
'/c2/route2' => array(array('_route' => 'route2'), 'a.example.com', null, null),
'/route4' => array(array('_route' => 'route4'), 'a.example.com', null, null),
'/c2/route3' => array(array('_route' => 'route3'), 'b.example.com', null, null),
'/route5' => array(array('_route' => 'route5'), 'c.example.com', null, null),
'/route6' => array(array('_route' => 'route6'), null, null, null),
'/route11' => array(array('_route' => 'route11'), '#^(?P<var1>[^\\.]++)\\.example\\.com$#sDi', null, null),
'/route12' => array(array('_route' => 'route12', 'var1' => 'val'), '#^(?P<var1>[^\\.]++)\\.example\\.com$#sDi', null, null),
'/route17' => array(array('_route' => 'route17'), null, null, null),
'/secure' => array(array('_route' => 'secure'), null, null, array('https' => 0)),
'/nonsecure' => array(array('_route' => 'nonsecure'), null, null, array('http' => 0)),
);
// 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;
if (!isset($routes[$pathinfo])) {
break;
}
list($ret, $requiredHost, $requiredMethods, $requiredSchemes) = $routes[$pathinfo];
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'));
if ($requiredHost) {
if ('#' !== $requiredHost[0] ? $requiredHost !== $host : !preg_match($requiredHost, $host, $hostMatches)) {
break;
}
if ('#' === $requiredHost[0] && $hostMatches) {
$hostMatches['_route'] = $ret['_route'];
$ret = $this->mergeDefaults($hostMatches, $ret);
}
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'));
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
if ($hasRequiredScheme) {
$allow += $requiredMethods;
}
break;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
break;
}
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;
$matchedPathinfo = $host.'.'.$pathinfo;
$regexList = array(
0 => '{^(?'
.'|(?:(?:[^./]*+\\.)++)(?'
.'|/foo/(baz|symfony)(*:47)'
.'|/bar(?'
.'|/([^/]++)(*:70)'
.'|head/([^/]++)(*:90)'
.')'
.'|/test/([^/]++)/(?'
.'|(*:116)'
.')'
.'|/([\']+)(*:132)'
.'|/a/(?'
.'|b\'b/([^/]++)(?'
.'|(*:161)'
.'|(*:169)'
.')'
.'|(.*)(*:182)'
.'|b\'b/([^/]++)(?'
.'|(*:205)'
.'|(*:213)'
.')'
.')'
.'|/multi/hello(?:/([^/]++))?(*:249)'
.'|/([^/]++)/b/([^/]++)(?'
.'|(*:280)'
.'|(*:288)'
.')'
.'|/aba/([^/]++)(*:310)'
.')|(?i:([^\\.]++)\\.example\\.com)\\.(?'
.'|/route1(?'
.'|3/([^/]++)(*:372)'
.'|4/([^/]++)(*:390)'
.')'
.')|(?i:c\\.example\\.com)\\.(?'
.'|/route15/([^/]++)(*:442)'
.')|(?:(?:[^./]*+\\.)++)(?'
.'|/route16/([^/]++)(*:490)'
.'|/a/(?'
.'|a\\.\\.\\.(*:511)'
.'|b/(?'
.'|([^/]++)(*:532)'
.'|c/([^/]++)(*:550)'
.')'
.')'
.')'
.')$}sD',
);
foreach ($regexList as $offset => $regex) {
while (preg_match($regex, $matchedPathinfo, $matches)) {
switch ($m = (int) $matches['MARK']) {
case 116:
$matches = array('foo' => $matches[1] ?? null);
// baz4
return $this->mergeDefaults(array('_route' => 'baz4') + $matches, array());
// baz5
$ret = $this->mergeDefaults(array('_route' => 'baz5') + $matches, array());
if (!isset(($a = array('POST' => 0))[$requestMethod])) {
$allow += $a;
goto not_baz5;
}
return $ret;
not_baz5:
// baz.baz6
$ret = $this->mergeDefaults(array('_route' => 'baz.baz6') + $matches, array());
if (!isset(($a = array('PUT' => 0))[$requestMethod])) {
$allow += $a;
goto not_bazbaz6;
}
return $ret;
not_bazbaz6:
break;
case 161:
$matches = array('foo' => $matches[1] ?? null);
// foo1
$ret = $this->mergeDefaults(array('_route' => 'foo1') + $matches, array());
if (!isset(($a = array('PUT' => 0))[$requestMethod])) {
$allow += $a;
goto not_foo1;
}
return $ret;
not_foo1:
break;
case 205:
$matches = array('foo1' => $matches[1] ?? null);
// foo2
return $this->mergeDefaults(array('_route' => 'foo2') + $matches, array());
break;
case 280:
$matches = array('_locale' => $matches[1] ?? null, 'foo' => $matches[2] ?? null);
// foo3
return $this->mergeDefaults(array('_route' => 'foo3') + $matches, array());
break;
default:
$routes = array(
47 => array(array('_route' => 'foo', 'def' => 'test'), array('bar'), null, null),
70 => array(array('_route' => 'bar'), array('foo'), array('GET' => 0, 'HEAD' => 1), null),
90 => array(array('_route' => 'barhead'), array('foo'), array('GET' => 0), null),
132 => array(array('_route' => 'quoter'), array('quoter'), null, null),
169 => array(array('_route' => 'bar1'), array('bar'), null, null),
182 => array(array('_route' => 'overridden'), array('var'), null, null),
213 => array(array('_route' => 'bar2'), array('bar1'), null, null),
249 => array(array('_route' => 'helloWorld', 'who' => 'World!'), array('who'), null, null),
288 => array(array('_route' => 'bar3'), array('_locale', 'bar'), null, null),
310 => array(array('_route' => 'foo4'), array('foo'), null, null),
372 => array(array('_route' => 'route13'), array('var1', 'name'), null, null),
390 => array(array('_route' => 'route14', 'var1' => 'val'), array('var1', 'name'), null, null),
442 => array(array('_route' => 'route15'), array('name'), null, null),
490 => array(array('_route' => 'route16', 'var1' => 'val'), array('name'), null, null),
511 => array(array('_route' => 'a'), array(), null, null),
532 => array(array('_route' => 'b'), array('var'), null, null),
550 => array(array('_route' => 'c'), array('var'), null, null),
);
list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m];
foreach ($vars as $i => $v) {
if (isset($matches[1 + $i])) {
$ret[$v] = $matches[1 + $i];
}
}
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
if ($hasRequiredScheme) {
$allow += $requiredMethods;
}
break;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
break;
}
return $ret;
}
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;
if (550 === $m) {
break;
}
return $ret;
$regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m));
$offset += strlen($m);
}
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();
return null;
}
}

View File

@@ -17,39 +17,96 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
public function match($rawPathinfo)
{
$allow = array();
$allow = $allowSchemes = 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');
}
switch ($pathinfo) {
case '/with-condition':
// with-condition
if (($context->getMethod() == "GET")) {
return array('_route' => 'with-condition');
}
break;
default:
$routes = array(
'/rootprefix/test' => array(array('_route' => 'static'), null, null, null),
);
// dynamic
if (preg_match('#^/rootprefix/(?P<var>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'dynamic')), array ());
}
if (!isset($routes[$pathinfo])) {
break;
}
list($ret, $requiredHost, $requiredMethods, $requiredSchemes) = $routes[$pathinfo];
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
if ($hasRequiredScheme) {
$allow += $requiredMethods;
}
break;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
break;
}
return $ret;
}
// with-condition
if ('/with-condition' === $pathinfo && ($context->getMethod() == "GET")) {
return array('_route' => 'with-condition');
}
$matchedPathinfo = $pathinfo;
$regexList = array(
0 => '{^(?'
.'|/rootprefix/([^/]++)(*:27)'
.')$}sD',
);
foreach ($regexList as $offset => $regex) {
while (preg_match($regex, $matchedPathinfo, $matches)) {
switch ($m = (int) $matches['MARK']) {
default:
$routes = array(
27 => array(array('_route' => 'dynamic'), array('var'), null, null),
);
list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m];
foreach ($vars as $i => $v) {
if (isset($matches[1 + $i])) {
$ret[$v] = $matches[1 + $i];
}
}
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
if ($hasRequiredScheme) {
$allow += $requiredMethods;
}
break;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
break;
}
return $ret;
}
if (27 === $m) {
break;
}
$regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m));
$offset += strlen($m);
}
}
if ('/' === $pathinfo && !$allow) {
throw new Symfony\Component\Routing\Exception\NoConfigurationException();
}
throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException();
throw $allow ? new MethodNotAllowedException(array_keys($allow)) : new ResourceNotFoundException();
}
}

View File

@@ -17,96 +17,68 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
public function match($rawPathinfo)
{
$allow = array();
$allow = $allowSchemes = 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) {
switch ($pathinfo) {
case '/put_and_post':
// put_and_post
$ret = array('_route' => 'put_and_post');
if (!in_array($requestMethod, array('PUT', 'POST'))) {
$allow = array_merge($allow, array('PUT', 'POST'));
if (!isset(($a = array('PUT' => 0, 'POST' => 1))[$requestMethod])) {
$allow += $a;
goto not_put_and_post;
}
return $ret;
}
not_put_and_post:
// put_and_get_and_head
if ('/put_and_post' === $pathinfo) {
not_put_and_post:
// put_and_get_and_head
$ret = array('_route' => 'put_and_get_and_head');
if (!in_array($canonicalMethod, array('PUT', 'GET', 'HEAD'))) {
$allow = array_merge($allow, array('PUT', 'GET', 'HEAD'));
if (!isset(($a = array('PUT' => 0, 'GET' => 1, 'HEAD' => 2))[$canonicalMethod])) {
$allow += $a;
goto not_put_and_get_and_head;
}
return $ret;
}
not_put_and_get_and_head:
not_put_and_get_and_head:
break;
default:
$routes = array(
'/just_head' => array(array('_route' => 'just_head'), null, array('HEAD' => 0), null),
'/head_and_get' => array(array('_route' => 'head_and_get'), null, array('HEAD' => 0, 'GET' => 1), null),
'/get_and_head' => array(array('_route' => 'get_and_head'), null, array('GET' => 0, 'HEAD' => 1), null),
'/post_and_head' => array(array('_route' => 'post_and_head'), null, array('POST' => 0, 'HEAD' => 1), null),
);
if (!isset($routes[$pathinfo])) {
break;
}
list($ret, $requiredHost, $requiredMethods, $requiredSchemes) = $routes[$pathinfo];
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
if ($hasRequiredScheme) {
$allow += $requiredMethods;
}
break;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
break;
}
return $ret;
}
if ('/' === $pathinfo && !$allow) {
throw new Symfony\Component\Routing\Exception\NoConfigurationException();
}
throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException();
throw $allow ? new MethodNotAllowedException(array_keys($allow)) : new ResourceNotFoundException();
}
}

View File

@@ -15,195 +15,140 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
$this->context = $context;
}
public function match($rawPathinfo)
public function match($pathinfo)
{
$allow = array();
$allow = $allowSchemes = array();
if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) {
return $ret;
}
if ($allow) {
throw new MethodNotAllowedException(array_keys($allow));
}
if (!in_array($this->context->getMethod(), array('HEAD', 'GET'), true)) {
// no-op
} elseif ($allowSchemes) {
redirect_scheme:
$scheme = $this->context->getScheme();
$this->context->setScheme(key($allowSchemes));
try {
if ($ret = $this->doMatch($pathinfo)) {
return $this->redirect($pathinfo, $ret['_route'], $this->context->getScheme()) + $ret;
}
} finally {
$this->context->setScheme($scheme);
}
} elseif ('/' !== $pathinfo) {
$pathinfo = '/' !== $pathinfo[-1] ? $pathinfo.'/' : substr($pathinfo, 0, -1);
if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) {
return $this->redirect($pathinfo, $ret['_route']) + $ret;
}
if ($allowSchemes) {
goto redirect_scheme;
}
}
throw new ResourceNotFoundException();
}
private function doMatch(string $rawPathinfo, array &$allow = array(), array &$allowSchemes = array()): ?array
{
$allow = $allowSchemes = 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');
}
switch ($pathinfo) {
default:
$routes = array(
'/a/11' => array(array('_route' => 'a_first'), null, null, null),
'/a/22' => array(array('_route' => 'a_second'), null, null, null),
'/a/333' => array(array('_route' => 'a_third'), null, null, null),
'/a/44/' => array(array('_route' => 'a_fourth'), null, null, null),
'/a/55/' => array(array('_route' => 'a_fifth'), null, null, null),
'/a/66/' => array(array('_route' => 'a_sixth'), null, null, null),
'/nested/group/a/' => array(array('_route' => 'nested_a'), null, null, null),
'/nested/group/b/' => array(array('_route' => 'nested_b'), null, null, null),
'/nested/group/c/' => array(array('_route' => 'nested_c'), null, null, null),
'/slashed/group/' => array(array('_route' => 'slashed_a'), null, null, null),
'/slashed/group/b/' => array(array('_route' => 'slashed_b'), null, null, null),
'/slashed/group/c/' => array(array('_route' => 'slashed_c'), null, null, null),
);
// a_second
if ('/a/22' === $pathinfo) {
return array('_route' => 'a_second');
}
if (!isset($routes[$pathinfo])) {
break;
}
list($ret, $requiredHost, $requiredMethods, $requiredSchemes) = $routes[$pathinfo];
// a_third
if ('/a/333' === $pathinfo) {
return array('_route' => 'a_third');
}
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
if ($hasRequiredScheme) {
$allow += $requiredMethods;
}
break;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
break;
}
return $ret;
}
// a_wildcard
if (preg_match('#^/(?P<param>[^/]++)$#sD', $pathinfo, $matches)) {
return $this->mergeDefaults(array_replace($matches, array('_route' => 'a_wildcard')), array ());
$matchedPathinfo = $pathinfo;
$regexList = array(
0 => '{^(?'
.'|/([^/]++)(*:16)'
.'|/nested/([^/]++)(*:39)'
.')$}sD',
);
foreach ($regexList as $offset => $regex) {
while (preg_match($regex, $matchedPathinfo, $matches)) {
switch ($m = (int) $matches['MARK']) {
default:
$routes = array(
16 => array(array('_route' => 'a_wildcard'), array('param'), null, null),
39 => array(array('_route' => 'nested_wildcard'), array('param'), null, null),
);
list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m];
foreach ($vars as $i => $v) {
if (isset($matches[1 + $i])) {
$ret[$v] = $matches[1 + $i];
}
}
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
if ($hasRequiredScheme) {
$allow += $requiredMethods;
}
break;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
break;
}
return $ret;
}
if (39 === $m) {
break;
}
$regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m));
$offset += strlen($m);
}
}
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();
return null;
}
}

View File

@@ -17,197 +17,115 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
public function match($rawPathinfo)
{
$allow = array();
$allow = $allowSchemes = 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');
}
switch ($pathinfo) {
default:
$routes = array(
'/trailing/simple/no-methods/' => array(array('_route' => 'simple_trailing_slash_no_methods'), null, null, null),
'/trailing/simple/get-method/' => array(array('_route' => 'simple_trailing_slash_GET_method'), null, array('GET' => 0), null),
'/trailing/simple/head-method/' => array(array('_route' => 'simple_trailing_slash_HEAD_method'), null, array('HEAD' => 0), null),
'/trailing/simple/post-method/' => array(array('_route' => 'simple_trailing_slash_POST_method'), null, array('POST' => 0), null),
'/not-trailing/simple/no-methods' => array(array('_route' => 'simple_not_trailing_slash_no_methods'), null, null, null),
'/not-trailing/simple/get-method' => array(array('_route' => 'simple_not_trailing_slash_GET_method'), null, array('GET' => 0), null),
'/not-trailing/simple/head-method' => array(array('_route' => 'simple_not_trailing_slash_HEAD_method'), null, array('HEAD' => 0), null),
'/not-trailing/simple/post-method' => array(array('_route' => 'simple_not_trailing_slash_POST_method'), null, array('POST' => 0), null),
);
// 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;
if (!isset($routes[$pathinfo])) {
break;
}
list($ret, $requiredHost, $requiredMethods, $requiredSchemes) = $routes[$pathinfo];
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
if ($hasRequiredScheme) {
$allow += $requiredMethods;
}
break;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
break;
}
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 ());
}
$matchedPathinfo = $pathinfo;
$regexList = array(
0 => '{^(?'
.'|/trailing/regex/(?'
.'|no\\-methods/([^/]++)/(*:47)'
.'|get\\-method/([^/]++)/(*:75)'
.'|head\\-method/([^/]++)/(*:104)'
.'|post\\-method/([^/]++)/(*:134)'
.')'
.'|/not\\-trailing/regex/(?'
.'|no\\-methods/([^/]++)(*:187)'
.'|get\\-method/([^/]++)(*:215)'
.'|head\\-method/([^/]++)(*:244)'
.'|post\\-method/([^/]++)(*:273)'
.')'
.')$}sD',
);
// 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;
foreach ($regexList as $offset => $regex) {
while (preg_match($regex, $matchedPathinfo, $matches)) {
switch ($m = (int) $matches['MARK']) {
default:
$routes = array(
47 => array(array('_route' => 'regex_trailing_slash_no_methods'), array('param'), null, null),
75 => array(array('_route' => 'regex_trailing_slash_GET_method'), array('param'), array('GET' => 0), null),
104 => array(array('_route' => 'regex_trailing_slash_HEAD_method'), array('param'), array('HEAD' => 0), null),
134 => array(array('_route' => 'regex_trailing_slash_POST_method'), array('param'), array('POST' => 0), null),
187 => array(array('_route' => 'regex_not_trailing_slash_no_methods'), array('param'), null, null),
215 => array(array('_route' => 'regex_not_trailing_slash_GET_method'), array('param'), array('GET' => 0), null),
244 => array(array('_route' => 'regex_not_trailing_slash_HEAD_method'), array('param'), array('HEAD' => 0), null),
273 => array(array('_route' => 'regex_not_trailing_slash_POST_method'), array('param'), array('POST' => 0), null),
);
list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m];
foreach ($vars as $i => $v) {
if (isset($matches[1 + $i])) {
$ret[$v] = $matches[1 + $i];
}
}
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
if ($hasRequiredScheme) {
$allow += $requiredMethods;
}
break;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
break;
}
return $ret;
}
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;
if (273 === $m) {
break;
}
return $ret;
$regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m));
$offset += strlen($m);
}
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();
throw $allow ? new MethodNotAllowedException(array_keys($allow)) : new ResourceNotFoundException();
}
}

View File

@@ -15,235 +15,152 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
$this->context = $context;
}
public function match($rawPathinfo)
public function match($pathinfo)
{
$allow = array();
$allow = $allowSchemes = array();
if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) {
return $ret;
}
if ($allow) {
throw new MethodNotAllowedException(array_keys($allow));
}
if (!in_array($this->context->getMethod(), array('HEAD', 'GET'), true)) {
// no-op
} elseif ($allowSchemes) {
redirect_scheme:
$scheme = $this->context->getScheme();
$this->context->setScheme(key($allowSchemes));
try {
if ($ret = $this->doMatch($pathinfo)) {
return $this->redirect($pathinfo, $ret['_route'], $this->context->getScheme()) + $ret;
}
} finally {
$this->context->setScheme($scheme);
}
} elseif ('/' !== $pathinfo) {
$pathinfo = '/' !== $pathinfo[-1] ? $pathinfo.'/' : substr($pathinfo, 0, -1);
if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) {
return $this->redirect($pathinfo, $ret['_route']) + $ret;
}
if ($allowSchemes) {
goto redirect_scheme;
}
}
throw new ResourceNotFoundException();
}
private function doMatch(string $rawPathinfo, array &$allow = array(), array &$allowSchemes = array()): ?array
{
$allow = $allowSchemes = 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'));
switch ($pathinfo) {
default:
$routes = array(
'/trailing/simple/no-methods/' => array(array('_route' => 'simple_trailing_slash_no_methods'), null, null, null),
'/trailing/simple/get-method/' => array(array('_route' => 'simple_trailing_slash_GET_method'), null, array('GET' => 0), null),
'/trailing/simple/head-method/' => array(array('_route' => 'simple_trailing_slash_HEAD_method'), null, array('HEAD' => 0), null),
'/trailing/simple/post-method/' => array(array('_route' => 'simple_trailing_slash_POST_method'), null, array('POST' => 0), null),
'/not-trailing/simple/no-methods' => array(array('_route' => 'simple_not_trailing_slash_no_methods'), null, null, null),
'/not-trailing/simple/get-method' => array(array('_route' => 'simple_not_trailing_slash_GET_method'), null, array('GET' => 0), null),
'/not-trailing/simple/head-method' => array(array('_route' => 'simple_not_trailing_slash_HEAD_method'), null, array('HEAD' => 0), null),
'/not-trailing/simple/post-method' => array(array('_route' => 'simple_not_trailing_slash_POST_method'), null, array('POST' => 0), null),
);
if (!isset($routes[$pathinfo])) {
break;
}
list($ret, $requiredHost, $requiredMethods, $requiredSchemes) = $routes[$pathinfo];
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
if ($hasRequiredScheme) {
$allow += $requiredMethods;
}
break;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
break;
}
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'));
$matchedPathinfo = $pathinfo;
$regexList = array(
0 => '{^(?'
.'|/trailing/regex/(?'
.'|no\\-methods/([^/]++)/(*:47)'
.'|get\\-method/([^/]++)/(*:75)'
.'|head\\-method/([^/]++)/(*:104)'
.'|post\\-method/([^/]++)/(*:134)'
.')'
.'|/not\\-trailing/regex/(?'
.'|no\\-methods/([^/]++)(*:187)'
.'|get\\-method/([^/]++)(*:215)'
.'|head\\-method/([^/]++)(*:244)'
.'|post\\-method/([^/]++)(*:273)'
.')'
.')$}sD',
);
foreach ($regexList as $offset => $regex) {
while (preg_match($regex, $matchedPathinfo, $matches)) {
switch ($m = (int) $matches['MARK']) {
default:
$routes = array(
47 => array(array('_route' => 'regex_trailing_slash_no_methods'), array('param'), null, null),
75 => array(array('_route' => 'regex_trailing_slash_GET_method'), array('param'), array('GET' => 0), null),
104 => array(array('_route' => 'regex_trailing_slash_HEAD_method'), array('param'), array('HEAD' => 0), null),
134 => array(array('_route' => 'regex_trailing_slash_POST_method'), array('param'), array('POST' => 0), null),
187 => array(array('_route' => 'regex_not_trailing_slash_no_methods'), array('param'), null, null),
215 => array(array('_route' => 'regex_not_trailing_slash_GET_method'), array('param'), array('GET' => 0), null),
244 => array(array('_route' => 'regex_not_trailing_slash_HEAD_method'), array('param'), array('HEAD' => 0), null),
273 => array(array('_route' => 'regex_not_trailing_slash_POST_method'), array('param'), array('POST' => 0), null),
);
list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m];
foreach ($vars as $i => $v) {
if (isset($matches[1 + $i])) {
$ret[$v] = $matches[1 + $i];
}
}
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
if ($hasRequiredScheme) {
$allow += $requiredMethods;
}
break;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
break;
}
return $ret;
}
return $ret;
if (273 === $m) {
break;
}
$regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m));
$offset += strlen($m);
}
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();
return null;
}
}

View File

@@ -0,0 +1,88 @@
<?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 = $allowSchemes = array();
$pathinfo = rawurldecode($rawPathinfo);
$context = $this->context;
$requestMethod = $canonicalMethod = $context->getMethod();
if ('HEAD' === $requestMethod) {
$canonicalMethod = 'GET';
}
$matchedPathinfo = $pathinfo;
$regexList = array(
0 => '{^(?'
.'|/(a)(*:11)'
.')$}sD',
11 => '{^(?'
.'|/(.)(*:22)'
.')$}sDu',
22 => '{^(?'
.'|/(.)(*:33)'
.')$}sD',
);
foreach ($regexList as $offset => $regex) {
while (preg_match($regex, $matchedPathinfo, $matches)) {
switch ($m = (int) $matches['MARK']) {
default:
$routes = array(
11 => array(array('_route' => 'a'), array('a'), null, null),
22 => array(array('_route' => 'b'), array('a'), null, null),
33 => array(array('_route' => 'c'), array('a'), null, null),
);
list($ret, $vars, $requiredMethods, $requiredSchemes) = $routes[$m];
foreach ($vars as $i => $v) {
if (isset($matches[1 + $i])) {
$ret[$v] = $matches[1 + $i];
}
}
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
if ($hasRequiredScheme) {
$allow += $requiredMethods;
}
break;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
break;
}
return $ret;
}
if (33 === $m) {
break;
}
$regex = substr_replace($regex, 'F', $m - $offset, 1 + strlen($m));
$offset += strlen($m);
}
}
if ('/' === $pathinfo && !$allow) {
throw new Symfony\Component\Routing\Exception\NoConfigurationException();
}
throw $allow ? new MethodNotAllowedException(array_keys($allow)) : new ResourceNotFoundException();
}
}

View File

@@ -0,0 +1,53 @@
<?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 = $allowSchemes = array();
$pathinfo = rawurldecode($rawPathinfo);
$context = $this->context;
$requestMethod = $canonicalMethod = $context->getMethod();
$host = strtolower($context->getHost());
if ('HEAD' === $requestMethod) {
$canonicalMethod = 'GET';
}
switch ($pathinfo) {
case '/':
// a
if (preg_match('#^(?P<d>[^\\.]++)\\.e\\.c\\.b\\.a$#sDi', $host, $hostMatches)) {
return $this->mergeDefaults(array('_route' => 'a') + $hostMatches, array());
}
// c
if (preg_match('#^(?P<e>[^\\.]++)\\.e\\.c\\.b\\.a$#sDi', $host, $hostMatches)) {
return $this->mergeDefaults(array('_route' => 'c') + $hostMatches, array());
}
// b
if ('d.c.b.a' === $host) {
return array('_route' => 'b');
}
break;
}
if ('/' === $pathinfo && !$allow) {
throw new Symfony\Component\Routing\Exception\NoConfigurationException();
}
throw $allow ? new MethodNotAllowedException(array_keys($allow)) : new ResourceNotFoundException();
}
}

View File

@@ -0,0 +1,10 @@
<?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="../controller/routing.xml" />
<import resource="../controller/routing.xml" prefix="/api" name-prefix="api_" />
</routes>

View File

@@ -0,0 +1,7 @@
app:
resource: ../controller/routing.yml
api:
resource: ../controller/routing.yml
name_prefix: api_
prefix: /api

View File

@@ -0,0 +1,10 @@
<?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="../controller/routing.xml" prefix="/slash" name-prefix="a_" />
<import resource="../controller/routing.xml" prefix="/no-slash" name-prefix="b_" trailing-slash-on-root="false" />
</routes>

View File

@@ -0,0 +1,10 @@
app:
resource: ../controller/routing.yml
name_prefix: a_
prefix: /slash
api:
resource: ../controller/routing.yml
name_prefix: b_
prefix: /no-slash
trailing_slash_on_root: false

View File

@@ -0,0 +1,13 @@
<?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="localized">
<default key="_controller">MyBundle:Blog:show</default>
<path locale="en">/path</path>
<path locale="fr">/route</path>
</route>
</routes>

View File

@@ -0,0 +1,9 @@
<?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="imported" path="/suffix">
<default key="_controller">MyBundle:Blog:show</default>
</route>
</routes>

View File

@@ -0,0 +1,4 @@
---
imported:
controller: ImportedController::someAction
path: /imported

View File

@@ -0,0 +1,11 @@
<?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="imported">
<default key="_controller">MyBundle:Blog:show</default>
<path locale="en">/suffix</path>
<path locale="fr">/le-suffix</path>
</route>
</routes>

View File

@@ -0,0 +1,6 @@
---
imported:
controller: ImportedController::someAction
path:
nl: /voorbeeld
en: /example

View File

@@ -0,0 +1,5 @@
---
i_need:
defaults:
_controller: DefaultController::defaultAction
resource: ./localized-route.yml

View File

@@ -0,0 +1,10 @@
<?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="./imported-with-locale-but-not-localized.xml">
<prefix locale="fr">/le-prefix</prefix>
<prefix locale="en">/the-prefix</prefix>
</import>
</routes>

View File

@@ -0,0 +1,6 @@
---
i_need:
resource: ./imported-with-locale-but-not-localized.yml
prefix:
nl: /nl
en: /en

View File

@@ -0,0 +1,10 @@
<?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="./imported-with-locale.xml">
<prefix locale="fr">/le-prefix</prefix>
<prefix locale="en">/the-prefix</prefix>
</import>
</routes>

View File

@@ -0,0 +1,6 @@
---
i_need:
resource: ./imported-with-locale.yml
prefix:
nl: /nl
en: /en

View File

@@ -0,0 +1,3 @@
---
i_need:
resource: ./localized-route.yml

View File

@@ -0,0 +1,9 @@
---
home:
path:
nl: /nl
en: /en
not_localized:
controller: HomeController::otherAction
path: /here

View File

@@ -0,0 +1,5 @@
---
importing_with_missing_prefix:
resource: ./localized-route.yml
prefix:
nl: /prefix

View File

@@ -0,0 +1,4 @@
---
not_localized:
controller: string
path: /here

View File

@@ -0,0 +1,7 @@
---
official:
controller: HomeController::someAction
path:
fr.UTF-8: /omelette-au-fromage
pt-PT: /eu-não-sou-espanhol
pt_BR: /churrasco

View File

@@ -0,0 +1,3 @@
---
routename:
controller: Here::here

View File

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

View File

@@ -15,6 +15,13 @@ return function (RoutingConfigurator $routes) {
->prefix('/sub')
->requirements(array('id' => '\d+'));
$routes->import('php_dsl_sub.php')
->namePrefix('z_')
->prefix('/zub');
$routes->import('php_dsl_sub_root.php')
->prefix('/bus', false);
$routes->add('ouf', '/ouf')
->schemes(array('https'))
->methods(array('GET'))

View File

@@ -0,0 +1,17 @@
<?php
namespace Symfony\Component\Routing\Loader\Configurator;
return function (RoutingConfigurator $routes) {
$routes
->collection()
->prefix(array('en' => '/glish'))
->add('foo', '/foo')
->add('bar', array('en' => '/bar'));
$routes
->add('baz', array('en' => '/baz'));
$routes->import('php_dsl_sub_i18n.php')
->prefix(array('fr' => '/ench'));
};

View File

@@ -6,6 +6,7 @@ return function (RoutingConfigurator $routes) {
$add = $routes->collection('c_')
->prefix('pub');
$add('root', '/');
$add('bar', '/bar');
$add->collection('pub_')

View File

@@ -0,0 +1,11 @@
<?php
namespace Symfony\Component\Routing\Loader\Configurator;
return function (RoutingConfigurator $routes) {
$add = $routes->collection('c_')
->prefix('pub');
$add('foo', array('fr' => '/foo'));
$add('bar', array('fr' => '/bar'));
};

View File

@@ -0,0 +1,10 @@
<?php
namespace Symfony\Component\Routing\Loader\Configurator;
return function (RoutingConfigurator $routes) {
$add = $routes->collection('r_');
$add('root', '/');
$add('bar', '/bar/');
};

View File

@@ -0,0 +1,32 @@
<?php
namespace Symfony\Component\Routing\Loader\Configurator;
return new class() {
public function __invoke(RoutingConfigurator $routes)
{
$routes
->collection()
->add('foo', '/foo')
->condition('abc')
->options(array('utf8' => true))
->add('buz', 'zub')
->controller('foo:act');
$routes->import('php_dsl_sub.php')
->prefix('/sub')
->requirements(array('id' => '\d+'));
$routes->import('php_dsl_sub.php')
->namePrefix('z_')
->prefix('/zub');
$routes->import('php_dsl_sub_root.php')
->prefix('/bus', false);
$routes->add('ouf', '/ouf')
->schemes(array('https'))
->methods(array('GET'))
->defaults(array('id' => 0));
}
};

View File

@@ -84,12 +84,35 @@ class PhpGeneratorDumperTest extends TestCase
$this->assertEquals('/app.php/testing2', $relativeUrlWithoutParameter);
}
public function testDumpWithLocalizedRoutes()
{
$this->routeCollection->add('test.en', (new Route('/testing/is/fun'))->setDefault('_locale', 'en')->setDefault('_canonical_route', 'test'));
$this->routeCollection->add('test.nl', (new Route('/testen/is/leuk'))->setDefault('_locale', 'nl')->setDefault('_canonical_route', 'test'));
$code = $this->generatorDumper->dump(array(
'class' => 'LocalizedProjectUrlGenerator',
));
file_put_contents($this->testTmpFilepath, $code);
include $this->testTmpFilepath;
$context = new RequestContext('/app.php');
$projectUrlGenerator = new \LocalizedProjectUrlGenerator($context, null, 'en');
$urlWithDefaultLocale = $projectUrlGenerator->generate('test');
$urlWithSpecifiedLocale = $projectUrlGenerator->generate('test', array('_locale' => 'nl'));
$context->setParameter('_locale', 'en');
$urlWithEnglishContext = $projectUrlGenerator->generate('test');
$context->setParameter('_locale', 'nl');
$urlWithDutchContext = $projectUrlGenerator->generate('test');
$this->assertEquals('/app.php/testing/is/fun', $urlWithDefaultLocale);
$this->assertEquals('/app.php/testen/is/leuk', $urlWithSpecifiedLocale);
$this->assertEquals('/app.php/testing/is/fun', $urlWithEnglishContext);
$this->assertEquals('/app.php/testen/is/leuk', $urlWithDutchContext);
}
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));

View File

@@ -11,35 +11,46 @@
namespace Symfony\Component\Routing\Tests\Loader;
use Symfony\Component\Routing\Annotation\Route;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;
use Symfony\Component\Routing\Annotation\Route as RouteAnnotation;
use Symfony\Component\Routing\Loader\AnnotationClassLoader;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures\AbstractClassController;
use Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures\ActionPathController;
use Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures\DefaultValueController;
use Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures\ExplicitLocalizedActionPathController;
use Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures\InvokableController;
use Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures\InvokableLocalizedController;
use Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures\LocalizedActionPathController;
use Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures\LocalizedMethodActionControllers;
use Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures\LocalizedPrefixLocalizedActionController;
use Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures\LocalizedPrefixMissingLocaleActionController;
use Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures\LocalizedPrefixMissingRouteLocaleActionController;
use Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures\LocalizedPrefixWithRouteWithoutLocale;
use Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures\MethodActionControllers;
use Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures\MissingRouteNameController;
use Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures\NothingButNameController;
use Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures\PrefixedActionLocalizedRouteController;
use Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures\PrefixedActionPathController;
use Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures\RouteWithPrefixController;
class AnnotationClassLoaderTest extends AbstractAnnotationLoaderTest
{
protected $loader;
private $reader;
/**
* @var AnnotationClassLoader
*/
private $loader;
protected function setUp()
{
parent::setUp();
$this->reader = $this->getReader();
$this->loader = $this->getClassLoader($this->reader);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testLoadMissingClass()
{
$this->loader->load('MissingClass');
}
/**
* @expectedException \InvalidArgumentException
*/
public function testLoadAbstractClass()
{
$this->loader->load('Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\AbstractClass');
$reader = new AnnotationReader();
$this->loader = new class($reader) extends AnnotationClassLoader {
protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, $annot)
{
}
};
AnnotationRegistry::registerLoader('class_exists');
}
/**
@@ -69,144 +80,92 @@ class AnnotationClassLoaderTest extends AbstractAnnotationLoaderTest
$this->assertFalse($this->loader->supports('class', 'foo'), '->supports() checks the resource type if specified');
}
public function getLoadTests()
public function testSimplePathRoute()
{
return array(
array(
'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass',
array('name' => 'route1', 'path' => '/path'),
array('arg2' => 'defaultValue2', 'arg3' => 'defaultValue3'),
),
array(
'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass',
array('defaults' => array('arg2' => 'foo'), 'requirements' => array('arg3' => '\w+')),
array('arg2' => 'defaultValue2', 'arg3' => 'defaultValue3'),
),
array(
'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass',
array('options' => array('foo' => 'bar')),
array('arg2' => 'defaultValue2', 'arg3' => 'defaultValue3'),
),
array(
'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass',
array('schemes' => array('https'), 'methods' => array('GET')),
array('arg2' => 'defaultValue2', 'arg3' => 'defaultValue3'),
),
array(
'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass',
array('condition' => 'context.getMethod() == "GET"'),
array('arg2' => 'defaultValue2', 'arg3' => 'defaultValue3'),
),
);
$routes = $this->loader->load(ActionPathController::class);
$this->assertCount(1, $routes);
$this->assertEquals('/path', $routes->get('action')->getPath());
}
/**
* @dataProvider getLoadTests
*/
public function testLoad($className, $routeData = array(), $methodArgs = array())
public function testInvokableControllerLoader()
{
$routeData = array_replace(array(
'name' => 'route',
'path' => '/',
'requirements' => array(),
'options' => array(),
'defaults' => array(),
'schemes' => array(),
'methods' => array(),
'condition' => '',
), $routeData);
$this->reader
->expects($this->once())
->method('getMethodAnnotations')
->will($this->returnValue(array($this->getAnnotatedRoute($routeData))))
;
$routeCollection = $this->loader->load($className);
$route = $routeCollection->get($routeData['name']);
$this->assertSame($routeData['path'], $route->getPath(), '->load preserves path annotation');
$this->assertCount(
\count($routeData['requirements']),
array_intersect_assoc($routeData['requirements'], $route->getRequirements()),
'->load preserves requirements annotation'
);
$this->assertCount(
\count($routeData['options']),
array_intersect_assoc($routeData['options'], $route->getOptions()),
'->load preserves options annotation'
);
$this->assertCount(
\count($routeData['defaults']),
$route->getDefaults(),
'->load preserves defaults annotation'
);
$this->assertEquals($routeData['schemes'], $route->getSchemes(), '->load preserves schemes annotation');
$this->assertEquals($routeData['methods'], $route->getMethods(), '->load preserves methods annotation');
$this->assertSame($routeData['condition'], $route->getCondition(), '->load preserves condition annotation');
$routes = $this->loader->load(InvokableController::class);
$this->assertCount(1, $routes);
$this->assertEquals('/here', $routes->get('lol')->getPath());
}
public function testClassRouteLoad()
public function testInvokableLocalizedControllerLoading()
{
$classRouteData = array(
'name' => 'prefix_',
'path' => '/prefix',
'schemes' => array('https'),
'methods' => array('GET'),
);
$methodRouteData = array(
'name' => 'route1',
'path' => '/path',
'schemes' => array('http'),
'methods' => array('POST', 'PUT'),
);
$this->reader
->expects($this->once())
->method('getClassAnnotation')
->will($this->returnValue($this->getAnnotatedRoute($classRouteData)))
;
$this->reader
->expects($this->once())
->method('getMethodAnnotations')
->will($this->returnValue(array($this->getAnnotatedRoute($methodRouteData))))
;
$routeCollection = $this->loader->load('Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass');
$route = $routeCollection->get($classRouteData['name'].$methodRouteData['name']);
$this->assertSame($classRouteData['path'].$methodRouteData['path'], $route->getPath(), '->load concatenates class and method route path');
$this->assertEquals(array_merge($classRouteData['schemes'], $methodRouteData['schemes']), $route->getSchemes(), '->load merges class and method route schemes');
$this->assertEquals(array_merge($classRouteData['methods'], $methodRouteData['methods']), $route->getMethods(), '->load merges class and method route methods');
$routes = $this->loader->load(InvokableLocalizedController::class);
$this->assertCount(2, $routes);
$this->assertEquals('/here', $routes->get('action.en')->getPath());
$this->assertEquals('/hier', $routes->get('action.nl')->getPath());
}
public function testInvokableClassRouteLoad()
public function testLocalizedPathRoutes()
{
$classRouteData = array(
'name' => 'route1',
'path' => '/',
'schemes' => array('https'),
'methods' => array('GET'),
);
$routes = $this->loader->load(LocalizedActionPathController::class);
$this->assertCount(2, $routes);
$this->assertEquals('/path', $routes->get('action.en')->getPath());
$this->assertEquals('/pad', $routes->get('action.nl')->getPath());
}
$this->reader
->expects($this->exactly(1))
->method('getClassAnnotations')
->will($this->returnValue(array($this->getAnnotatedRoute($classRouteData))))
;
$this->reader
->expects($this->once())
->method('getMethodAnnotations')
->will($this->returnValue(array()))
;
public function testLocalizedPathRoutesWithExplicitPathPropety()
{
$routes = $this->loader->load(ExplicitLocalizedActionPathController::class);
$this->assertCount(2, $routes);
$this->assertEquals('/path', $routes->get('action.en')->getPath());
$this->assertEquals('/pad', $routes->get('action.nl')->getPath());
}
$routeCollection = $this->loader->load('Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BazClass');
$route = $routeCollection->get($classRouteData['name']);
public function testDefaultValuesForMethods()
{
$routes = $this->loader->load(DefaultValueController::class);
$this->assertCount(1, $routes);
$this->assertEquals('/{default}/path', $routes->get('action')->getPath());
$this->assertEquals('value', $routes->get('action')->getDefault('default'));
}
$this->assertSame($classRouteData['path'], $route->getPath(), '->load preserves class route path');
$this->assertEquals($classRouteData['schemes'], $route->getSchemes(), '->load preserves class route schemes');
$this->assertEquals($classRouteData['methods'], $route->getMethods(), '->load preserves class route methods');
public function testMethodActionControllers()
{
$routes = $this->loader->load(MethodActionControllers::class);
$this->assertCount(2, $routes);
$this->assertEquals('/the/path', $routes->get('put')->getPath());
$this->assertEquals('/the/path', $routes->get('post')->getPath());
}
public function testLocalizedMethodActionControllers()
{
$routes = $this->loader->load(LocalizedMethodActionControllers::class);
$this->assertCount(4, $routes);
$this->assertEquals('/the/path', $routes->get('put.en')->getPath());
$this->assertEquals('/the/path', $routes->get('post.en')->getPath());
}
public function testRouteWithPathWithPrefix()
{
$routes = $this->loader->load(PrefixedActionPathController::class);
$this->assertCount(1, $routes);
$route = $routes->get('action');
$this->assertEquals('/prefix/path', $route->getPath());
$this->assertEquals('lol=fun', $route->getCondition());
$this->assertEquals('frankdejonge.nl', $route->getHost());
}
public function testLocalizedRouteWithPathWithPrefix()
{
$routes = $this->loader->load(PrefixedActionLocalizedRouteController::class);
$this->assertCount(2, $routes);
$this->assertEquals('/prefix/path', $routes->get('action.en')->getPath());
$this->assertEquals('/prefix/pad', $routes->get('action.nl')->getPath());
}
public function testLocalizedPrefixLocalizedRoute()
{
$routes = $this->loader->load(LocalizedPrefixLocalizedActionController::class);
$this->assertCount(2, $routes);
$this->assertEquals('/nl/actie', $routes->get('action.nl')->getPath());
$this->assertEquals('/en/action', $routes->get('action.en')->getPath());
}
public function testInvokableClassMultipleRouteLoad()
@@ -225,18 +184,24 @@ class AnnotationClassLoaderTest extends AbstractAnnotationLoaderTest
'methods' => array('GET'),
);
$this->reader
$reader = $this->getReader();
$reader
->expects($this->exactly(1))
->method('getClassAnnotations')
->will($this->returnValue(array($this->getAnnotatedRoute($classRouteData1), $this->getAnnotatedRoute($classRouteData2))))
->will($this->returnValue(array(new RouteAnnotation($classRouteData1), new RouteAnnotation($classRouteData2))))
;
$this->reader
$reader
->expects($this->once())
->method('getMethodAnnotations')
->will($this->returnValue(array()))
;
$loader = new class($reader) extends AnnotationClassLoader {
protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, $annot)
{
}
};
$routeCollection = $this->loader->load('Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BazClass');
$routeCollection = $loader->load('Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BazClass');
$route = $routeCollection->get($classRouteData1['name']);
$this->assertSame($classRouteData1['path'], $route->getPath(), '->load preserves class route path');
@@ -250,47 +215,56 @@ class AnnotationClassLoaderTest extends AbstractAnnotationLoaderTest
$this->assertEquals($classRouteData2['methods'], $route->getMethods(), '->load preserves class route methods');
}
public function testInvokableClassWithMethodRouteLoad()
public function testMissingPrefixLocale()
{
$classRouteData = array(
'name' => 'route1',
'path' => '/prefix',
'schemes' => array('https'),
'methods' => array('GET'),
);
$methodRouteData = array(
'name' => 'route2',
'path' => '/path',
'schemes' => array('http'),
'methods' => array('POST', 'PUT'),
);
$this->reader
->expects($this->once())
->method('getClassAnnotation')
->will($this->returnValue($this->getAnnotatedRoute($classRouteData)))
;
$this->reader
->expects($this->once())
->method('getMethodAnnotations')
->will($this->returnValue(array($this->getAnnotatedRoute($methodRouteData))))
;
$routeCollection = $this->loader->load('Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BazClass');
$route = $routeCollection->get($classRouteData['name']);
$this->assertNull($route, '->load ignores class route');
$route = $routeCollection->get($classRouteData['name'].$methodRouteData['name']);
$this->assertSame($classRouteData['path'].$methodRouteData['path'], $route->getPath(), '->load concatenates class and method route path');
$this->assertEquals(array_merge($classRouteData['schemes'], $methodRouteData['schemes']), $route->getSchemes(), '->load merges class and method route schemes');
$this->assertEquals(array_merge($classRouteData['methods'], $methodRouteData['methods']), $route->getMethods(), '->load merges class and method route methods');
$this->expectException(\LogicException::class);
$this->loader->load(LocalizedPrefixMissingLocaleActionController::class);
}
private function getAnnotatedRoute($data)
public function testMissingRouteLocale()
{
return new Route($data);
$this->expectException(\LogicException::class);
$this->loader->load(LocalizedPrefixMissingRouteLocaleActionController::class);
}
public function testRouteWithoutName()
{
$routes = $this->loader->load(MissingRouteNameController::class)->all();
$this->assertCount(1, $routes);
$this->assertEquals('/path', reset($routes)->getPath());
}
public function testNothingButName()
{
$routes = $this->loader->load(NothingButNameController::class)->all();
$this->assertCount(1, $routes);
$this->assertEquals('/', reset($routes)->getPath());
}
public function testNonExistingClass()
{
$this->expectException(\LogicException::class);
$this->loader->load('ClassThatDoesNotExist');
}
public function testLoadingAbstractClass()
{
$this->expectException(\LogicException::class);
$this->loader->load(AbstractClassController::class);
}
public function testLocalizedPrefixWithoutRouteLocale()
{
$routes = $this->loader->load(LocalizedPrefixWithRouteWithoutLocale::class);
$this->assertCount(2, $routes);
$this->assertEquals('/en/suffix', $routes->get('action.en')->getPath());
$this->assertEquals('/nl/suffix', $routes->get('action.nl')->getPath());
}
public function testLoadingRouteWithPrefix()
{
$routes = $this->loader->load(RouteWithPrefixController::class);
$this->assertCount(1, $routes);
$this->assertEquals('/prefix/path', $routes->get('action')->getPath());
}
}

View File

@@ -35,9 +35,6 @@ class AnnotationFileLoaderTest extends AbstractAnnotationLoaderTest
$this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses/FooClass.php');
}
/**
* @requires PHP 5.4
*/
public function testLoadTraitWithClassConstant()
{
$this->reader->expects($this->never())->method('getClassAnnotation');
@@ -54,9 +51,6 @@ class AnnotationFileLoaderTest extends AbstractAnnotationLoaderTest
$this->loader->load(__DIR__.'/../Fixtures/OtherAnnotatedClasses/NoStartTagClass.php');
}
/**
* @requires PHP 5.6
*/
public function testLoadVariadic()
{
$route = new Route(array('path' => '/path/to/{id}'));

View File

@@ -0,0 +1,17 @@
<?php
namespace Symfony\Component\Routing\Tests\Loader;
use Symfony\Component\Config\FileLocatorInterface;
class FileLocatorStub implements FileLocatorInterface
{
public function locate($name, $currentPath = null, $first = true)
{
if (0 === strpos($name, 'http')) {
return $name;
}
return rtrim($currentPath, '/').'/'.$name;
}
}

View File

@@ -18,7 +18,11 @@ use Symfony\Component\Routing\RouteCollection;
class ObjectRouteLoaderTest extends TestCase
{
public function testLoadCallsServiceAndReturnsCollection()
/**
* @group legacy
* @expectedDeprecation Referencing service route loaders with a single colon is deprecated since Symfony 4.1. Use my_route_provider_service::loadRoutes instead.
*/
public function testLoadCallsServiceAndReturnsCollectionWithLegacyNotation()
{
$loader = new ObjectRouteLoaderForTest();
@@ -40,6 +44,28 @@ class ObjectRouteLoaderTest extends TestCase
$this->assertNotEmpty($actualRoutes->getResources());
}
public function testLoadCallsServiceAndReturnsCollection()
{
$loader = new ObjectRouteLoaderForTest();
// create a basic collection that will be returned
$collection = new RouteCollection();
$collection->add('foo', new Route('/foo'));
$loader->loaderMap = array(
'my_route_provider_service' => new RouteService($collection),
);
$actualRoutes = $loader->load(
'my_route_provider_service::loadRoutes',
'service'
);
$this->assertSame($collection, $actualRoutes);
// the service file should be listed as a resource
$this->assertNotEmpty($actualRoutes->getResources());
}
/**
* @expectedException \InvalidArgumentException
* @dataProvider getBadResourceStrings
@@ -54,7 +80,6 @@ class ObjectRouteLoaderTest extends TestCase
{
return array(
array('Foo'),
array('Bar::baz'),
array('Foo:Bar:baz'),
);
}
@@ -66,7 +91,7 @@ class ObjectRouteLoaderTest extends TestCase
{
$loader = new ObjectRouteLoaderForTest();
$loader->loaderMap = array('my_service' => 'NOT_AN_OBJECT');
$loader->load('my_service:method');
$loader->load('my_service::method');
}
/**
@@ -76,7 +101,7 @@ class ObjectRouteLoaderTest extends TestCase
{
$loader = new ObjectRouteLoaderForTest();
$loader->loaderMap = array('my_service' => new \stdClass());
$loader->load('my_service:method');
$loader->load('my_service::method');
}
/**
@@ -93,7 +118,7 @@ class ObjectRouteLoaderTest extends TestCase
$loader = new ObjectRouteLoaderForTest();
$loader->loaderMap = array('my_service' => $service);
$loader->load('my_service:loadRoutes');
$loader->load('my_service::loadRoutes');
}
}

View File

@@ -88,7 +88,8 @@ class PhpFileLoaderTest extends TestCase
{
$locator = new FileLocator(array(__DIR__.'/../Fixtures'));
$loader = new PhpFileLoader($locator);
$routeCollection = $loader->load('php_dsl.php');
$routeCollectionClosure = $loader->load('php_dsl.php');
$routeCollectionObject = $loader->load('php_object_dsl.php');
$expectedCollection = new RouteCollection();
@@ -99,6 +100,9 @@ class PhpFileLoaderTest extends TestCase
$expectedCollection->add('buz', (new Route('/zub'))
->setDefaults(array('_controller' => 'foo:act'))
);
$expectedCollection->add('c_root', (new Route('/sub/pub/'))
->setRequirements(array('id' => '\d+'))
);
$expectedCollection->add('c_bar', (new Route('/sub/pub/bar'))
->setRequirements(array('id' => '\d+'))
);
@@ -106,6 +110,11 @@ class PhpFileLoaderTest extends TestCase
->setHost('host')
->setRequirements(array('id' => '\d+'))
);
$expectedCollection->add('z_c_root', new Route('/zub/pub/'));
$expectedCollection->add('z_c_bar', new Route('/zub/pub/bar'));
$expectedCollection->add('z_c_pub_buz', (new Route('/zub/pub/buz'))->setHost('host'));
$expectedCollection->add('r_root', new Route('/bus'));
$expectedCollection->add('r_bar', new Route('/bus/bar/'));
$expectedCollection->add('ouf', (new Route('/ouf'))
->setSchemes(array('https'))
->setMethods(array('GET'))
@@ -113,9 +122,16 @@ class PhpFileLoaderTest extends TestCase
);
$expectedCollection->addResource(new FileResource(realpath(__DIR__.'/../Fixtures/php_dsl_sub.php')));
$expectedCollection->addResource(new FileResource(realpath(__DIR__.'/../Fixtures/php_dsl.php')));
$expectedCollection->addResource(new FileResource(realpath(__DIR__.'/../Fixtures/php_dsl_sub_root.php')));
$this->assertEquals($expectedCollection, $routeCollection);
$expectedCollectionClosure = $expectedCollection;
$expectedCollectionObject = clone $expectedCollection;
$expectedCollectionClosure->addResource(new FileResource(realpath(__DIR__.'/../Fixtures/php_dsl.php')));
$expectedCollectionObject->addResource(new FileResource(realpath(__DIR__.'/../Fixtures/php_object_dsl.php')));
$this->assertEquals($expectedCollectionClosure, $routeCollectionClosure);
$this->assertEquals($expectedCollectionObject, $routeCollectionObject);
}
public function testRoutingConfiguratorCanImportGlobPatterns()
@@ -130,4 +146,24 @@ class PhpFileLoaderTest extends TestCase
$route = $routeCollection->get('baz_route');
$this->assertSame('AppBundle:Baz:view', $route->getDefault('_controller'));
}
public function testRoutingI18nConfigurator()
{
$locator = new FileLocator(array(__DIR__.'/../Fixtures'));
$loader = new PhpFileLoader($locator);
$routeCollection = $loader->load('php_dsl_i18n.php');
$expectedCollection = new RouteCollection();
$expectedCollection->add('foo.en', (new Route('/glish/foo'))->setDefaults(array('_locale' => 'en', '_canonical_route' => 'foo')));
$expectedCollection->add('bar.en', (new Route('/glish/bar'))->setDefaults(array('_locale' => 'en', '_canonical_route' => 'bar')));
$expectedCollection->add('baz.en', (new Route('/baz'))->setDefaults(array('_locale' => 'en', '_canonical_route' => 'baz')));
$expectedCollection->add('c_foo.fr', (new Route('/ench/pub/foo'))->setDefaults(array('_locale' => 'fr', '_canonical_route' => 'c_foo')));
$expectedCollection->add('c_bar.fr', (new Route('/ench/pub/bar'))->setDefaults(array('_locale' => 'fr', '_canonical_route' => 'c_bar')));
$expectedCollection->addResource(new FileResource(realpath(__DIR__.'/../Fixtures/php_dsl_sub_i18n.php')));
$expectedCollection->addResource(new FileResource(realpath(__DIR__.'/../Fixtures/php_dsl_i18n.php')));
$this->assertEquals($expectedCollection, $routeCollection);
}
}

View File

@@ -83,6 +83,45 @@ class XmlFileLoaderTest extends TestCase
}
}
public function testLoadLocalized()
{
$loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
$routeCollection = $loader->load('localized.xml');
$routes = $routeCollection->all();
$this->assertCount(2, $routes, 'Two routes are loaded');
$this->assertContainsOnly('Symfony\Component\Routing\Route', $routes);
$this->assertEquals('/route', $routeCollection->get('localized.fr')->getPath());
$this->assertEquals('/path', $routeCollection->get('localized.en')->getPath());
}
public function testLocalizedImports()
{
$loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/localized')));
$routeCollection = $loader->load('importer-with-locale.xml');
$routes = $routeCollection->all();
$this->assertCount(2, $routes, 'Two routes are loaded');
$this->assertContainsOnly('Symfony\Component\Routing\Route', $routes);
$this->assertEquals('/le-prefix/le-suffix', $routeCollection->get('imported.fr')->getPath());
$this->assertEquals('/the-prefix/suffix', $routeCollection->get('imported.en')->getPath());
}
public function testLocalizedImportsOfNotLocalizedRoutes()
{
$loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/localized')));
$routeCollection = $loader->load('importer-with-locale-imports-non-localized-route.xml');
$routes = $routeCollection->all();
$this->assertCount(2, $routes, 'Two routes are loaded');
$this->assertContainsOnly('Symfony\Component\Routing\Route', $routes);
$this->assertEquals('/le-prefix/suffix', $routeCollection->get('imported.fr')->getPath());
$this->assertEquals('/the-prefix/suffix', $routeCollection->get('imported.en')->getPath());
}
/**
* @expectedException \InvalidArgumentException
* @dataProvider getPathsToInvalidFiles
@@ -382,4 +421,24 @@ class XmlFileLoaderTest extends TestCase
$route = $routeCollection->get('baz_route');
$this->assertSame('AppBundle:Baz:view', $route->getDefault('_controller'));
}
public function testImportRouteWithNamePrefix()
{
$loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/import_with_name_prefix')));
$routeCollection = $loader->load('routing.xml');
$this->assertNotNull($routeCollection->get('app_blog'));
$this->assertEquals('/blog', $routeCollection->get('app_blog')->getPath());
$this->assertNotNull($routeCollection->get('api_app_blog'));
$this->assertEquals('/api/blog', $routeCollection->get('api_app_blog')->getPath());
}
public function testImportRouteWithNoTrailingSlash()
{
$loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/import_with_no_trailing_slash')));
$routeCollection = $loader->load('routing.xml');
$this->assertEquals('/slash/', $routeCollection->get('a_app_homepage')->getPath());
$this->assertEquals('/no-slash', $routeCollection->get('b_app_homepage')->getPath());
}
}

View File

@@ -203,4 +203,105 @@ class YamlFileLoaderTest extends TestCase
$route = $routeCollection->get('baz_route');
$this->assertSame('AppBundle:Baz:view', $route->getDefault('_controller'));
}
public function testImportRouteWithNamePrefix()
{
$loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/import_with_name_prefix')));
$routeCollection = $loader->load('routing.yml');
$this->assertNotNull($routeCollection->get('app_blog'));
$this->assertEquals('/blog', $routeCollection->get('app_blog')->getPath());
$this->assertNotNull($routeCollection->get('api_app_blog'));
$this->assertEquals('/api/blog', $routeCollection->get('api_app_blog')->getPath());
}
public function testRemoteSourcesAreNotAccepted()
{
$loader = new YamlFileLoader(new FileLocatorStub());
$this->expectException(\InvalidArgumentException::class);
$loader->load('http://remote.com/here.yml');
}
public function testLoadingLocalizedRoute()
{
$loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/localized')));
$routes = $loader->load('localized-route.yml');
$this->assertCount(3, $routes);
}
public function testImportingRoutesFromDefinition()
{
$loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/localized')));
$routes = $loader->load('importing-localized-route.yml');
$this->assertCount(3, $routes);
$this->assertEquals('/nl', $routes->get('home.nl')->getPath());
$this->assertEquals('/en', $routes->get('home.en')->getPath());
$this->assertEquals('/here', $routes->get('not_localized')->getPath());
}
public function testImportingRoutesWithLocales()
{
$loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/localized')));
$routes = $loader->load('importer-with-locale.yml');
$this->assertCount(2, $routes);
$this->assertEquals('/nl/voorbeeld', $routes->get('imported.nl')->getPath());
$this->assertEquals('/en/example', $routes->get('imported.en')->getPath());
}
public function testImportingNonLocalizedRoutesWithLocales()
{
$loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/localized')));
$routes = $loader->load('importer-with-locale-imports-non-localized-route.yml');
$this->assertCount(2, $routes);
$this->assertEquals('/nl/imported', $routes->get('imported.nl')->getPath());
$this->assertEquals('/en/imported', $routes->get('imported.en')->getPath());
}
public function testImportingRoutesWithOfficialLocales()
{
$loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/localized')));
$routes = $loader->load('officially_formatted_locales.yml');
$this->assertCount(3, $routes);
$this->assertEquals('/omelette-au-fromage', $routes->get('official.fr.UTF-8')->getPath());
$this->assertEquals('/eu-não-sou-espanhol', $routes->get('official.pt-PT')->getPath());
$this->assertEquals('/churrasco', $routes->get('official.pt_BR')->getPath());
}
public function testImportingRoutesFromDefinitionMissingLocalePrefix()
{
$loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/localized')));
$this->expectException(\InvalidArgumentException::class);
$loader->load('missing-locale-in-importer.yml');
}
public function testImportingRouteWithoutPathOrLocales()
{
$loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/localized')));
$this->expectException(\InvalidArgumentException::class);
$loader->load('route-without-path-or-locales.yml');
}
public function testImportingWithControllerDefault()
{
$loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/localized')));
$routes = $loader->load('importer-with-controller-default.yml');
$this->assertCount(3, $routes);
$this->assertEquals('DefaultController::defaultAction', $routes->get('home.en')->getDefault('_controller'));
$this->assertEquals('DefaultController::defaultAction', $routes->get('home.nl')->getDefault('_controller'));
$this->assertEquals('DefaultController::defaultAction', $routes->get('not_localized')->getDefault('_controller'));
}
public function testImportRouteWithNoTrailingSlash()
{
$loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/import_with_no_trailing_slash')));
$routeCollection = $loader->load('routing.yml');
$this->assertEquals('/slash/', $routeCollection->get('a_app_homepage')->getPath());
$this->assertEquals('/no-slash', $routeCollection->get('b_app_homepage')->getPath());
}
}

View File

@@ -17,24 +17,6 @@ use Symfony\Component\Routing\RouteCollection;
class DumpedUrlMatcherTest extends UrlMatcherTest
{
/**
* @expectedException \LogicException
* @expectedExceptionMessage The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface.
*/
public function testSchemeRequirement()
{
parent::testSchemeRequirement();
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface.
*/
public function testSchemeAndMethodMismatch()
{
parent::testSchemeRequirement();
}
protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null)
{
static $i = 0;

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