+ * @author Nicolas Grekas
+ */
+class StaticUrlGeneratorDumper extends GeneratorDumper
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function dump(array $options = array())
+ {
+ return <<generateDeclaredRoutes()}
+);
+
+EOF;
+ }
+
+ /**
+ * Generates PHP code representing an array of defined routes
+ * together with the routes properties (e.g. requirements).
+ */
+ private function generateDeclaredRoutes(): string
+ {
+ $routes = '';
+ foreach ($this->getRoutes()->all() as $name => $route) {
+ $compiledRoute = $route->compile();
+
+ $properties = array();
+ $properties[] = $compiledRoute->getVariables();
+ $properties[] = $route->getDefaults();
+ $properties[] = $route->getRequirements();
+ $properties[] = $compiledRoute->getTokens();
+ $properties[] = $compiledRoute->getHostTokens();
+ $properties[] = $route->getSchemes();
+
+ $routes .= sprintf("\n '%s' => %s,", $name, StaticUrlMatcherDumper::export($properties));
+ }
+
+ return $routes;
+ }
+}
diff --git a/src/Symfony/Component/Routing/Generator/StaticUrlGenerator.php b/src/Symfony/Component/Routing/Generator/StaticUrlGenerator.php
new file mode 100644
index 0000000000000..7367898da8838
--- /dev/null
+++ b/src/Symfony/Component/Routing/Generator/StaticUrlGenerator.php
@@ -0,0 +1,39 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Generator;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Routing\Exception\RouteNotFoundException;
+use Symfony\Component\Routing\RequestContext;
+
+class StaticUrlGenerator extends UrlGenerator
+{
+ private $dumpedRoutes = array();
+
+ public function __construct(array $dumpedRoutes, RequestContext $context, LoggerInterface $logger = null)
+ {
+ $this->dumpedRoutes = $dumpedRoutes;
+ $this->context = $context;
+ $this->logger = $logger;
+ }
+
+ public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH)
+ {
+ if (!isset($this->dumpedRoutes[$name])) {
+ throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name));
+ }
+
+ list($variables, $defaults, $requirements, $tokens, $hostTokens, $requiredSchemes) = $this->dumpedRoutes[$name];
+
+ return $this->doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, $requiredSchemes);
+ }
+}
diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php
index acb9eddb34de8..cb89ae5eaa6a5 100644
--- a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php
+++ b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php
@@ -11,6 +11,8 @@
namespace Symfony\Component\Routing\Matcher\Dumper;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.1 and will be removed in 5.0. Use StaticUrlMatcherDumper instead.', PhpMatcherDumper::class), E_USER_DEPRECATED);
+
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
@@ -22,6 +24,8 @@
* @author Fabien Potencier
* @author Tobias Schultze
* @author Arnaud Le Blanc
+ *
+ * @deprecated since Symfony 4.1, to be removed in 5.0. Use StaticUrlMatcherDumper instead.
*/
class PhpMatcherDumper extends MatcherDumper
{
diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/StaticUrlMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/StaticUrlMatcherDumper.php
new file mode 100644
index 0000000000000..1dfd08645c086
--- /dev/null
+++ b/src/Symfony/Component/Routing/Matcher/Dumper/StaticUrlMatcherDumper.php
@@ -0,0 +1,296 @@
+
+ *
+ * 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;
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
+
+/**
+ * StaticUrlMatcherDumper creates a PHP array to be used with StaticUrlMatcher.
+ *
+ * @author Fabien Potencier
+ * @author Tobias Schultze
+ * @author Arnaud Le Blanc
+ * @author Nicolas Grekas
+ */
+class StaticUrlMatcherDumper extends MatcherDumper
+{
+ private $expressionLanguage;
+ private $expressions = array();
+ private $expressionsIndex = array();
+
+ /**
+ * @var ExpressionFunctionProviderInterface[]
+ */
+ private $expressionLanguageProviders = array();
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dump(array $options = array())
+ {
+ list($routes, $expressions) = $this->compileRoutes($this->getRoutes());
+
+ return <<exportRoutes($routes)},
+ {$this->exportExpressions($expressions)},
+);
+
+EOF;
+ }
+
+ public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider)
+ {
+ $this->expressionLanguageProviders[] = $provider;
+ }
+
+ private function compileRoutes(RouteCollection $routes): array
+ {
+ try {
+ $groups = $this->groupRoutesByHostRegex($routes);
+ $dumpedRoutes = array();
+
+ foreach ($groups as $collection) {
+ $tree = $this->buildStaticPrefixCollection($collection);
+ if (!$tree->getItems()) {
+ continue;
+ }
+ $dumpedRoutes[] = array(
+ $collection->getAttribute('host_regex'),
+ $this->compileStaticPrefixRoutes($tree),
+ );
+ }
+
+ return array($dumpedRoutes, $this->expressions);
+ } finally {
+ $this->expressions = $this->expressionsIndex = array();
+ }
+ }
+
+ private function buildStaticPrefixCollection(DumperCollection $collection): StaticPrefixCollection
+ {
+ $prefixCollection = new StaticPrefixCollection();
+
+ foreach ($collection as $dumperRoute) {
+ $prefix = $dumperRoute->getRoute()->compile()->getStaticPrefix();
+ $prefixCollection->addRoute($prefix, $dumperRoute);
+ }
+
+ $prefixCollection->optimizeGroups();
+
+ return $prefixCollection;
+ }
+
+ private function compileStaticPrefixRoutes(StaticPrefixCollection $collection): array
+ {
+ $prefix = $collection->getPrefix();
+ $dumpedRoutes = array(
+ !empty($prefix) && '/' !== $prefix ? $prefix : null,
+ );
+
+ foreach ($collection->getItems() as $route) {
+ if (!$route instanceof StaticPrefixCollection) {
+ $dumpedRoutes[] = $this->compileRoute($route[1]->getRoute(), $route[1]->getName(), $prefix);
+ } elseif ($route->getItems()) {
+ $dumpedRoutes[] = $this->compileStaticPrefixRoutes($route);
+ }
+ }
+
+ return $dumpedRoutes;
+ }
+
+ /**
+ * Compiles a single Route to PHP code used to match it against the path info.
+ */
+ private function compileRoute(Route $route, string $name, string $parentPrefix = null): array
+ {
+ $dumpedRoute = array();
+ $compiledRoute = $route->compile();
+ $conditions = array();
+ $methods = array_values($route->getMethods());
+ $schemes = array_values($route->getSchemes());
+
+ $supportsTrailingSlash = !$methods || in_array('HEAD', $methods) || in_array('GET', $methods);
+ $regex = $compiledRoute->getRegex();
+
+ if (!count($compiledRoute->getPathVariables()) && false !== preg_match('#^(.)\^(?P.*?)\$\1#'.('u' === $regex[-1] ? 'u' : ''), $regex, $m)) {
+ if ($supportsTrailingSlash && '/' === $m['url'][-1]) {
+ $conditions[] = 'trim';
+ $conditions[] = rtrim(str_replace('\\', '', $m['url']), '/');
+ } else {
+ $conditions[] = 'path';
+ $conditions[] = str_replace('\\', '', $m['url']);
+ }
+ } else {
+ if ($start = $compiledRoute->getStaticPrefix() && $compiledRoute->getStaticPrefix() !== $parentPrefix) {
+ $conditions[] = 'start';
+ } else {
+ $conditions[] = 'match';
+ }
+
+ $conditions[] = ($supportsTrailingSlash && $pos = strpos($regex, '/$')) ? substr_replace($regex, '(?P<>/?)$', $pos, 2) : $regex;
+
+ if ($start) {
+ $conditions[] = $compiledRoute->getStaticPrefix();
+ }
+ }
+
+ if ($route->getCondition()) {
+ $expression = $this->getExpressionLanguage()->compile($route->getCondition(), array('context', 'request'));
+
+ if (isset($this->expressionsIndex[$expression])) {
+ $expr = $this->expressionsIndex[$expression];
+ } else {
+ $this->expressionsIndex[$expression] = $expr = count($this->expressionsIndex);
+ $this->expressions[] = $expression;
+ }
+ } else {
+ $expr = null;
+ }
+
+ $dumpedRoute[] = $conditions;
+ $dumpedRoute[] = $expr;
+ $dumpedRoute[] = array_combine($methods, $methods) ?: null;
+ $dumpedRoute[] = array_combine($schemes, $schemes) ?: null;
+ $dumpedRoute[] = array_replace($route->getDefaults(), array('_route' => $name));
+
+ return $dumpedRoute;
+ }
+
+ /**
+ * Groups consecutive routes having the same host regex.
+ *
+ * The result is a collection of collections of routes having the same host regex.
+ */
+ private function groupRoutesByHostRegex(RouteCollection $routes): DumperCollection
+ {
+ $groups = new DumperCollection();
+ $currentGroup = new DumperCollection();
+ $currentGroup->setAttribute('host_regex', null);
+ $groups->add($currentGroup);
+
+ foreach ($routes as $name => $route) {
+ $hostRegex = $route->compile()->getHostRegex();
+ if ($currentGroup->getAttribute('host_regex') !== $hostRegex) {
+ $currentGroup = new DumperCollection();
+ $currentGroup->setAttribute('host_regex', $hostRegex);
+ $groups->add($currentGroup);
+ }
+ $currentGroup->add(new DumperRoute($name, $route));
+ }
+
+ return $groups;
+ }
+
+ private function getExpressionLanguage(): ExpressionLanguage
+ {
+ if (null === $this->expressionLanguage) {
+ if (!class_exists(ExpressionLanguage::class)) {
+ throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
+ }
+ $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders);
+ }
+
+ return $this->expressionLanguage;
+ }
+
+ private function exportRoutes(array $routes): string
+ {
+ if (!$routes) {
+ return 'array()';
+ }
+
+ $code = '';
+ foreach ($routes as list($hostRegex, $routeCollection)) {
+ $code .= <<export($hostRegex)},
+ {$this->exportCollection($routeCollection)},
+ ),
+
+EOF;
+ }
+
+ return "array(\n$code )";
+ }
+
+ private function exportCollection(array $routes, int $indent = 1): string
+ {
+ $pad = ' '.str_repeat(' ', $indent);
+ $code = "array(\n$pad{$this->export($routes[0])},\n";
+ for ($i = 1; $i < \count($routes); ++$i) {
+ if (!\is_array($routes[$i][0])) {
+ $code .= $pad.$this->exportCollection($routes[$i], 1 + $indent).",\n";
+ } else {
+ $code .= $pad.$this->export($routes[$i]).",\n";
+ }
+ }
+
+ return $code.substr_replace($pad, ')', -4);
+ }
+
+ private function exportExpressions(array $expressions): string
+ {
+ if (!$expressions) {
+ return 'array()';
+ }
+
+ $code = '';
+ foreach ($expressions as $expression) {
+ $code .= " function (\$context, \$request) { return $expression; },\n";
+ }
+
+ return "array(\n$code )";
+ }
+
+ /**
+ * @internal
+ */
+ public static function export($value): string
+ {
+ if (null === $value) {
+ return 'null';
+ }
+ if (!\is_array($value)) {
+ return var_export($value, true);
+ }
+ if (!$value) {
+ return 'array()';
+ }
+
+ $i = 0;
+ $export = 'array(';
+
+ foreach ($value as $k => $v) {
+ if ($i === $k) {
+ ++$i;
+ } else {
+ $export .= var_export($k, true).' => ';
+
+ if (\is_int($k) && $i < $k) {
+ $i = 1 + $k;
+ }
+ }
+
+ $export .= self::export($v).', ';
+ }
+
+ return substr_replace($export, ')', -2);
+ }
+}
diff --git a/src/Symfony/Component/Routing/Matcher/StaticUrlMatcher.php b/src/Symfony/Component/Routing/Matcher/StaticUrlMatcher.php
new file mode 100644
index 0000000000000..3a81ed0e3dd34
--- /dev/null
+++ b/src/Symfony/Component/Routing/Matcher/StaticUrlMatcher.php
@@ -0,0 +1,160 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Matcher;
+
+use Symfony\Component\Routing\Exception\MethodNotAllowedException;
+use Symfony\Component\Routing\Exception\NoConfigurationException;
+use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+use Symfony\Component\Routing\RequestContext;
+
+/**
+ * StaticUrlMatcher matches URL based on a set of rules dumped by StaticUrlMatcherDumper.
+ *
+ * @author Nicolas Grekas
+ */
+class StaticUrlMatcher extends UrlMatcher
+{
+ private $dumpedRoutes = array();
+
+ public function __construct(array $dumpedRoutes, RequestContext $context)
+ {
+ $this->dumpedRoutes = $dumpedRoutes;
+ $this->context = $context;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function match($rawPathinfo)
+ {
+ $allow = array();
+ $pathinfo = rawurldecode($rawPathinfo);
+ $trimmedPathinfo = rtrim($pathinfo, '/');
+ $context = $this->context;
+ $request = $this->request ?: $this->createRequest($pathinfo);
+ $requestMethod = $context->getMethod();
+ $host = $context->getHost();
+ $canRedirect = $this instanceof RedirectableUrlMatcherInterface;
+
+ foreach ($this->dumpedRoutes[0] as list($hostRegex, $routes)) {
+ if (null === $hostRegex) {
+ $hostMatches = array();
+ } elseif (!preg_match($hostRegex, $host, $hostMatches)) {
+ continue;
+ }
+
+ $i = 1;
+ $count = \count($routes);
+ $nextRoutes = array();
+
+ while (true) {
+ for (; $i < $count; ++$i) {
+ if (!\is_array($conditions = $routes[$i][0])) {
+ // nested collection
+
+ if (null !== $conditions && 0 !== strpos($pathinfo, $conditions)) {
+ continue;
+ }
+
+ if ($i < $count - 1) {
+ $nextRoutes[] = array(1 + $i, $routes);
+ }
+
+ $routes = $routes[$i];
+ $count = \count($routes);
+ $i = 0;
+ } else {
+ // single route
+
+ list(, $expr, $methods, $schemes, $defaults) = $routes[$i];
+ $matches = array();
+
+ switch ($conditions[0]) {
+ case 'trim':
+ if ($canRedirect) {
+ if ($conditions[1] !== $trimmedPathinfo) {
+ continue 2;
+ }
+ $redirectTrailingSlash = '/' !== $pathinfo[-1];
+ break;
+ }
+ $conditions[1] .= '/';
+ // no break
+ case 'path':
+ if ($conditions[1] !== $pathinfo) {
+ continue 2;
+ }
+ $redirectTrailingSlash = false;
+ break;
+ case 'start':
+ if (0 !== strpos($pathinfo, $conditions[2])) {
+ continue 2;
+ }
+ // no break
+ case 'match':
+ if (!preg_match($conditions[1], $pathinfo, $matches)) {
+ continue 2;
+ }
+ if ($redirectTrailingSlash = isset($matches[''])) {
+ $redirectTrailingSlash = $canRedirect && '/' !== $matches[''];
+ unset($matches['']);
+ }
+ break;
+ }
+
+ if (null !== $expr && !$this->dumpedRoutes[1][$expr]($context, $request)) {
+ continue;
+ }
+
+ if ($methods && !isset($methods[$requestMethod]) && ('HEAD' !== $requestMethod || !isset($methods['GET']))) {
+ $allow += $methods;
+ continue;
+ }
+
+ if ($matches || $hostMatches) {
+ $ret = $this->mergeDefaults(array_replace($hostMatches, $matches), $defaults);
+ $ret['_route'] = $defaults['_route'];
+ } else {
+ $ret = $defaults;
+ }
+
+ if ($redirectTrailingSlash) {
+ return array_replace($ret, $this->redirect($rawPathinfo.'/', $defaults['_route']));
+ }
+
+ if ($schemes && !isset($schemes[$context->getScheme()])) {
+ if ($canRedirect) {
+ return array_replace($ret, $this->redirect($rawPathinfo, $defaults['_route'], key($schemes)));
+ }
+ continue;
+ }
+
+ return $ret;
+ }
+ }
+
+ if (!$nextRoutes) {
+ break;
+ }
+
+ list($i, $routes) = array_pop($nextRoutes);
+ $count = \count($routes);
+ }
+ }
+
+ if (!$this->dumpedRoutes[0] && '/' === $pathinfo) {
+ throw new NoConfigurationException();
+ }
+
+ throw 0 < count($allow) ? new MethodNotAllowedException(array_values($allow)) : new ResourceNotFoundException();
+ }
+}
diff --git a/src/Symfony/Component/Routing/Router.php b/src/Symfony/Component/Routing/Router.php
index 0de921853f875..8e78c3b5ac94e 100644
--- a/src/Symfony/Component/Routing/Router.php
+++ b/src/Symfony/Component/Routing/Router.php
@@ -17,9 +17,11 @@
use Symfony\Component\Config\ConfigCacheFactory;
use Psr\Log\LoggerInterface;
use Symfony\Component\Routing\Generator\ConfigurableRequirementsInterface;
+use Symfony\Component\Routing\Generator\StaticUrlGenerator;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\Generator\Dumper\GeneratorDumperInterface;
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
+use Symfony\Component\Routing\Matcher\StaticUrlMatcher;
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
use Symfony\Component\Routing\Matcher\Dumper\MatcherDumperInterface;
use Symfony\Component\HttpFoundation\Request;
@@ -83,6 +85,8 @@ class Router implements RouterInterface, RequestMatcherInterface
*/
private $expressionLanguageProviders = array();
+ private static $dumpCache = array();
+
/**
* @param LoaderInterface $loader A LoaderInterface instance
* @param mixed $resource The main resource to load
@@ -273,8 +277,10 @@ public function getMatcher()
return $this->matcher;
}
+ $useStatic = is_subclass_of($this->options['matcher_class'], StaticUrlMatcher::class);
+
if (null === $this->options['cache_dir'] || null === $this->options['matcher_cache_class']) {
- $this->matcher = new $this->options['matcher_class']($this->getRouteCollection(), $this->context);
+ $this->matcher = new $this->options[$useStatic ? 'matcher_base_class' : 'matcher_class']($this->getRouteCollection(), $this->context);
if (method_exists($this->matcher, 'addExpressionLanguageProvider')) {
foreach ($this->expressionLanguageProviders as $provider) {
$this->matcher->addExpressionLanguageProvider($provider);
@@ -302,6 +308,14 @@ function (ConfigCacheInterface $cache) {
}
);
+ if ($useStatic) {
+ if (!isset(self::$dumpCache[$path = $cache->getPath()])) {
+ self::$dumpCache[$path] = require $path;
+ }
+
+ return $this->matcher = new $this->options['matcher_class'](self::$dumpCache[$path], $this->context);
+ }
+
require_once $cache->getPath();
return $this->matcher = new $this->options['matcher_cache_class']($this->context);
@@ -318,8 +332,10 @@ public function getGenerator()
return $this->generator;
}
+ $useStatic = is_a($this->options['generator_class'], StaticUrlGenerator::class, true);
+
if (null === $this->options['cache_dir'] || null === $this->options['generator_cache_class']) {
- $this->generator = new $this->options['generator_class']($this->getRouteCollection(), $this->context, $this->logger);
+ $this->generator = new $this->options[$useStatic ? 'generator_base_class' : 'generator_class']($this->getRouteCollection(), $this->context, $this->logger);
} else {
$cache = $this->getConfigCacheFactory()->cache($this->options['cache_dir'].'/'.$this->options['generator_cache_class'].'.php',
function (ConfigCacheInterface $cache) {
@@ -334,9 +350,17 @@ function (ConfigCacheInterface $cache) {
}
);
- require_once $cache->getPath();
+ if ($useStatic) {
+ if (!isset(self::$dumpCache[$path = $cache->getPath()])) {
+ self::$dumpCache[$path] = require $path;
+ }
- $this->generator = new $this->options['generator_cache_class']($this->context, $this->logger);
+ $this->generator = new $this->options['generator_class'](self::$dumpCache[$path], $this->context, $this->logger);
+ } else {
+ require_once $cache->getPath();
+
+ $this->generator = new $this->options['generator_cache_class']($this->context, $this->logger);
+ }
}
if ($this->generator instanceof ConfigurableRequirementsInterface) {
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/static_url_matcher0.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/static_url_matcher0.php
new file mode 100644
index 0000000000000..f81ee0847a9e8
--- /dev/null
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/static_url_matcher0.php
@@ -0,0 +1,8 @@
+baz|symfony)$#s'), null, null, null, array('def' => 'test', '_route' => 'foo')),
+ array(array('path', '/foofoo'), null, null, null, array('def' => 'test', '_route' => 'foofoo')),
+ ),
+ array(
+ '/bar',
+ array(array('match', '#^/bar/(?P[^/]++)$#s'), null, array('GET' => 'GET', 'HEAD' => 'HEAD'), null, array('_route' => 'bar')),
+ array(array('start', '#^/barhead/(?P[^/]++)$#s', '/barhead'), null, array('GET' => 'GET'), null, array('_route' => 'barhead')),
+ ),
+ array(
+ '/test',
+ array(
+ '/test/baz',
+ array(array('path', '/test/baz'), null, null, null, array('_route' => 'baz')),
+ array(array('path', '/test/baz.html'), null, null, null, array('_route' => 'baz2')),
+ array(array('trim', '/test/baz3'), null, null, null, array('_route' => 'baz3')),
+ ),
+ array(array('match', '#^/test/(?P[^/]++)(?P<>/?)$#s'), null, null, null, array('_route' => 'baz4')),
+ array(array('match', '#^/test/(?P[^/]++)/$#s'), null, array('POST' => 'POST'), null, array('_route' => 'baz5')),
+ array(array('match', '#^/test/(?P[^/]++)/$#s'), null, array('PUT' => 'PUT'), null, array('_route' => 'baz.baz6')),
+ ),
+ array(array('match', '#^/(?P[\']+)$#s'), null, null, null, array('_route' => 'quoter')),
+ array(array('path', '/spa ce'), null, null, null, array('_route' => 'space')),
+ array(
+ '/a',
+ array(
+ '/a/b\'b',
+ array(array('match', '#^/a/b\'b/(?P[^/]++)$#s'), null, null, null, array('_route' => 'foo1')),
+ array(array('match', '#^/a/b\'b/(?P[^/]++)$#s'), null, null, null, array('_route' => 'bar1')),
+ ),
+ array(array('match', '#^/a/(?P.*)$#s'), null, null, null, array('_route' => 'overridden')),
+ array(
+ '/a/b\'b',
+ array(array('match', '#^/a/b\'b/(?P[^/]++)$#s'), null, null, null, array('_route' => 'foo2')),
+ array(array('match', '#^/a/b\'b/(?P[^/]++)$#s'), null, null, null, array('_route' => 'bar2')),
+ ),
+ ),
+ array(
+ '/multi',
+ array(array('start', '#^/multi/hello(?:/(?P[^/]++))?$#s', '/multi/hello'), null, null, null, array('who' => 'World!', '_route' => 'helloWorld')),
+ array(array('trim', '/multi/hey'), null, null, null, array('_route' => 'hey')),
+ array(array('path', '/multi/new'), null, null, null, array('_route' => 'overridden2')),
+ ),
+ array(array('match', '#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#s'), null, null, null, array('_route' => 'foo3')),
+ array(array('match', '#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#s'), null, null, null, array('_route' => 'bar3')),
+ array(
+ '/aba',
+ array(array('path', '/ababa'), null, null, null, array('_route' => 'ababa')),
+ array(array('match', '#^/aba/(?P[^/]++)$#s'), null, null, null, array('_route' => 'foo4')),
+ ),
+ ),
+ ),
+ array(
+ '#^a\\.example\\.com$#si',
+ array(
+ null,
+ array(array('path', '/route1'), null, null, null, array('_route' => 'route1')),
+ array(array('path', '/c2/route2'), null, null, null, array('_route' => 'route2')),
+ ),
+ ),
+ array(
+ '#^b\\.example\\.com$#si',
+ array(
+ null,
+ array(array('path', '/c2/route3'), null, null, null, array('_route' => 'route3')),
+ ),
+ ),
+ array(
+ '#^a\\.example\\.com$#si',
+ array(
+ null,
+ array(array('path', '/route4'), null, null, null, array('_route' => 'route4')),
+ ),
+ ),
+ array(
+ '#^c\\.example\\.com$#si',
+ array(
+ null,
+ array(array('path', '/route5'), null, null, null, array('_route' => 'route5')),
+ ),
+ ),
+ array(
+ null,
+ array(
+ null,
+ array(array('path', '/route6'), null, null, null, array('_route' => 'route6')),
+ ),
+ ),
+ array(
+ '#^(?P[^\\.]++)\\.example\\.com$#si',
+ array(
+ null,
+ array(
+ '/route1',
+ array(array('path', '/route11'), null, null, null, array('_route' => 'route11')),
+ array(array('path', '/route12'), null, null, null, array('var1' => 'val', '_route' => 'route12')),
+ array(array('start', '#^/route13/(?P[^/]++)$#s', '/route13'), null, null, null, array('_route' => 'route13')),
+ array(array('start', '#^/route14/(?P[^/]++)$#s', '/route14'), null, null, null, array('var1' => 'val', '_route' => 'route14')),
+ ),
+ ),
+ ),
+ array(
+ '#^c\\.example\\.com$#si',
+ array(
+ null,
+ array(array('start', '#^/route15/(?P[^/]++)$#s', '/route15'), null, null, null, array('_route' => 'route15')),
+ ),
+ ),
+ array(
+ null,
+ array(
+ null,
+ array(array('start', '#^/route16/(?P[^/]++)$#s', '/route16'), null, null, null, array('var1' => 'val', '_route' => 'route16')),
+ array(array('path', '/route17'), null, null, null, array('_route' => 'route17')),
+ array(array('path', '/a/a...'), null, null, null, array('_route' => 'a')),
+ array(
+ '/a/b',
+ array(array('match', '#^/a/b/(?P[^/]++)$#s'), null, null, null, array('_route' => 'b')),
+ array(array('start', '#^/a/b/c/(?P[^/]++)$#s', '/a/b/c'), null, null, null, array('_route' => 'c')),
+ ),
+ ),
+ ),
+ ),
+ array(),
+);
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/static_url_matcher2.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/static_url_matcher2.php
new file mode 100644
index 0000000000000..a80ba85c7ecb6
--- /dev/null
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/static_url_matcher2.php
@@ -0,0 +1,138 @@
+baz|symfony)$#s'), null, null, null, array('def' => 'test', '_route' => 'foo')),
+ array(array('path', '/foofoo'), null, null, null, array('def' => 'test', '_route' => 'foofoo')),
+ ),
+ array(
+ '/bar',
+ array(array('match', '#^/bar/(?P[^/]++)$#s'), null, array('GET' => 'GET', 'HEAD' => 'HEAD'), null, array('_route' => 'bar')),
+ array(array('start', '#^/barhead/(?P[^/]++)$#s', '/barhead'), null, array('GET' => 'GET'), null, array('_route' => 'barhead')),
+ ),
+ array(
+ '/test',
+ array(
+ '/test/baz',
+ array(array('path', '/test/baz'), null, null, null, array('_route' => 'baz')),
+ array(array('path', '/test/baz.html'), null, null, null, array('_route' => 'baz2')),
+ array(array('trim', '/test/baz3'), null, null, null, array('_route' => 'baz3')),
+ ),
+ array(array('match', '#^/test/(?P[^/]++)(?P<>/?)$#s'), null, null, null, array('_route' => 'baz4')),
+ array(array('match', '#^/test/(?P[^/]++)/$#s'), null, array('POST' => 'POST'), null, array('_route' => 'baz5')),
+ array(array('match', '#^/test/(?P[^/]++)/$#s'), null, array('PUT' => 'PUT'), null, array('_route' => 'baz.baz6')),
+ ),
+ array(array('match', '#^/(?P[\']+)$#s'), null, null, null, array('_route' => 'quoter')),
+ array(array('path', '/spa ce'), null, null, null, array('_route' => 'space')),
+ array(
+ '/a',
+ array(
+ '/a/b\'b',
+ array(array('match', '#^/a/b\'b/(?P[^/]++)$#s'), null, null, null, array('_route' => 'foo1')),
+ array(array('match', '#^/a/b\'b/(?P[^/]++)$#s'), null, null, null, array('_route' => 'bar1')),
+ ),
+ array(array('match', '#^/a/(?P.*)$#s'), null, null, null, array('_route' => 'overridden')),
+ array(
+ '/a/b\'b',
+ array(array('match', '#^/a/b\'b/(?P[^/]++)$#s'), null, null, null, array('_route' => 'foo2')),
+ array(array('match', '#^/a/b\'b/(?P[^/]++)$#s'), null, null, null, array('_route' => 'bar2')),
+ ),
+ ),
+ array(
+ '/multi',
+ array(array('start', '#^/multi/hello(?:/(?P[^/]++))?$#s', '/multi/hello'), null, null, null, array('who' => 'World!', '_route' => 'helloWorld')),
+ array(array('trim', '/multi/hey'), null, null, null, array('_route' => 'hey')),
+ array(array('path', '/multi/new'), null, null, null, array('_route' => 'overridden2')),
+ ),
+ array(array('match', '#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#s'), null, null, null, array('_route' => 'foo3')),
+ array(array('match', '#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#s'), null, null, null, array('_route' => 'bar3')),
+ array(
+ '/aba',
+ array(array('path', '/ababa'), null, null, null, array('_route' => 'ababa')),
+ array(array('match', '#^/aba/(?P[^/]++)$#s'), null, null, null, array('_route' => 'foo4')),
+ ),
+ ),
+ ),
+ array(
+ '#^a\\.example\\.com$#si',
+ array(
+ null,
+ array(array('path', '/route1'), null, null, null, array('_route' => 'route1')),
+ array(array('path', '/c2/route2'), null, null, null, array('_route' => 'route2')),
+ ),
+ ),
+ array(
+ '#^b\\.example\\.com$#si',
+ array(
+ null,
+ array(array('path', '/c2/route3'), null, null, null, array('_route' => 'route3')),
+ ),
+ ),
+ array(
+ '#^a\\.example\\.com$#si',
+ array(
+ null,
+ array(array('path', '/route4'), null, null, null, array('_route' => 'route4')),
+ ),
+ ),
+ array(
+ '#^c\\.example\\.com$#si',
+ array(
+ null,
+ array(array('path', '/route5'), null, null, null, array('_route' => 'route5')),
+ ),
+ ),
+ array(
+ null,
+ array(
+ null,
+ array(array('path', '/route6'), null, null, null, array('_route' => 'route6')),
+ ),
+ ),
+ array(
+ '#^(?P[^\\.]++)\\.example\\.com$#si',
+ array(
+ null,
+ array(
+ '/route1',
+ array(array('path', '/route11'), null, null, null, array('_route' => 'route11')),
+ array(array('path', '/route12'), null, null, null, array('var1' => 'val', '_route' => 'route12')),
+ array(array('start', '#^/route13/(?P[^/]++)$#s', '/route13'), null, null, null, array('_route' => 'route13')),
+ array(array('start', '#^/route14/(?P[^/]++)$#s', '/route14'), null, null, null, array('var1' => 'val', '_route' => 'route14')),
+ ),
+ ),
+ ),
+ array(
+ '#^c\\.example\\.com$#si',
+ array(
+ null,
+ array(array('start', '#^/route15/(?P[^/]++)$#s', '/route15'), null, null, null, array('_route' => 'route15')),
+ ),
+ ),
+ array(
+ null,
+ array(
+ null,
+ array(array('start', '#^/route16/(?P[^/]++)$#s', '/route16'), null, null, null, array('var1' => 'val', '_route' => 'route16')),
+ array(array('path', '/route17'), null, null, null, array('_route' => 'route17')),
+ array(array('path', '/a/a...'), null, null, null, array('_route' => 'a')),
+ array(
+ '/a/b',
+ array(array('match', '#^/a/b/(?P[^/]++)$#s'), null, null, null, array('_route' => 'b')),
+ array(array('start', '#^/a/b/c/(?P[^/]++)$#s', '/a/b/c'), null, null, null, array('_route' => 'c')),
+ ),
+ array(array('path', '/secure'), null, null, array('https' => 'https'), array('_route' => 'secure')),
+ array(array('path', '/nonsecure'), null, null, array('http' => 'http'), array('_route' => 'nonsecure')),
+ ),
+ ),
+ ),
+ array(),
+);
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/static_url_matcher3.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/static_url_matcher3.php
new file mode 100644
index 0000000000000..37e658f87d439
--- /dev/null
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/static_url_matcher3.php
@@ -0,0 +1,23 @@
+ 'static')),
+ array(array('match', '#^/rootprefix/(?P[^/]++)$#s'), null, null, null, array('_route' => 'dynamic')),
+ ),
+ array(array('path', '/with-condition'), 0, null, null, array('_route' => 'with-condition')),
+ ),
+ ),
+ ),
+ array(
+ function ($context, $request) { return ($context->getMethod() == "GET"); },
+ ),
+);
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/static_url_matcher4.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/static_url_matcher4.php
new file mode 100644
index 0000000000000..37cb69f0df45e
--- /dev/null
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/static_url_matcher4.php
@@ -0,0 +1,24 @@
+ 'HEAD'), null, array('_route' => 'just_head')),
+ array(array('path', '/head_and_get'), null, array('HEAD' => 'HEAD', 'GET' => 'GET'), null, array('_route' => 'head_and_get')),
+ array(array('path', '/get_and_head'), null, array('GET' => 'GET', 'HEAD' => 'HEAD'), null, array('_route' => 'get_and_head')),
+ array(array('path', '/post_and_get'), null, array('POST' => 'POST', 'HEAD' => 'HEAD'), null, array('_route' => 'post_and_head')),
+ array(
+ '/put_and_post',
+ array(array('path', '/put_and_post'), null, array('PUT' => 'PUT', 'POST' => 'POST'), null, array('_route' => 'put_and_post')),
+ array(array('path', '/put_and_post'), null, array('PUT' => 'PUT', 'GET' => 'GET', 'HEAD' => 'HEAD'), null, array('_route' => 'put_and_get_and_head')),
+ ),
+ ),
+ ),
+ ),
+ array(),
+);
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/static_url_matcher5.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/static_url_matcher5.php
new file mode 100644
index 0000000000000..8bf38e631faa4
--- /dev/null
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/static_url_matcher5.php
@@ -0,0 +1,41 @@
+ 'a_first')),
+ array(array('path', '/a/22'), null, null, null, array('_route' => 'a_second')),
+ array(array('path', '/a/333'), null, null, null, array('_route' => 'a_third')),
+ ),
+ array(array('match', '#^/(?P[^/]++)$#s'), null, null, null, array('_route' => 'a_wildcard')),
+ array(
+ '/a',
+ array(array('trim', '/a/44'), null, null, null, array('_route' => 'a_fourth')),
+ array(array('trim', '/a/55'), null, null, null, array('_route' => 'a_fifth')),
+ array(array('trim', '/a/66'), null, null, null, array('_route' => 'a_sixth')),
+ ),
+ array(array('start', '#^/nested/(?P[^/]++)$#s', '/nested'), null, null, null, array('_route' => 'nested_wildcard')),
+ array(
+ '/nested/group',
+ array(array('trim', '/nested/group/a'), null, null, null, array('_route' => 'nested_a')),
+ array(array('trim', '/nested/group/b'), null, null, null, array('_route' => 'nested_b')),
+ array(array('trim', '/nested/group/c'), null, null, null, array('_route' => 'nested_c')),
+ ),
+ array(
+ '/slashed/group',
+ array(array('trim', '/slashed/group'), null, null, null, array('_route' => 'slashed_a')),
+ array(array('trim', '/slashed/group/b'), null, null, null, array('_route' => 'slashed_b')),
+ array(array('trim', '/slashed/group/c'), null, null, null, array('_route' => 'slashed_c')),
+ ),
+ ),
+ ),
+ ),
+ array(),
+);
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/static_url_matcher6.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/static_url_matcher6.php
new file mode 100644
index 0000000000000..51dcf4a8cd644
--- /dev/null
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/static_url_matcher6.php
@@ -0,0 +1,43 @@
+ 'simple_trailing_slash_no_methods')),
+ array(array('trim', '/trailing/simple/get-method'), null, array('GET' => 'GET'), null, array('_route' => 'simple_trailing_slash_GET_method')),
+ array(array('trim', '/trailing/simple/head-method'), null, array('HEAD' => 'HEAD'), null, array('_route' => 'simple_trailing_slash_HEAD_method')),
+ array(array('path', '/trailing/simple/post-method/'), null, array('POST' => 'POST'), null, array('_route' => 'simple_trailing_slash_POST_method')),
+ ),
+ array(
+ '/trailing/regex',
+ array(array('start', '#^/trailing/regex/no\\-methods/(?P[^/]++)(?P<>/?)$#s', '/trailing/regex/no-methods'), null, null, null, array('_route' => 'regex_trailing_slash_no_methods')),
+ array(array('start', '#^/trailing/regex/get\\-method/(?P[^/]++)(?P<>/?)$#s', '/trailing/regex/get-method'), null, array('GET' => 'GET'), null, array('_route' => 'regex_trailing_slash_GET_method')),
+ array(array('start', '#^/trailing/regex/head\\-method/(?P[^/]++)(?P<>/?)$#s', '/trailing/regex/head-method'), null, array('HEAD' => 'HEAD'), null, array('_route' => 'regex_trailing_slash_HEAD_method')),
+ array(array('start', '#^/trailing/regex/post\\-method/(?P[^/]++)/$#s', '/trailing/regex/post-method'), null, array('POST' => 'POST'), null, array('_route' => 'regex_trailing_slash_POST_method')),
+ ),
+ array(
+ '/not-trailing/simple',
+ array(array('path', '/not-trailing/simple/no-methods'), null, null, null, array('_route' => 'simple_not_trailing_slash_no_methods')),
+ array(array('path', '/not-trailing/simple/get-method'), null, array('GET' => 'GET'), null, array('_route' => 'simple_not_trailing_slash_GET_method')),
+ array(array('path', '/not-trailing/simple/head-method'), null, array('HEAD' => 'HEAD'), null, array('_route' => 'simple_not_trailing_slash_HEAD_method')),
+ array(array('path', '/not-trailing/simple/post-method'), null, array('POST' => 'POST'), null, array('_route' => 'simple_not_trailing_slash_POST_method')),
+ ),
+ array(
+ '/not-trailing/regex',
+ array(array('start', '#^/not\\-trailing/regex/no\\-methods/(?P[^/]++)$#s', '/not-trailing/regex/no-methods'), null, null, null, array('_route' => 'regex_not_trailing_slash_no_methods')),
+ array(array('start', '#^/not\\-trailing/regex/get\\-method/(?P[^/]++)$#s', '/not-trailing/regex/get-method'), null, array('GET' => 'GET'), null, array('_route' => 'regex_not_trailing_slash_GET_method')),
+ array(array('start', '#^/not\\-trailing/regex/head\\-method/(?P[^/]++)$#s', '/not-trailing/regex/head-method'), null, array('HEAD' => 'HEAD'), null, array('_route' => 'regex_not_trailing_slash_HEAD_method')),
+ array(array('start', '#^/not\\-trailing/regex/post\\-method/(?P[^/]++)$#s', '/not-trailing/regex/post-method'), null, array('POST' => 'POST'), null, array('_route' => 'regex_not_trailing_slash_POST_method')),
+ ),
+ ),
+ ),
+ ),
+ array(),
+);
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/static_url_matcher7.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/static_url_matcher7.php
new file mode 100644
index 0000000000000..51dcf4a8cd644
--- /dev/null
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/static_url_matcher7.php
@@ -0,0 +1,43 @@
+ 'simple_trailing_slash_no_methods')),
+ array(array('trim', '/trailing/simple/get-method'), null, array('GET' => 'GET'), null, array('_route' => 'simple_trailing_slash_GET_method')),
+ array(array('trim', '/trailing/simple/head-method'), null, array('HEAD' => 'HEAD'), null, array('_route' => 'simple_trailing_slash_HEAD_method')),
+ array(array('path', '/trailing/simple/post-method/'), null, array('POST' => 'POST'), null, array('_route' => 'simple_trailing_slash_POST_method')),
+ ),
+ array(
+ '/trailing/regex',
+ array(array('start', '#^/trailing/regex/no\\-methods/(?P[^/]++)(?P<>/?)$#s', '/trailing/regex/no-methods'), null, null, null, array('_route' => 'regex_trailing_slash_no_methods')),
+ array(array('start', '#^/trailing/regex/get\\-method/(?P[^/]++)(?P<>/?)$#s', '/trailing/regex/get-method'), null, array('GET' => 'GET'), null, array('_route' => 'regex_trailing_slash_GET_method')),
+ array(array('start', '#^/trailing/regex/head\\-method/(?P[^/]++)(?P<>/?)$#s', '/trailing/regex/head-method'), null, array('HEAD' => 'HEAD'), null, array('_route' => 'regex_trailing_slash_HEAD_method')),
+ array(array('start', '#^/trailing/regex/post\\-method/(?P[^/]++)/$#s', '/trailing/regex/post-method'), null, array('POST' => 'POST'), null, array('_route' => 'regex_trailing_slash_POST_method')),
+ ),
+ array(
+ '/not-trailing/simple',
+ array(array('path', '/not-trailing/simple/no-methods'), null, null, null, array('_route' => 'simple_not_trailing_slash_no_methods')),
+ array(array('path', '/not-trailing/simple/get-method'), null, array('GET' => 'GET'), null, array('_route' => 'simple_not_trailing_slash_GET_method')),
+ array(array('path', '/not-trailing/simple/head-method'), null, array('HEAD' => 'HEAD'), null, array('_route' => 'simple_not_trailing_slash_HEAD_method')),
+ array(array('path', '/not-trailing/simple/post-method'), null, array('POST' => 'POST'), null, array('_route' => 'simple_not_trailing_slash_POST_method')),
+ ),
+ array(
+ '/not-trailing/regex',
+ array(array('start', '#^/not\\-trailing/regex/no\\-methods/(?P[^/]++)$#s', '/not-trailing/regex/no-methods'), null, null, null, array('_route' => 'regex_not_trailing_slash_no_methods')),
+ array(array('start', '#^/not\\-trailing/regex/get\\-method/(?P[^/]++)$#s', '/not-trailing/regex/get-method'), null, array('GET' => 'GET'), null, array('_route' => 'regex_not_trailing_slash_GET_method')),
+ array(array('start', '#^/not\\-trailing/regex/head\\-method/(?P[^/]++)$#s', '/not-trailing/regex/head-method'), null, array('HEAD' => 'HEAD'), null, array('_route' => 'regex_not_trailing_slash_HEAD_method')),
+ array(array('start', '#^/not\\-trailing/regex/post\\-method/(?P[^/]++)$#s', '/not-trailing/regex/post-method'), null, array('POST' => 'POST'), null, array('_route' => 'regex_not_trailing_slash_POST_method')),
+ ),
+ ),
+ ),
+ ),
+ array(),
+);
diff --git a/src/Symfony/Component/Routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php b/src/Symfony/Component/Routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php
index 4b2e5b196dc06..89a6b0f7f68e4 100644
--- a/src/Symfony/Component/Routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php
+++ b/src/Symfony/Component/Routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php
@@ -18,6 +18,9 @@
use Symfony\Component\Routing\Generator\Dumper\PhpGeneratorDumper;
use Symfony\Component\Routing\RequestContext;
+/**
+ * @group legacy
+ */
class PhpGeneratorDumperTest extends TestCase
{
/**
diff --git a/src/Symfony/Component/Routing/Tests/Generator/Dumper/StaticUrlGeneratorDumperTest.php b/src/Symfony/Component/Routing/Tests/Generator/Dumper/StaticUrlGeneratorDumperTest.php
new file mode 100644
index 0000000000000..04d379279ffa6
--- /dev/null
+++ b/src/Symfony/Component/Routing/Tests/Generator/Dumper/StaticUrlGeneratorDumperTest.php
@@ -0,0 +1,170 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Generator\Dumper;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\Generator\Dumper\StaticUrlGeneratorDumper;
+use Symfony\Component\Routing\RequestContext;
+use Symfony\Component\Routing\Generator\StaticUrlGenerator;
+
+class StaticUrlGeneratorDumperTest extends TestCase
+{
+ /**
+ * @var RouteCollection
+ */
+ private $routeCollection;
+
+ /**
+ * @var StaticUrlGeneratorDumper
+ */
+ private $generatorDumper;
+
+ /**
+ * @var string
+ */
+ private $testTmpFilepath;
+
+ /**
+ * @var string
+ */
+ private $largeTestTmpFilepath;
+
+ protected function setUp()
+ {
+ parent::setUp();
+
+ $this->routeCollection = new RouteCollection();
+ $this->generatorDumper = new StaticUrlGeneratorDumper($this->routeCollection);
+ $this->testTmpFilepath = sys_get_temp_dir().'/php_generator.'.$this->getName().'.php';
+ $this->largeTestTmpFilepath = sys_get_temp_dir().'/php_generator.'.$this->getName().'.large.php';
+ @unlink($this->testTmpFilepath);
+ @unlink($this->largeTestTmpFilepath);
+ }
+
+ protected function tearDown()
+ {
+ parent::tearDown();
+
+ @unlink($this->testTmpFilepath);
+
+ $this->routeCollection = null;
+ $this->generatorDumper = null;
+ $this->testTmpFilepath = null;
+ }
+
+ public function testDumpWithRoutes()
+ {
+ $this->routeCollection->add('Test', new Route('/testing/{foo}'));
+ $this->routeCollection->add('Test2', new Route('/testing2'));
+
+ file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump());
+
+ $projectUrlGenerator = new StaticUrlGenerator(require $this->testTmpFilepath, new RequestContext('/app.php'));
+
+ $absoluteUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL);
+ $absoluteUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), UrlGeneratorInterface::ABSOLUTE_URL);
+ $relativeUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_PATH);
+ $relativeUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), UrlGeneratorInterface::ABSOLUTE_PATH);
+
+ $this->assertEquals('http://localhost/app.php/testing/bar', $absoluteUrlWithParameter);
+ $this->assertEquals('http://localhost/app.php/testing2', $absoluteUrlWithoutParameter);
+ $this->assertEquals('/app.php/testing/bar', $relativeUrlWithParameter);
+ $this->assertEquals('/app.php/testing2', $relativeUrlWithoutParameter);
+ }
+
+ public function testDumpWithTooManyRoutes()
+ {
+ $this->routeCollection->add('Test', new Route('/testing/{foo}'));
+ for ($i = 0; $i < 32769; ++$i) {
+ $this->routeCollection->add('route_'.$i, new Route('/route_'.$i));
+ }
+ $this->routeCollection->add('Test2', new Route('/testing2'));
+
+ file_put_contents($this->largeTestTmpFilepath, $this->generatorDumper->dump());
+ $this->routeCollection = $this->generatorDumper = null;
+
+ $projectUrlGenerator = new StaticUrlGenerator(require $this->largeTestTmpFilepath, new RequestContext('/app.php'));
+
+ $absoluteUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL);
+ $absoluteUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), UrlGeneratorInterface::ABSOLUTE_URL);
+ $relativeUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_PATH);
+ $relativeUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), UrlGeneratorInterface::ABSOLUTE_PATH);
+
+ $this->assertEquals('http://localhost/app.php/testing/bar', $absoluteUrlWithParameter);
+ $this->assertEquals('http://localhost/app.php/testing2', $absoluteUrlWithoutParameter);
+ $this->assertEquals('/app.php/testing/bar', $relativeUrlWithParameter);
+ $this->assertEquals('/app.php/testing2', $relativeUrlWithoutParameter);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testDumpWithoutRoutes()
+ {
+ file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump());
+
+ $projectUrlGenerator = new StaticUrlGenerator(require $this->testTmpFilepath, new RequestContext('/app.php'));
+
+ $projectUrlGenerator->generate('Test', array());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Routing\Exception\RouteNotFoundException
+ */
+ public function testGenerateNonExistingRoute()
+ {
+ $this->routeCollection->add('Test', new Route('/test'));
+
+ file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump());
+
+ $projectUrlGenerator = new StaticUrlGenerator(require $this->testTmpFilepath, new RequestContext());
+ $url = $projectUrlGenerator->generate('NonExisting', array());
+ }
+
+ public function testDumpForRouteWithDefaults()
+ {
+ $this->routeCollection->add('Test', new Route('/testing/{foo}', array('foo' => 'bar')));
+
+ file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump());
+
+ $projectUrlGenerator = new StaticUrlGenerator(require $this->testTmpFilepath, new RequestContext());
+ $url = $projectUrlGenerator->generate('Test', array());
+
+ $this->assertEquals('/testing', $url);
+ }
+
+ public function testDumpWithSchemeRequirement()
+ {
+ $this->routeCollection->add('Test1', new Route('/testing', array(), array(), array(), '', array('ftp', 'https')));
+
+ file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump());
+
+ $projectUrlGenerator = new StaticUrlGenerator(require $this->testTmpFilepath, new RequestContext('/app.php'));
+
+ $absoluteUrl = $projectUrlGenerator->generate('Test1', array(), UrlGeneratorInterface::ABSOLUTE_URL);
+ $relativeUrl = $projectUrlGenerator->generate('Test1', array(), UrlGeneratorInterface::ABSOLUTE_PATH);
+
+ $this->assertEquals('ftp://localhost/app.php/testing', $absoluteUrl);
+ $this->assertEquals('ftp://localhost/app.php/testing', $relativeUrl);
+
+ $projectUrlGenerator = new StaticUrlGenerator(require $this->testTmpFilepath, new RequestContext('/app.php', 'GET', 'localhost', 'https'));
+
+ $absoluteUrl = $projectUrlGenerator->generate('Test1', array(), UrlGeneratorInterface::ABSOLUTE_URL);
+ $relativeUrl = $projectUrlGenerator->generate('Test1', array(), UrlGeneratorInterface::ABSOLUTE_PATH);
+
+ $this->assertEquals('https://localhost/app.php/testing', $absoluteUrl);
+ $this->assertEquals('/app.php/testing', $relativeUrl);
+ }
+}
diff --git a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php
index e4c18c47b144f..46660c9138a56 100644
--- a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php
+++ b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php
@@ -19,6 +19,9 @@
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
+/**
+ * @group legacy
+ */
class PhpMatcherDumperTest extends TestCase
{
/**
diff --git a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/StaticUrlMatcherDumperTest.php b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/StaticUrlMatcherDumperTest.php
new file mode 100644
index 0000000000000..f876c6a874bb1
--- /dev/null
+++ b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/StaticUrlMatcherDumperTest.php
@@ -0,0 +1,427 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Matcher\Dumper;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Routing\Matcher\Dumper\StaticUrlMatcherDumper;
+use Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface;
+use Symfony\Component\Routing\Matcher\StaticUrlMatcher;
+use Symfony\Component\Routing\RequestContext;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+class StaticUrlMatcherDumperTest extends TestCase
+{
+ /**
+ * @var string
+ */
+ private $dumpPath;
+
+ protected function setUp()
+ {
+ parent::setUp();
+
+ $this->dumpPath = sys_get_temp_dir().DIRECTORY_SEPARATOR.'php_matcher.'.uniqid('StaticUrlMatcher').'.php';
+ }
+
+ protected function tearDown()
+ {
+ parent::tearDown();
+
+ @unlink($this->dumpPath);
+ }
+
+ public function testRedirectPreservesUrlEncoding()
+ {
+ $collection = new RouteCollection();
+ $collection->add('foo', new Route('/foo:bar/'));
+
+ $matcher = $this->generateDumpedMatcher($collection);
+ $matcher->expects($this->once())->method('redirect')->with('/foo%3Abar/', 'foo')->willReturn(array());
+
+ $matcher->match('/foo%3Abar');
+ }
+
+ /**
+ * @dataProvider getRouteCollections
+ */
+ public function testDump(RouteCollection $collection, $fixture)
+ {
+ $basePath = __DIR__.'/../../Fixtures/dumper/';
+
+ $dumper = new StaticUrlMatcherDumper($collection);
+ $this->assertStringEqualsFile($basePath.$fixture, $dumper->dump());
+ }
+
+ public function getRouteCollections()
+ {
+ /* test case 1 */
+
+ $collection = new RouteCollection();
+
+ $collection->add('overridden', new Route('/overridden'));
+
+ // defaults and requirements
+ $collection->add('foo', new Route(
+ '/foo/{bar}',
+ array('def' => 'test'),
+ array('bar' => 'baz|symfony')
+ ));
+ // method requirement
+ $collection->add('bar', new Route(
+ '/bar/{foo}',
+ array(),
+ array(),
+ array(),
+ '',
+ array(),
+ array('GET', 'head')
+ ));
+ // GET method requirement automatically adds HEAD as valid
+ $collection->add('barhead', new Route(
+ '/barhead/{foo}',
+ array(),
+ array(),
+ array(),
+ '',
+ array(),
+ array('GET')
+ ));
+ // simple
+ $collection->add('baz', new Route(
+ '/test/baz'
+ ));
+ // simple with extension
+ $collection->add('baz2', new Route(
+ '/test/baz.html'
+ ));
+ // trailing slash
+ $collection->add('baz3', new Route(
+ '/test/baz3/'
+ ));
+ // trailing slash with variable
+ $collection->add('baz4', new Route(
+ '/test/{foo}/'
+ ));
+ // trailing slash and method
+ $collection->add('baz5', new Route(
+ '/test/{foo}/',
+ array(),
+ array(),
+ array(),
+ '',
+ array(),
+ array('post')
+ ));
+ // complex name
+ $collection->add('baz.baz6', new Route(
+ '/test/{foo}/',
+ array(),
+ array(),
+ array(),
+ '',
+ array(),
+ array('put')
+ ));
+ // defaults without variable
+ $collection->add('foofoo', new Route(
+ '/foofoo',
+ array('def' => 'test')
+ ));
+ // pattern with quotes
+ $collection->add('quoter', new Route(
+ '/{quoter}',
+ array(),
+ array('quoter' => '[\']+')
+ ));
+ // space in pattern
+ $collection->add('space', new Route(
+ '/spa ce'
+ ));
+
+ // prefixes
+ $collection1 = new RouteCollection();
+ $collection1->add('overridden', new Route('/overridden1'));
+ $collection1->add('foo1', new Route('/{foo}'));
+ $collection1->add('bar1', new Route('/{bar}'));
+ $collection1->addPrefix('/b\'b');
+ $collection2 = new RouteCollection();
+ $collection2->addCollection($collection1);
+ $collection2->add('overridden', new Route('/{var}', array(), array('var' => '.*')));
+ $collection1 = new RouteCollection();
+ $collection1->add('foo2', new Route('/{foo1}'));
+ $collection1->add('bar2', new Route('/{bar1}'));
+ $collection1->addPrefix('/b\'b');
+ $collection2->addCollection($collection1);
+ $collection2->addPrefix('/a');
+ $collection->addCollection($collection2);
+
+ // overridden through addCollection() and multiple sub-collections with no own prefix
+ $collection1 = new RouteCollection();
+ $collection1->add('overridden2', new Route('/old'));
+ $collection1->add('helloWorld', new Route('/hello/{who}', array('who' => 'World!')));
+ $collection2 = new RouteCollection();
+ $collection3 = new RouteCollection();
+ $collection3->add('overridden2', new Route('/new'));
+ $collection3->add('hey', new Route('/hey/'));
+ $collection2->addCollection($collection3);
+ $collection1->addCollection($collection2);
+ $collection1->addPrefix('/multi');
+ $collection->addCollection($collection1);
+
+ // "dynamic" prefix
+ $collection1 = new RouteCollection();
+ $collection1->add('foo3', new Route('/{foo}'));
+ $collection1->add('bar3', new Route('/{bar}'));
+ $collection1->addPrefix('/b');
+ $collection1->addPrefix('{_locale}');
+ $collection->addCollection($collection1);
+
+ // route between collections
+ $collection->add('ababa', new Route('/ababa'));
+
+ // collection with static prefix but only one route
+ $collection1 = new RouteCollection();
+ $collection1->add('foo4', new Route('/{foo}'));
+ $collection1->addPrefix('/aba');
+ $collection->addCollection($collection1);
+
+ // prefix and host
+
+ $collection1 = new RouteCollection();
+
+ $route1 = new Route('/route1', array(), array(), array(), 'a.example.com');
+ $collection1->add('route1', $route1);
+
+ $route2 = new Route('/c2/route2', array(), array(), array(), 'a.example.com');
+ $collection1->add('route2', $route2);
+
+ $route3 = new Route('/c2/route3', array(), array(), array(), 'b.example.com');
+ $collection1->add('route3', $route3);
+
+ $route4 = new Route('/route4', array(), array(), array(), 'a.example.com');
+ $collection1->add('route4', $route4);
+
+ $route5 = new Route('/route5', array(), array(), array(), 'c.example.com');
+ $collection1->add('route5', $route5);
+
+ $route6 = new Route('/route6', array(), array(), array(), null);
+ $collection1->add('route6', $route6);
+
+ $collection->addCollection($collection1);
+
+ // host and variables
+
+ $collection1 = new RouteCollection();
+
+ $route11 = new Route('/route11', array(), array(), array(), '{var1}.example.com');
+ $collection1->add('route11', $route11);
+
+ $route12 = new Route('/route12', array('var1' => 'val'), array(), array(), '{var1}.example.com');
+ $collection1->add('route12', $route12);
+
+ $route13 = new Route('/route13/{name}', array(), array(), array(), '{var1}.example.com');
+ $collection1->add('route13', $route13);
+
+ $route14 = new Route('/route14/{name}', array('var1' => 'val'), array(), array(), '{var1}.example.com');
+ $collection1->add('route14', $route14);
+
+ $route15 = new Route('/route15/{name}', array(), array(), array(), 'c.example.com');
+ $collection1->add('route15', $route15);
+
+ $route16 = new Route('/route16/{name}', array('var1' => 'val'), array(), array(), null);
+ $collection1->add('route16', $route16);
+
+ $route17 = new Route('/route17', array(), array(), array(), null);
+ $collection1->add('route17', $route17);
+
+ $collection->addCollection($collection1);
+
+ // multiple sub-collections with a single route and a prefix each
+ $collection1 = new RouteCollection();
+ $collection1->add('a', new Route('/a...'));
+ $collection2 = new RouteCollection();
+ $collection2->add('b', new Route('/{var}'));
+ $collection3 = new RouteCollection();
+ $collection3->add('c', new Route('/{var}'));
+ $collection3->addPrefix('/c');
+ $collection2->addCollection($collection3);
+ $collection2->addPrefix('/b');
+ $collection1->addCollection($collection2);
+ $collection1->addPrefix('/a');
+ $collection->addCollection($collection1);
+
+ /* test case 2 */
+
+ $redirectCollection = clone $collection;
+
+ // force HTTPS redirection
+ $redirectCollection->add('secure', new Route(
+ '/secure',
+ array(),
+ array(),
+ array(),
+ '',
+ array('https')
+ ));
+
+ // force HTTP redirection
+ $redirectCollection->add('nonsecure', new Route(
+ '/nonsecure',
+ array(),
+ array(),
+ array(),
+ '',
+ array('http')
+ ));
+
+ /* test case 3 */
+
+ $rootprefixCollection = new RouteCollection();
+ $rootprefixCollection->add('static', new Route('/test'));
+ $rootprefixCollection->add('dynamic', new Route('/{var}'));
+ $rootprefixCollection->addPrefix('rootprefix');
+ $route = new Route('/with-condition');
+ $route->setCondition('context.getMethod() == "GET"');
+ $rootprefixCollection->add('with-condition', $route);
+
+ /* test case 4 */
+ $headMatchCasesCollection = new RouteCollection();
+ $headMatchCasesCollection->add('just_head', new Route(
+ '/just_head',
+ array(),
+ array(),
+ array(),
+ '',
+ array(),
+ array('HEAD')
+ ));
+ $headMatchCasesCollection->add('head_and_get', new Route(
+ '/head_and_get',
+ array(),
+ array(),
+ array(),
+ '',
+ array(),
+ array('HEAD', 'GET')
+ ));
+ $headMatchCasesCollection->add('get_and_head', new Route(
+ '/get_and_head',
+ array(),
+ array(),
+ array(),
+ '',
+ array(),
+ array('GET', 'HEAD')
+ ));
+ $headMatchCasesCollection->add('post_and_head', new Route(
+ '/post_and_get',
+ array(),
+ array(),
+ array(),
+ '',
+ array(),
+ array('POST', 'HEAD')
+ ));
+ $headMatchCasesCollection->add('put_and_post', new Route(
+ '/put_and_post',
+ array(),
+ array(),
+ array(),
+ '',
+ array(),
+ array('PUT', 'POST')
+ ));
+ $headMatchCasesCollection->add('put_and_get_and_head', new Route(
+ '/put_and_post',
+ array(),
+ array(),
+ array(),
+ '',
+ array(),
+ array('PUT', 'GET', 'HEAD')
+ ));
+
+ /* test case 5 */
+ $groupOptimisedCollection = new RouteCollection();
+ $groupOptimisedCollection->add('a_first', new Route('/a/11'));
+ $groupOptimisedCollection->add('a_second', new Route('/a/22'));
+ $groupOptimisedCollection->add('a_third', new Route('/a/333'));
+ $groupOptimisedCollection->add('a_wildcard', new Route('/{param}'));
+ $groupOptimisedCollection->add('a_fourth', new Route('/a/44/'));
+ $groupOptimisedCollection->add('a_fifth', new Route('/a/55/'));
+ $groupOptimisedCollection->add('a_sixth', new Route('/a/66/'));
+ $groupOptimisedCollection->add('nested_wildcard', new Route('/nested/{param}'));
+ $groupOptimisedCollection->add('nested_a', new Route('/nested/group/a/'));
+ $groupOptimisedCollection->add('nested_b', new Route('/nested/group/b/'));
+ $groupOptimisedCollection->add('nested_c', new Route('/nested/group/c/'));
+
+ $groupOptimisedCollection->add('slashed_a', new Route('/slashed/group/'));
+ $groupOptimisedCollection->add('slashed_b', new Route('/slashed/group/b/'));
+ $groupOptimisedCollection->add('slashed_c', new Route('/slashed/group/c/'));
+
+ $trailingSlashCollection = new RouteCollection();
+ $trailingSlashCollection->add('simple_trailing_slash_no_methods', new Route('/trailing/simple/no-methods/', array(), array(), array(), '', array(), array()));
+ $trailingSlashCollection->add('simple_trailing_slash_GET_method', new Route('/trailing/simple/get-method/', array(), array(), array(), '', array(), array('GET')));
+ $trailingSlashCollection->add('simple_trailing_slash_HEAD_method', new Route('/trailing/simple/head-method/', array(), array(), array(), '', array(), array('HEAD')));
+ $trailingSlashCollection->add('simple_trailing_slash_POST_method', new Route('/trailing/simple/post-method/', array(), array(), array(), '', array(), array('POST')));
+ $trailingSlashCollection->add('regex_trailing_slash_no_methods', new Route('/trailing/regex/no-methods/{param}/', array(), array(), array(), '', array(), array()));
+ $trailingSlashCollection->add('regex_trailing_slash_GET_method', new Route('/trailing/regex/get-method/{param}/', array(), array(), array(), '', array(), array('GET')));
+ $trailingSlashCollection->add('regex_trailing_slash_HEAD_method', new Route('/trailing/regex/head-method/{param}/', array(), array(), array(), '', array(), array('HEAD')));
+ $trailingSlashCollection->add('regex_trailing_slash_POST_method', new Route('/trailing/regex/post-method/{param}/', array(), array(), array(), '', array(), array('POST')));
+
+ $trailingSlashCollection->add('simple_not_trailing_slash_no_methods', new Route('/not-trailing/simple/no-methods', array(), array(), array(), '', array(), array()));
+ $trailingSlashCollection->add('simple_not_trailing_slash_GET_method', new Route('/not-trailing/simple/get-method', array(), array(), array(), '', array(), array('GET')));
+ $trailingSlashCollection->add('simple_not_trailing_slash_HEAD_method', new Route('/not-trailing/simple/head-method', array(), array(), array(), '', array(), array('HEAD')));
+ $trailingSlashCollection->add('simple_not_trailing_slash_POST_method', new Route('/not-trailing/simple/post-method', array(), array(), array(), '', array(), array('POST')));
+ $trailingSlashCollection->add('regex_not_trailing_slash_no_methods', new Route('/not-trailing/regex/no-methods/{param}', array(), array(), array(), '', array(), array()));
+ $trailingSlashCollection->add('regex_not_trailing_slash_GET_method', new Route('/not-trailing/regex/get-method/{param}', array(), array(), array(), '', array(), array('GET')));
+ $trailingSlashCollection->add('regex_not_trailing_slash_HEAD_method', new Route('/not-trailing/regex/head-method/{param}', array(), array(), array(), '', array(), array('HEAD')));
+ $trailingSlashCollection->add('regex_not_trailing_slash_POST_method', new Route('/not-trailing/regex/post-method/{param}', array(), array(), array(), '', array(), array('POST')));
+
+ return array(
+ array(new RouteCollection(), 'static_url_matcher0.php'),
+ array($collection, 'static_url_matcher1.php'),
+ array($redirectCollection, 'static_url_matcher2.php'),
+ array($rootprefixCollection, 'static_url_matcher3.php'),
+ array($headMatchCasesCollection, 'static_url_matcher4.php'),
+ array($groupOptimisedCollection, 'static_url_matcher5.php'),
+ array($trailingSlashCollection, 'static_url_matcher6.php'),
+ array($trailingSlashCollection, 'static_url_matcher7.php'),
+ );
+ }
+
+ /**
+ * @param $dumper
+ */
+ private function generateDumpedMatcher(RouteCollection $collection)
+ {
+ $dumper = new StaticUrlMatcherDumper($collection);
+ $code = $dumper->dump();
+
+ file_put_contents($this->dumpPath, $code);
+ $dumpedRoutes = require $this->dumpPath;
+
+ return $this->getMockBuilder(TestStaticUrlMatcher::class)
+ ->setConstructorArgs(array($dumpedRoutes, new RequestContext()))
+ ->setMethods(array('redirect'))
+ ->getMock();
+ }
+}
+
+class TestStaticUrlMatcher extends StaticUrlMatcher implements RedirectableUrlMatcherInterface
+{
+ public function redirect($path, $route, $scheme = null)
+ {
+ return array();
+ }
+}
diff --git a/src/Symfony/Component/Routing/Tests/Matcher/StaticUrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/StaticUrlMatcherTest.php
new file mode 100644
index 0000000000000..0c5d6aab38f7e
--- /dev/null
+++ b/src/Symfony/Component/Routing/Tests/Matcher/StaticUrlMatcherTest.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Matcher;
+
+use Symfony\Component\Routing\Matcher\Dumper\StaticUrlMatcherDumper;
+use Symfony\Component\Routing\Matcher\StaticUrlMatcher;
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\RequestContext;
+
+class StaticUrlMatcherTest extends UrlMatcherTest
+{
+ protected function getUrlMatcher(RouteCollection $routes, RequestContext $context)
+ {
+ $dumper = new StaticUrlMatcherDumper($routes);
+ $dumpedRoutes = eval('?>'.$dumper->dump());
+
+ return new StaticUrlMatcher($dumpedRoutes, $context);
+ }
+}
diff --git a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php
index 8545c2c29d83d..bb3f61864837f 100644
--- a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php
+++ b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php
@@ -26,7 +26,7 @@ public function testNoMethodSoAllowed()
$coll = new RouteCollection();
$coll->add('foo', new Route('/foo'));
- $matcher = new UrlMatcher($coll, new RequestContext());
+ $matcher = $this->getUrlMatcher($coll, new RequestContext());
$this->assertInternalType('array', $matcher->match('/foo'));
}
@@ -35,7 +35,7 @@ public function testMethodNotAllowed()
$coll = new RouteCollection();
$coll->add('foo', new Route('/foo', array(), array(), array(), '', array(), array('post')));
- $matcher = new UrlMatcher($coll, new RequestContext());
+ $matcher = $this->getUrlMatcher($coll, new RequestContext());
try {
$matcher->match('/foo');
@@ -50,7 +50,7 @@ public function testHeadAllowedWhenRequirementContainsGet()
$coll = new RouteCollection();
$coll->add('foo', new Route('/foo', array(), array(), array(), '', array(), array('get')));
- $matcher = new UrlMatcher($coll, new RequestContext('', 'head'));
+ $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'head'));
$this->assertInternalType('array', $matcher->match('/foo'));
}
@@ -60,7 +60,7 @@ public function testMethodNotAllowedAggregatesAllowedMethods()
$coll->add('foo1', new Route('/foo', array(), array(), array(), '', array(), array('post')));
$coll->add('foo2', new Route('/foo', array(), array(), array(), '', array(), array('put', 'delete')));
- $matcher = new UrlMatcher($coll, new RequestContext());
+ $matcher = $this->getUrlMatcher($coll, new RequestContext());
try {
$matcher->match('/foo');
@@ -75,7 +75,7 @@ public function testMatch()
// test the patterns are matched and parameters are returned
$collection = new RouteCollection();
$collection->add('foo', new Route('/foo/{bar}'));
- $matcher = new UrlMatcher($collection, new RequestContext());
+ $matcher = $this->getUrlMatcher($collection, new RequestContext());
try {
$matcher->match('/no-match');
$this->fail();
@@ -86,17 +86,17 @@ public function testMatch()
// test that defaults are merged
$collection = new RouteCollection();
$collection->add('foo', new Route('/foo/{bar}', array('def' => 'test')));
- $matcher = new UrlMatcher($collection, new RequestContext());
+ $matcher = $this->getUrlMatcher($collection, new RequestContext());
$this->assertEquals(array('_route' => 'foo', 'bar' => 'baz', 'def' => 'test'), $matcher->match('/foo/baz'));
// test that route "method" is ignored if no method is given in the context
$collection = new RouteCollection();
$collection->add('foo', new Route('/foo', array(), array(), array(), '', array(), array('get', 'head')));
- $matcher = new UrlMatcher($collection, new RequestContext());
+ $matcher = $this->getUrlMatcher($collection, new RequestContext());
$this->assertInternalType('array', $matcher->match('/foo'));
// route does not match with POST method context
- $matcher = new UrlMatcher($collection, new RequestContext('', 'post'));
+ $matcher = $this->getUrlMatcher($collection, new RequestContext('', 'post'));
try {
$matcher->match('/foo');
$this->fail();
@@ -104,28 +104,28 @@ public function testMatch()
}
// route does match with GET or HEAD method context
- $matcher = new UrlMatcher($collection, new RequestContext());
+ $matcher = $this->getUrlMatcher($collection, new RequestContext());
$this->assertInternalType('array', $matcher->match('/foo'));
- $matcher = new UrlMatcher($collection, new RequestContext('', 'head'));
+ $matcher = $this->getUrlMatcher($collection, new RequestContext('', 'head'));
$this->assertInternalType('array', $matcher->match('/foo'));
// route with an optional variable as the first segment
$collection = new RouteCollection();
$collection->add('bar', new Route('/{bar}/foo', array('bar' => 'bar'), array('bar' => 'foo|bar')));
- $matcher = new UrlMatcher($collection, new RequestContext());
+ $matcher = $this->getUrlMatcher($collection, new RequestContext());
$this->assertEquals(array('_route' => 'bar', 'bar' => 'bar'), $matcher->match('/bar/foo'));
$this->assertEquals(array('_route' => 'bar', 'bar' => 'foo'), $matcher->match('/foo/foo'));
$collection = new RouteCollection();
$collection->add('bar', new Route('/{bar}', array('bar' => 'bar'), array('bar' => 'foo|bar')));
- $matcher = new UrlMatcher($collection, new RequestContext());
+ $matcher = $this->getUrlMatcher($collection, new RequestContext());
$this->assertEquals(array('_route' => 'bar', 'bar' => 'foo'), $matcher->match('/foo'));
$this->assertEquals(array('_route' => 'bar', 'bar' => 'bar'), $matcher->match('/'));
// route with only optional variables
$collection = new RouteCollection();
$collection->add('bar', new Route('/{foo}/{bar}', array('foo' => 'foo', 'bar' => 'bar'), array()));
- $matcher = new UrlMatcher($collection, new RequestContext());
+ $matcher = $this->getUrlMatcher($collection, new RequestContext());
$this->assertEquals(array('_route' => 'bar', 'foo' => 'foo', 'bar' => 'bar'), $matcher->match('/'));
$this->assertEquals(array('_route' => 'bar', 'foo' => 'a', 'bar' => 'bar'), $matcher->match('/a'));
$this->assertEquals(array('_route' => 'bar', 'foo' => 'a', 'bar' => 'b'), $matcher->match('/a/b'));
@@ -138,7 +138,7 @@ public function testMatchWithPrefixes()
$collection->addPrefix('/b');
$collection->addPrefix('/a');
- $matcher = new UrlMatcher($collection, new RequestContext());
+ $matcher = $this->getUrlMatcher($collection, new RequestContext());
$this->assertEquals(array('_route' => 'foo', 'foo' => 'foo'), $matcher->match('/a/b/foo'));
}
@@ -149,7 +149,7 @@ public function testMatchWithDynamicPrefix()
$collection->addPrefix('/b');
$collection->addPrefix('/{_locale}');
- $matcher = new UrlMatcher($collection, new RequestContext());
+ $matcher = $this->getUrlMatcher($collection, new RequestContext());
$this->assertEquals(array('_locale' => 'fr', '_route' => 'foo', 'foo' => 'foo'), $matcher->match('/fr/b/foo'));
}
@@ -158,7 +158,7 @@ public function testMatchSpecialRouteName()
$collection = new RouteCollection();
$collection->add('$péß^a|', new Route('/bar'));
- $matcher = new UrlMatcher($collection, new RequestContext());
+ $matcher = $this->getUrlMatcher($collection, new RequestContext());
$this->assertEquals(array('_route' => '$péß^a|'), $matcher->match('/bar'));
}
@@ -168,7 +168,7 @@ public function testMatchNonAlpha()
$chars = '!"$%éà &\'()*+,./:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ\\[]^_`abcdefghijklmnopqrstuvwxyz{|}~-';
$collection->add('foo', new Route('/{foo}/bar', array(), array('foo' => '['.preg_quote($chars).']+'), array('utf8' => true)));
- $matcher = new UrlMatcher($collection, new RequestContext());
+ $matcher = $this->getUrlMatcher($collection, new RequestContext());
$this->assertEquals(array('_route' => 'foo', 'foo' => $chars), $matcher->match('/'.rawurlencode($chars).'/bar'));
$this->assertEquals(array('_route' => 'foo', 'foo' => $chars), $matcher->match('/'.strtr($chars, array('%' => '%25')).'/bar'));
}
@@ -178,7 +178,7 @@ public function testMatchWithDotMetacharacterInRequirements()
$collection = new RouteCollection();
$collection->add('foo', new Route('/{foo}/bar', array(), array('foo' => '.+')));
- $matcher = new UrlMatcher($collection, new RequestContext());
+ $matcher = $this->getUrlMatcher($collection, new RequestContext());
$this->assertEquals(array('_route' => 'foo', 'foo' => "\n"), $matcher->match('/'.urlencode("\n").'/bar'), 'linefeed character is matched');
}
@@ -192,7 +192,7 @@ public function testMatchOverriddenRoute()
$collection->addCollection($collection1);
- $matcher = new UrlMatcher($collection, new RequestContext());
+ $matcher = $this->getUrlMatcher($collection, new RequestContext());
$this->assertEquals(array('_route' => 'foo'), $matcher->match('/foo1'));
$this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Routing\Exception\ResourceNotFoundException');
@@ -205,12 +205,12 @@ public function testMatchRegression()
$coll->add('foo', new Route('/foo/{foo}'));
$coll->add('bar', new Route('/foo/bar/{foo}'));
- $matcher = new UrlMatcher($coll, new RequestContext());
+ $matcher = $this->getUrlMatcher($coll, new RequestContext());
$this->assertEquals(array('foo' => 'bar', '_route' => 'bar'), $matcher->match('/foo/bar/bar'));
$collection = new RouteCollection();
$collection->add('foo', new Route('/{bar}'));
- $matcher = new UrlMatcher($collection, new RequestContext());
+ $matcher = $this->getUrlMatcher($collection, new RequestContext());
try {
$matcher->match('/');
$this->fail();
@@ -223,7 +223,7 @@ public function testDefaultRequirementForOptionalVariables()
$coll = new RouteCollection();
$coll->add('test', new Route('/{page}.{_format}', array('page' => 'index', '_format' => 'html')));
- $matcher = new UrlMatcher($coll, new RequestContext());
+ $matcher = $this->getUrlMatcher($coll, new RequestContext());
$this->assertEquals(array('page' => 'my-page', '_format' => 'xml', '_route' => 'test'), $matcher->match('/my-page.xml'));
}
@@ -232,7 +232,7 @@ public function testMatchingIsEager()
$coll = new RouteCollection();
$coll->add('test', new Route('/{foo}-{bar}-', array(), array('foo' => '.+', 'bar' => '.+')));
- $matcher = new UrlMatcher($coll, new RequestContext());
+ $matcher = $this->getUrlMatcher($coll, new RequestContext());
$this->assertEquals(array('foo' => 'text1-text2-text3', 'bar' => 'text4', '_route' => 'test'), $matcher->match('/text1-text2-text3-text4-'));
}
@@ -241,7 +241,7 @@ public function testAdjacentVariables()
$coll = new RouteCollection();
$coll->add('test', new Route('/{w}{x}{y}{z}.{_format}', array('z' => 'default-z', '_format' => 'html'), array('y' => 'y|Y')));
- $matcher = new UrlMatcher($coll, new RequestContext());
+ $matcher = $this->getUrlMatcher($coll, new RequestContext());
// 'w' eagerly matches as much as possible and the other variables match the remaining chars.
// This also shows that the variables w-z must all exclude the separating char (the dot '.' in this case) by default requirement.
// Otherwise they would also consume '.xml' and _format would never match as it's an optional variable.
@@ -260,7 +260,7 @@ public function testOptionalVariableWithNoRealSeparator()
{
$coll = new RouteCollection();
$coll->add('test', new Route('/get{what}', array('what' => 'All')));
- $matcher = new UrlMatcher($coll, new RequestContext());
+ $matcher = $this->getUrlMatcher($coll, new RequestContext());
$this->assertEquals(array('what' => 'All', '_route' => 'test'), $matcher->match('/get'));
$this->assertEquals(array('what' => 'Sites', '_route' => 'test'), $matcher->match('/getSites'));
@@ -275,7 +275,7 @@ public function testRequiredVariableWithNoRealSeparator()
{
$coll = new RouteCollection();
$coll->add('test', new Route('/get{what}Suffix'));
- $matcher = new UrlMatcher($coll, new RequestContext());
+ $matcher = $this->getUrlMatcher($coll, new RequestContext());
$this->assertEquals(array('what' => 'Sites', '_route' => 'test'), $matcher->match('/getSitesSuffix'));
}
@@ -284,7 +284,7 @@ public function testDefaultRequirementOfVariable()
{
$coll = new RouteCollection();
$coll->add('test', new Route('/{page}.{_format}'));
- $matcher = new UrlMatcher($coll, new RequestContext());
+ $matcher = $this->getUrlMatcher($coll, new RequestContext());
$this->assertEquals(array('page' => 'index', '_format' => 'mobile.html', '_route' => 'test'), $matcher->match('/index.mobile.html'));
}
@@ -296,7 +296,7 @@ public function testDefaultRequirementOfVariableDisallowsSlash()
{
$coll = new RouteCollection();
$coll->add('test', new Route('/{page}.{_format}'));
- $matcher = new UrlMatcher($coll, new RequestContext());
+ $matcher = $this->getUrlMatcher($coll, new RequestContext());
$matcher->match('/index.sl/ash');
}
@@ -308,7 +308,7 @@ public function testDefaultRequirementOfVariableDisallowsNextSeparator()
{
$coll = new RouteCollection();
$coll->add('test', new Route('/{page}.{_format}', array(), array('_format' => 'html|xml')));
- $matcher = new UrlMatcher($coll, new RequestContext());
+ $matcher = $this->getUrlMatcher($coll, new RequestContext());
$matcher->match('/do.t.html');
}
@@ -320,7 +320,7 @@ public function testSchemeRequirement()
{
$coll = new RouteCollection();
$coll->add('foo', new Route('/foo', array(), array(), array(), '', array('https')));
- $matcher = new UrlMatcher($coll, new RequestContext());
+ $matcher = $this->getUrlMatcher($coll, new RequestContext());
$matcher->match('/foo');
}
@@ -333,7 +333,7 @@ public function testCondition()
$route = new Route('/foo');
$route->setCondition('context.getMethod() == "POST"');
$coll->add('foo', $route);
- $matcher = new UrlMatcher($coll, new RequestContext());
+ $matcher = $this->getUrlMatcher($coll, new RequestContext());
$matcher->match('/foo');
}
@@ -343,7 +343,7 @@ public function testRequestCondition()
$route = new Route('/foo/{bar}');
$route->setCondition('request.getBaseUrl() == "/sub/front.php" and request.getPathInfo() == "/foo/bar"');
$coll->add('foo', $route);
- $matcher = new UrlMatcher($coll, new RequestContext('/sub/front.php'));
+ $matcher = $this->getUrlMatcher($coll, new RequestContext('/sub/front.php'));
$this->assertEquals(array('bar' => 'bar', '_route' => 'foo'), $matcher->match('/foo/bar'));
}
@@ -352,7 +352,7 @@ public function testDecodeOnce()
$coll = new RouteCollection();
$coll->add('foo', new Route('/foo/{foo}'));
- $matcher = new UrlMatcher($coll, new RequestContext());
+ $matcher = $this->getUrlMatcher($coll, new RequestContext());
$this->assertEquals(array('foo' => 'bar%23', '_route' => 'foo'), $matcher->match('/foo/bar%2523'));
}
@@ -368,7 +368,7 @@ public function testCannotRelyOnPrefix()
$coll->addCollection($subColl);
- $matcher = new UrlMatcher($coll, new RequestContext());
+ $matcher = $this->getUrlMatcher($coll, new RequestContext());
$this->assertEquals(array('_route' => 'bar'), $matcher->match('/new'));
}
@@ -377,7 +377,7 @@ public function testWithHost()
$coll = new RouteCollection();
$coll->add('foo', new Route('/foo/{foo}', array(), array(), array(), '{locale}.example.com'));
- $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com'));
+ $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com'));
$this->assertEquals(array('foo' => 'bar', '_route' => 'foo', 'locale' => 'en'), $matcher->match('/foo/bar'));
}
@@ -388,10 +388,10 @@ public function testWithHostOnRouteCollection()
$coll->add('bar', new Route('/bar/{foo}', array(), array(), array(), '{locale}.example.net'));
$coll->setHost('{locale}.example.com');
- $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com'));
+ $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com'));
$this->assertEquals(array('foo' => 'bar', '_route' => 'foo', 'locale' => 'en'), $matcher->match('/foo/bar'));
- $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com'));
+ $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com'));
$this->assertEquals(array('foo' => 'bar', '_route' => 'bar', 'locale' => 'en'), $matcher->match('/bar/bar'));
}
@@ -403,7 +403,7 @@ public function testWithOutHostHostDoesNotMatch()
$coll = new RouteCollection();
$coll->add('foo', new Route('/foo/{foo}', array(), array(), array(), '{locale}.example.com'));
- $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'example.com'));
+ $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'GET', 'example.com'));
$matcher->match('/foo/bar');
}
@@ -415,7 +415,7 @@ public function testPathIsCaseSensitive()
$coll = new RouteCollection();
$coll->add('foo', new Route('/locale', array(), array('locale' => 'EN|FR|DE')));
- $matcher = new UrlMatcher($coll, new RequestContext());
+ $matcher = $this->getUrlMatcher($coll, new RequestContext());
$matcher->match('/en');
}
@@ -424,7 +424,7 @@ public function testHostIsCaseInsensitive()
$coll = new RouteCollection();
$coll->add('foo', new Route('/', array(), array('locale' => 'EN|FR|DE'), array(), '{locale}.example.com'));
- $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com'));
+ $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com'));
$this->assertEquals(array('_route' => 'foo', 'locale' => 'en'), $matcher->match('/'));
}
@@ -435,7 +435,36 @@ public function testNoConfiguration()
{
$coll = new RouteCollection();
- $matcher = new UrlMatcher($coll, new RequestContext());
+ $matcher = $this->getUrlMatcher($coll, new RequestContext());
$matcher->match('/');
}
+
+ public function testNestedCollections()
+ {
+ $coll = new RouteCollection();
+
+ $subColl = new RouteCollection();
+ $subColl->add('a', new Route('/a'));
+ $subColl->add('b', new Route('/b'));
+ $subColl->add('c', new Route('/c'));
+ $subColl->addPrefix('/p');
+ $coll->addCollection($subColl);
+
+ $coll->add('baz', new Route('/{baz}'));
+
+ $subColl = new RouteCollection();
+ $subColl->add('buz', new Route('/buz'));
+ $subColl->addPrefix('/prefix');
+ $coll->addCollection($subColl);
+
+ $matcher = $this->getUrlMatcher($coll, new RequestContext());
+ $this->assertEquals(array('_route' => 'a'), $matcher->match('/p/a'));
+ $this->assertEquals(array('_route' => 'baz', 'baz' => 'p'), $matcher->match('/p'));
+ $this->assertEquals(array('_route' => 'buz'), $matcher->match('/prefix/buz'));
+ }
+
+ protected function getUrlMatcher(RouteCollection $routes, RequestContext $context)
+ {
+ return new UrlMatcher($routes, $context);
+ }
}