From d477f157ce47e71937112ba15a7fbfd117027450 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 4 Sep 2013 09:25:51 +0200 Subject: [PATCH] [Routing] added support for expression conditions in routes --- .../Component/Routing/Annotation/Route.php | 11 +++++ .../Routing/Loader/AnnotationClassLoader.php | 12 ++++- .../Routing/Loader/XmlFileLoader.php | 12 +++-- .../Routing/Loader/YamlFileLoader.php | 5 ++- .../Loader/schema/routing/routing-1.0.xsd | 6 +++ .../Matcher/Dumper/ApacheMatcherDumper.php | 3 ++ .../Matcher/Dumper/PhpMatcherDumper.php | 24 +++++++++- .../Matcher/RedirectableUrlMatcher.php | 5 +++ .../Routing/Matcher/TraceableUrlMatcher.php | 9 ++++ .../Component/Routing/Matcher/UrlMatcher.php | 44 +++++++++++++++++-- src/Symfony/Component/Routing/Route.php | 35 ++++++++++++++- .../Component/Routing/RouteCollection.php | 14 ++++++ .../Routing/Tests/Annotation/RouteTest.php | 19 ++++---- .../Tests/Fixtures/dumper/url_matcher1.php | 2 + .../Tests/Fixtures/dumper/url_matcher2.php | 2 + .../Tests/Fixtures/dumper/url_matcher3.php | 7 +++ .../Routing/Tests/Fixtures/validpattern.php | 8 +++- .../Routing/Tests/Fixtures/validpattern.xml | 2 + .../Routing/Tests/Fixtures/validpattern.yml | 2 + .../Loader/AnnotationClassLoaderTest.php | 9 +++- .../Tests/Loader/XmlFileLoaderTest.php | 1 + .../Tests/Loader/YamlFileLoaderTest.php | 1 + .../Matcher/Dumper/PhpMatcherDumperTest.php | 3 ++ .../Tests/Matcher/TraceableUrlMatcherTest.php | 8 +++- .../Routing/Tests/Matcher/UrlMatcherTest.php | 13 ++++++ .../Routing/Tests/RouteCollectionTest.php | 14 ++++++ .../Component/Routing/Tests/RouteTest.php | 11 ++++- src/Symfony/Component/Routing/composer.json | 1 + 28 files changed, 255 insertions(+), 28 deletions(-) diff --git a/src/Symfony/Component/Routing/Annotation/Route.php b/src/Symfony/Component/Routing/Annotation/Route.php index abdbea27c6..f6100731dd 100644 --- a/src/Symfony/Component/Routing/Annotation/Route.php +++ b/src/Symfony/Component/Routing/Annotation/Route.php @@ -28,6 +28,7 @@ class Route private $host; private $methods; private $schemes; + private $condition; /** * Constructor. @@ -153,4 +154,14 @@ class Route { return $this->methods; } + + public function setCondition($condition) + { + $this->condition = $condition; + } + + public function getCondition() + { + return $this->condition; + } } diff --git a/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php b/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php index 7abab5a631..af70a888b8 100644 --- a/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php +++ b/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php @@ -116,6 +116,7 @@ abstract class AnnotationClassLoader implements LoaderInterface 'schemes' => array(), 'methods' => array(), 'host' => '', + 'condition' => '', ); $class = new \ReflectionClass($class); @@ -154,6 +155,10 @@ abstract class AnnotationClassLoader implements LoaderInterface if (null !== $annot->getHost()) { $globals['host'] = $annot->getHost(); } + + if (null !== $annot->getCondition()) { + $globals['condition'] = $annot->getCondition(); + } } $collection = new RouteCollection(); @@ -194,7 +199,12 @@ abstract class AnnotationClassLoader implements LoaderInterface $host = $globals['host']; } - $route = new Route($globals['path'].$annot->getPath(), $defaults, $requirements, $options, $host, $schemes, $methods); + $condition = $annot->getCondition(); + if (null === $condition) { + $condition = $globals['condition']; + } + + $route = new Route($globals['path'].$annot->getPath(), $defaults, $requirements, $options, $host, $schemes, $methods, $condition); $this->configureRoute($route, $class, $method, $annot); diff --git a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php index da7b33d856..e854202f70 100644 --- a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php @@ -129,9 +129,9 @@ class XmlFileLoader extends FileLoader $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) = $this->parseConfigs($node, $path); + list($defaults, $requirements, $options, $condition) = $this->parseConfigs($node, $path); - $route = new Route($node->getAttribute('path'), $defaults, $requirements, $options, $node->getAttribute('host'), $schemes, $methods); + $route = new Route($node->getAttribute('path'), $defaults, $requirements, $options, $node->getAttribute('host'), $schemes, $methods, $condition); $collection->add($id, $route); } @@ -157,7 +157,7 @@ class XmlFileLoader extends FileLoader $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; - list($defaults, $requirements, $options) = $this->parseConfigs($node, $path); + list($defaults, $requirements, $options, $condition) = $this->parseConfigs($node, $path); $this->setCurrentDir(dirname($path)); @@ -211,6 +211,7 @@ class XmlFileLoader extends FileLoader $defaults = array(); $requirements = array(); $options = array(); + $condition = null; foreach ($node->getElementsByTagNameNS(self::NAMESPACE_URI, '*') as $n) { switch ($n->localName) { @@ -228,11 +229,14 @@ class XmlFileLoader extends FileLoader case 'option': $options[$n->getAttribute('key')] = trim($n->textContent); break; + case 'condition': + $condition = trim($n->textContent); + break; default: throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "default", "requirement" or "option".', $n->localName, $path)); } } - return array($defaults, $requirements, $options); + return array($defaults, $requirements, $options, $condition); } } diff --git a/src/Symfony/Component/Routing/Loader/YamlFileLoader.php b/src/Symfony/Component/Routing/Loader/YamlFileLoader.php index 9deea7fe4f..d3eaea42e6 100644 --- a/src/Symfony/Component/Routing/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/Routing/Loader/YamlFileLoader.php @@ -28,7 +28,7 @@ use Symfony\Component\Config\Loader\FileLoader; class YamlFileLoader extends FileLoader { private static $availableKeys = array( - 'resource', 'type', 'prefix', 'pattern', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', + 'resource', 'type', 'prefix', 'pattern', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition' ); private $yamlParser; @@ -123,8 +123,9 @@ class YamlFileLoader extends FileLoader $host = isset($config['host']) ? $config['host'] : ''; $schemes = isset($config['schemes']) ? $config['schemes'] : array(); $methods = isset($config['methods']) ? $config['methods'] : array(); + $condition = isset($config['condition']) ? $config['condition'] : null; - $route = new Route($config['path'], $defaults, $requirements, $options, $host, $schemes, $methods); + $route = new Route($config['path'], $defaults, $requirements, $options, $host, $schemes, $methods, $condition); $collection->add($name, $route); } diff --git a/src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd b/src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd index daea814386..9ab969a41d 100644 --- a/src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd +++ b/src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd @@ -29,6 +29,7 @@ + @@ -61,4 +62,9 @@ + + + + + diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php index 01d8c03589..5b32684876 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php @@ -50,6 +50,9 @@ class ApacheMatcherDumper extends MatcherDumper $prevHostRegex = ''; foreach ($this->getRoutes()->all() as $name => $route) { + if ($route->getCondition()) { + throw new \LogicException(sprintf('Unable to dump the routes for Apache as route "%s" has a condition.', $name)); + } $compiledRoute = $route->compile(); $hostRegex = $compiledRoute->getHostRegex(); diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php index dc17ffbe98..a412fa7806 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Routing\Matcher\Dumper; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; /** * PhpMatcherDumper creates a PHP class able to match URLs for a given set of routes. @@ -23,6 +24,8 @@ use Symfony\Component\Routing\RouteCollection; */ class PhpMatcherDumper extends MatcherDumper { + private $expressionLanguage; + /** * Dumps a set of routes to a PHP class. * @@ -91,6 +94,8 @@ EOF; { \$allow = array(); \$pathinfo = rawurldecode(\$pathinfo); + \$context = \$this->context; + \$request = \$this->request; $code @@ -237,6 +242,10 @@ EOF; $hostMatches = true; } + if ($route->getCondition()) { + $conditions[] = $this->getExpressionLanguage()->compile($route->getCondition(), array('context', 'request')); + } + $conditions = implode(' && ', $conditions); $code .= <<context->getMethod() != '$methods[0]') { @@ -375,4 +383,16 @@ EOF; return $tree; } + + private function getExpressionLanguage() + { + if (null === $this->expressionLanguage) { + if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { + throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); + } + $this->expressionLanguage = new ExpressionLanguage(); + } + + return $this->expressionLanguage; + } } diff --git a/src/Symfony/Component/Routing/Matcher/RedirectableUrlMatcher.php b/src/Symfony/Component/Routing/Matcher/RedirectableUrlMatcher.php index 51e80057cd..56fcb604cc 100644 --- a/src/Symfony/Component/Routing/Matcher/RedirectableUrlMatcher.php +++ b/src/Symfony/Component/Routing/Matcher/RedirectableUrlMatcher.php @@ -50,6 +50,11 @@ abstract class RedirectableUrlMatcher extends UrlMatcher implements Redirectable */ protected function handleRouteRequirements($pathinfo, $name, Route $route) { + // expression condition + if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), array('context' => $this->context, 'request' => $this->request))) { + return array(self::REQUIREMENT_MISMATCH, null); + } + // check HTTP scheme requirement $scheme = $route->getRequirement('_scheme'); if ($scheme && $this->context->getScheme() !== $scheme) { diff --git a/src/Symfony/Component/Routing/Matcher/TraceableUrlMatcher.php b/src/Symfony/Component/Routing/Matcher/TraceableUrlMatcher.php index c09f83e86a..e70063b7b9 100644 --- a/src/Symfony/Component/Routing/Matcher/TraceableUrlMatcher.php +++ b/src/Symfony/Component/Routing/Matcher/TraceableUrlMatcher.php @@ -94,6 +94,15 @@ class TraceableUrlMatcher extends UrlMatcher } } + // check condition + if ($condition = $route->getCondition()) { + if (!$this->getExpressionLanguage()->evaluate($condition, array('context' => $this->context, 'request' => $this->request))) { + $this->addTrace(sprintf('Condition "%s" does not evaluate to "true"', $condition), self::ROUTE_ALMOST_MATCHES, $name, $route); + + continue; + } + } + // check HTTP scheme requirement if ($scheme = $route->getRequirement('_scheme')) { if ($this->context->getScheme() !== $scheme) { diff --git a/src/Symfony/Component/Routing/Matcher/UrlMatcher.php b/src/Symfony/Component/Routing/Matcher/UrlMatcher.php index db18ec4e7b..b4b7055db1 100644 --- a/src/Symfony/Component/Routing/Matcher/UrlMatcher.php +++ b/src/Symfony/Component/Routing/Matcher/UrlMatcher.php @@ -16,6 +16,8 @@ use Symfony\Component\Routing\Exception\ResourceNotFoundException; use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\RequestContext; use Symfony\Component\Routing\Route; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; /** * UrlMatcher matches URL based on a set of routes. @@ -24,7 +26,7 @@ use Symfony\Component\Routing\Route; * * @api */ -class UrlMatcher implements UrlMatcherInterface +class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface { const REQUIREMENT_MATCH = 0; const REQUIREMENT_MISMATCH = 1; @@ -45,6 +47,9 @@ class UrlMatcher implements UrlMatcherInterface */ protected $routes; + protected $request; + protected $expressionLanguage; + /** * Constructor. * @@ -91,6 +96,20 @@ class UrlMatcher implements UrlMatcherInterface : new ResourceNotFoundException(); } + /** + * {@inheritdoc} + */ + public function matchRequest(Request $request) + { + $this->request = $request; + + $ret = $this->match($request->getPathInfo()); + + $this->request = null; + + return $ret; + } + /** * Tries to match a URL with a set of routes. * @@ -180,11 +199,18 @@ class UrlMatcher implements UrlMatcherInterface */ protected function handleRouteRequirements($pathinfo, $name, Route $route) { + // expression condition + if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), array('context' => $this->context, 'request' => $this->request))) { + return array(self::REQUIREMENT_MISMATCH, null); + } + // check HTTP scheme requirement $scheme = $route->getRequirement('_scheme'); - $status = $scheme && $scheme !== $this->context->getScheme() ? self::REQUIREMENT_MISMATCH : self::REQUIREMENT_MATCH; + if ($scheme && $scheme !== $this->context->getScheme()) { + return array(self::REQUIREMENT_MISMATCH, null); + } - return array($status, null); + return array(self::REQUIREMENT_MATCH, null); } /** @@ -205,4 +231,16 @@ class UrlMatcher implements UrlMatcherInterface return $defaults; } + + protected function getExpressionLanguage() + { + if (null === $this->expressionLanguage) { + if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { + throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); + } + $this->expressionLanguage = new ExpressionLanguage(); + } + + return $this->expressionLanguage; + } } diff --git a/src/Symfony/Component/Routing/Route.php b/src/Symfony/Component/Routing/Route.php index 5bc535c683..aac654b01e 100644 --- a/src/Symfony/Component/Routing/Route.php +++ b/src/Symfony/Component/Routing/Route.php @@ -61,6 +61,8 @@ class Route implements \Serializable */ private $compiled; + private $condition; + /** * Constructor. * @@ -75,10 +77,11 @@ class Route implements \Serializable * @param string $host The host pattern to match * @param string|array $schemes A required URI scheme or an array of restricted schemes * @param string|array $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 * * @api */ - public function __construct($path, array $defaults = array(), array $requirements = array(), array $options = array(), $host = '', $schemes = array(), $methods = array()) + public function __construct($path, array $defaults = array(), array $requirements = array(), array $options = array(), $host = '', $schemes = array(), $methods = array(), $condition = null) { $this->setPath($path); $this->setDefaults($defaults); @@ -93,6 +96,7 @@ class Route implements \Serializable if ($methods) { $this->setMethods($methods); } + $this->setCondition($condition); } public function serialize() @@ -105,6 +109,7 @@ class Route implements \Serializable 'options' => $this->options, 'schemes' => $this->schemes, 'methods' => $this->methods, + 'condition' => $this->condition, )); } @@ -118,6 +123,7 @@ class Route implements \Serializable $this->options = $data['options']; $this->schemes = $data['schemes']; $this->methods = $data['methods']; + $this->condition = $data['condition']; } /** @@ -543,6 +549,33 @@ class Route implements \Serializable return $this; } + /** + * Returns the condition. + * + * @return string The condition + */ + public function getCondition() + { + return $this->condition; + } + + /** + * Sets the condition. + * + * This method implements a fluent interface. + * + * @param string $condition The condition + * + * @return Route The current Route instance + */ + public function setCondition($condition) + { + $this->condition = (string) $condition; + $this->compiled = null; + + return $this; + } + /** * Compiles the route. * diff --git a/src/Symfony/Component/Routing/RouteCollection.php b/src/Symfony/Component/Routing/RouteCollection.php index 499fe0f04f..f028324242 100644 --- a/src/Symfony/Component/Routing/RouteCollection.php +++ b/src/Symfony/Component/Routing/RouteCollection.php @@ -177,6 +177,20 @@ class RouteCollection implements \IteratorAggregate, \Countable } } + /** + * Sets a condition on all routes. + * + * Existing conditions will be overridden. + * + * @param string $condition The condition + */ + public function setCondition($condition) + { + foreach ($this->routes as $route) { + $route->setCondition($condition); + } + } + /** * Adds defaults to all routes. * diff --git a/src/Symfony/Component/Routing/Tests/Annotation/RouteTest.php b/src/Symfony/Component/Routing/Tests/Annotation/RouteTest.php index b58869f7af..bd0167089e 100644 --- a/src/Symfony/Component/Routing/Tests/Annotation/RouteTest.php +++ b/src/Symfony/Component/Routing/Tests/Annotation/RouteTest.php @@ -35,15 +35,16 @@ class RouteTest extends \PHPUnit_Framework_TestCase public function getValidParameters() { return array( - array('value', '/Blog', 'getPattern'), - array('value', '/Blog', 'getPath'), - array('requirements', array('_method' => 'GET'), 'getRequirements'), - array('options', array('compiler_class' => 'RouteCompiler'), 'getOptions'), - array('name', 'blog_index', 'getName'), - array('defaults', array('_controller' => 'MyBlogBundle:Blog:index'), 'getDefaults'), - array('schemes', array('https'), 'getSchemes'), - array('methods', array('GET', 'POST'), 'getMethods'), - array('host', array('{locale}.example.com'), 'getHost') + array('value', '/Blog', 'getPattern'), + array('value', '/Blog', 'getPath'), + array('requirements', array('_method' => 'GET'), 'getRequirements'), + array('options', array('compiler_class' => 'RouteCompiler'), 'getOptions'), + array('name', 'blog_index', 'getName'), + array('defaults', array('_controller' => 'MyBlogBundle:Blog:index'), 'getDefaults'), + array('schemes', array('https'), 'getSchemes'), + array('methods', array('GET', 'POST'), 'getMethods'), + array('host', array('{locale}.example.com'), 'getHost'), + array('condition', array('context.getMethod() == "GET"'), 'getCondition'), ); } } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php index e5f7665c81..248a4f153f 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php @@ -24,6 +24,8 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher { $allow = array(); $pathinfo = rawurldecode($pathinfo); + $context = $this->context; + $request = $this->request; // foo if (0 === strpos($pathinfo, '/foo') && preg_match('#^/foo/(?Pbaz|symfony)$#s', $pathinfo, $matches)) { diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php index ad157909b8..d7b99d67b9 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php @@ -24,6 +24,8 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec { $allow = array(); $pathinfo = rawurldecode($pathinfo); + $context = $this->context; + $request = $this->request; // foo if (0 === strpos($pathinfo, '/foo') && preg_match('#^/foo/(?Pbaz|symfony)$#s', $pathinfo, $matches)) { diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php index f2f642eb86..c3463fa772 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php @@ -24,6 +24,8 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher { $allow = array(); $pathinfo = rawurldecode($pathinfo); + $context = $this->context; + $request = $this->request; if (0 === strpos($pathinfo, '/rootprefix')) { // static @@ -38,6 +40,11 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher } + // with-condition + if ($pathinfo === '/with-condition' && ($context->getMethod() == "GET")) { + return array('_route' => 'with-condition'); + } + throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException(); } } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.php b/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.php index b8bbbb5f8f..b652d94656 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.php @@ -10,14 +10,18 @@ $collection->add('blog_show', new Route( array('compiler_class' => 'RouteCompiler'), '{locale}.example.com', array('https'), - array('GET','POST','put','OpTiOnS') + array('GET','POST','put','OpTiOnS'), + 'context.getMethod() == "GET"' )); $collection->add('blog_show_legacy', new Route( '/blog/{slug}', array('_controller' => 'MyBlogBundle:Blog:show'), array('_method' => 'GET|POST|put|OpTiOnS', '_scheme' => 'https', 'locale' => '\w+',), array('compiler_class' => 'RouteCompiler'), - '{locale}.example.com' + '{locale}.example.com', + array(), + array(), + 'context.getMethod() == "GET"' )); return $collection; diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.xml b/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.xml index b9f22347b4..cfee9d6882 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.xml +++ b/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.xml @@ -8,6 +8,7 @@ MyBundle:Blog:show \w+ + context.getMethod() == "GET" @@ -17,5 +18,6 @@ hTTps \w+ + context.getMethod() == "GET" diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.yml b/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.yml index 4ada883219..48cf7f8817 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.yml +++ b/src/Symfony/Component/Routing/Tests/Fixtures/validpattern.yml @@ -5,6 +5,7 @@ blog_show: requirements: { 'locale': '\w+' } methods: ['GET','POST','put','OpTiOnS'] schemes: ['https'] + condition: 'context.getMethod() == "GET"' options: compiler_class: RouteCompiler @@ -13,5 +14,6 @@ blog_show_legacy: defaults: { _controller: "MyBundle:Blog:show" } host: "{locale}.example.com" requirements: { '_method': 'GET|POST|put|OpTiOnS', _scheme: https, 'locale': '\w+' } + condition: 'context.getMethod() == "GET"' options: compiler_class: RouteCompiler diff --git a/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php index 5b7325c317..c60997902e 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php @@ -84,7 +84,12 @@ class AnnotationClassLoaderTest extends AbstractAnnotationLoaderTest array( 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass', array('name' => 'route1', 'defaults' => array('arg2' => 'foobar')), - array('arg2' => false, 'arg3' => 'defaultValue3') + array('arg2' => 'defaultValue2', 'arg3' =>'defaultValue3') + ), + array( + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass', + array('name' => 'route1', 'defaults' => array('arg2' => 'foo'), 'condition' => 'context.getMethod() == "GET"'), + array('arg2' => 'defaultValue2', 'arg3' =>'defaultValue3') ), ); } @@ -102,6 +107,7 @@ class AnnotationClassLoaderTest extends AbstractAnnotationLoaderTest 'defaults' => array(), 'schemes' => array(), 'methods' => array(), + 'condition' => null, ), $routeDatas); $this->reader @@ -116,6 +122,7 @@ class AnnotationClassLoaderTest extends AbstractAnnotationLoaderTest $this->assertSame($routeDatas['requirements'],$route->getRequirements(), '->load preserves requirements annotation'); $this->assertCount(0, array_intersect($route->getOptions(), $routeDatas['options']), '->load preserves options annotation'); $this->assertSame(array_replace($methodArgs, $routeDatas['defaults']), $route->getDefaults(), '->load preserves defaults annotation'); + $this->assertEquals($routeDatas['condition'], $route->getCondition(), '->load preserves condition annotation'); } private function getAnnotatedRoute($datas) diff --git a/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php index b678b3a8c8..c38adbd938 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php @@ -45,6 +45,7 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase $this->assertSame('RouteCompiler', $route->getOption('compiler_class')); $this->assertEquals(array('GET', 'POST', 'PUT', 'OPTIONS'), $route->getMethods()); $this->assertEquals(array('https'), $route->getSchemes()); + $this->assertEquals('context.getMethod() == "GET"', $route->getCondition()); } } diff --git a/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php index 1463326094..f030106205 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php @@ -79,6 +79,7 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase $this->assertSame('RouteCompiler', $route->getOption('compiler_class')); $this->assertEquals(array('GET', 'POST', 'PUT', 'OPTIONS'), $route->getMethods()); $this->assertEquals(array('https'), $route->getSchemes()); + $this->assertEquals('context.getMethod() == "GET"', $route->getCondition()); } } diff --git a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php index 542ede85c0..473af6aa5c 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php @@ -251,6 +251,9 @@ class PhpMatcherDumperTest extends \PHPUnit_Framework_TestCase $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); return array( array($collection, 'url_matcher1.php', array()), diff --git a/src/Symfony/Component/Routing/Tests/Matcher/TraceableUrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/TraceableUrlMatcherTest.php index 86d8d954c0..de6395226f 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/TraceableUrlMatcherTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/TraceableUrlMatcherTest.php @@ -26,13 +26,14 @@ class TraceableUrlMatcherTest extends \PHPUnit_Framework_TestCase $coll->add('bar1', new Route('/bar/{name}', array(), array('id' => '\w+', '_method' => 'POST'))); $coll->add('bar2', new Route('/foo', array(), array(), array(), 'baz')); $coll->add('bar3', new Route('/foo1', array(), array(), array(), 'baz')); + $coll->add('bar4', new Route('/foo2', array(), array(), array(), 'baz', array(), array(), 'context.getMethod() == "GET"')); $context = new RequestContext(); $context->setHost('baz'); $matcher = new TraceableUrlMatcher($coll, $context); $traces = $matcher->getTraces('/babar'); - $this->assertEquals(array(0, 0, 0, 0, 0), $this->getLevels($traces)); + $this->assertEquals(array(0, 0, 0, 0, 0, 0), $this->getLevels($traces)); $traces = $matcher->getTraces('/foo'); $this->assertEquals(array(1, 0, 0, 2), $this->getLevels($traces)); @@ -41,7 +42,7 @@ class TraceableUrlMatcherTest extends \PHPUnit_Framework_TestCase $this->assertEquals(array(0, 2), $this->getLevels($traces)); $traces = $matcher->getTraces('/bar/dd'); - $this->assertEquals(array(0, 1, 1, 0, 0), $this->getLevels($traces)); + $this->assertEquals(array(0, 1, 1, 0, 0, 0), $this->getLevels($traces)); $traces = $matcher->getTraces('/foo1'); $this->assertEquals(array(0, 0, 0, 0, 2), $this->getLevels($traces)); @@ -52,6 +53,9 @@ class TraceableUrlMatcherTest extends \PHPUnit_Framework_TestCase $traces = $matcher->getTraces('/bar/dd'); $this->assertEquals(array(0, 1, 2), $this->getLevels($traces)); + + $traces = $matcher->getTraces('/foo2'); + $this->assertEquals(array(0, 0, 0, 0, 0, 1), $this->getLevels($traces)); } public function getLevels($traces) diff --git a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php index 8a1428f170..b31cada8c0 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php @@ -321,6 +321,19 @@ class UrlMatcherTest extends \PHPUnit_Framework_TestCase $matcher->match('/foo'); } + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testCondition() + { + $coll = new RouteCollection(); + $route = new Route('/foo'); + $route->setCondition('context.getMethod() == "POST"'); + $coll->add('foo', $route); + $matcher = new UrlMatcher($coll, new RequestContext()); + $matcher->match('/foo'); + } + public function testDecodeOnce() { $coll = new RouteCollection(); diff --git a/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php b/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php index 94f2984368..027917bc1a 100644 --- a/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php +++ b/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php @@ -244,4 +244,18 @@ class RouteCollectionTest extends \PHPUnit_Framework_TestCase $this->assertEquals('{locale}.example.com', $routea->getHost()); $this->assertEquals('{locale}.example.com', $routeb->getHost()); } + + public function testSetCondition() + { + $collection = new RouteCollection(); + $routea = new Route('/a'); + $routeb = new Route('/b', array(), array(), array(), '{locale}.example.net', array(), array(), 'context.getMethod() == "GET"'); + $collection->add('a', $routea); + $collection->add('b', $routeb); + + $collection->setCondition('context.getMethod() == "POST"'); + + $this->assertEquals('context.getMethod() == "POST"', $routea->getCondition()); + $this->assertEquals('context.getMethod() == "POST"', $routeb->getCondition()); + } } diff --git a/src/Symfony/Component/Routing/Tests/RouteTest.php b/src/Symfony/Component/Routing/Tests/RouteTest.php index 47b87f33ab..d63bcbacf6 100644 --- a/src/Symfony/Component/Routing/Tests/RouteTest.php +++ b/src/Symfony/Component/Routing/Tests/RouteTest.php @@ -24,9 +24,10 @@ class RouteTest extends \PHPUnit_Framework_TestCase $this->assertEquals('bar', $route->getOption('foo'), '__construct() takes options as its fourth argument'); $this->assertEquals('{locale}.example.com', $route->getHost(), '__construct() takes a host pattern as its fifth argument'); - $route = new Route('/', array(), array(), array(), '', array('Https'), array('POST', 'put')); + $route = new Route('/', array(), array(), array(), '', array('Https'), array('POST', 'put'), 'context.getMethod() == "GET"'); $this->assertEquals(array('https'), $route->getSchemes(), '__construct() takes schemes as its sixth argument and lowercases it'); $this->assertEquals(array('POST', 'PUT'), $route->getMethods(), '__construct() takes methods as its seventh argument and uppercases it'); + $this->assertEquals('context.getMethod() == "GET"', $route->getCondition(), '__construct() takes a condition as its eight argument'); $route = new Route('/', array(), array(), array(), '', 'Https', 'Post'); $this->assertEquals(array('https'), $route->getSchemes(), '__construct() takes a single scheme as its sixth argument'); @@ -181,6 +182,14 @@ class RouteTest extends \PHPUnit_Framework_TestCase $this->assertNull($route->getRequirement('_method')); } + public function testCondition() + { + $route = new Route('/'); + $this->assertEquals(null, $route->getCondition()); + $route->setCondition('context.getMethod() == "GET"'); + $this->assertEquals('context.getMethod() == "GET"', $route->getCondition()); + } + public function testCompile() { $route = new Route('/{foo}'); diff --git a/src/Symfony/Component/Routing/composer.json b/src/Symfony/Component/Routing/composer.json index 0c476b3a68..67b9327834 100644 --- a/src/Symfony/Component/Routing/composer.json +++ b/src/Symfony/Component/Routing/composer.json @@ -21,6 +21,7 @@ "require-dev": { "symfony/config": "~2.2", "symfony/yaml": "~2.0", + "symfony/expression-language": "~2.4", "doctrine/common": "~2.2", "psr/log": "~1.0" },