8000 [Routing] added support for expression conditions in routes · symfony/symfony@d477f15 · GitHub
[go: up one dir, main page]

Skip to content

Commit d477f15

Browse files
committed
[Routing] added support for expression conditions in routes
1 parent 86ac8d7 commit d477f15

28 files changed

+255
-28
lines changed

src/Symfony/Component/Routing/Annotation/Route.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class Route
2828
private $host;
2929
private $methods;
3030
private $schemes;
31+
private $condition;
3132

3233
/**
3334
* Constructor.
@@ -153,4 +154,14 @@ public function getMethods()
153154
{
154155
return $this->methods;
155156
}
157+
158+
public function setCondition($condition)
159+
{
160+
$this->condition = $condition;
161+
}
162+
163+
public function getCondition()
164+
{
165+
return $this->condition;
166+
}
156167
}

src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ public function load($class, $type = null)
116116
'schemes' => array(),
117117
'methods' => array(),
118118
'host' => '',
119+
'condition' => '',
119120
);
120121

121122
$class = new \ReflectionClass($class);
@@ -154,6 +155,10 @@ public function load($class, $type = null)
154155
if (null !== $annot->getHost()) {
155156
$globals['host'] = $annot->getHost();
156157
}
158+
159+
if (null !== $annot->getCondition()) {
160+
$globals['condition'] = $annot->getCondition();
161+
}
157162
}
158163

159164
$collection = new RouteCollection();
@@ -194,7 +199,12 @@ protected function addRoute(RouteCollection $collection, $annot, $globals, \Refl
194199
$host = $globals['host'];
195200
}
196201

197-
$route = new Route($globals['path'].$annot->getPath(), $defaults, $requirements, $options, $host, $schemes, $methods);
202+
$condition = $annot->getCondition();
203+
if (null === $condition) {
204+
$condition = $globals['condition'];
205+
}
206+
207+
$route = new Route($globals['path'].$annot->getPath(), $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
198208

199209
$this->configureRoute($route, $class, $method, $annot);
200210

src/Symfony/Component/Routing/Loader/XmlFileLoader.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,9 @@ protected function parseRoute(RouteCollection $collection, \DOMElement $node, $p
129129
$schemes = preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY);
130130
$methods = preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY);
131131

132-
list($defaults, $requirements, $options) = $this->parseConfigs($node, $path);
132+
list($defaults, $requirements, $options, $condition) = $this->parseConfigs($node, $path);
133133

134-
$route = new Route($node->getAttribute('path'), $defaults, $requirements, $options, $node->getAttribute('host'), $schemes, $methods);
134+
$route = new Route($node->getAttribute('path'), $defaults, $requirements, $options, $node->getAttribute('host'), $schemes, $methods, $condition);
135135
$collection->add($id, $route);
136136
}
137137

@@ -157,7 +157,7 @@ protected function parseImport(RouteCollection $collection, \DOMElement $node, $
157157
$schemes = $node->hasAttribute('schemes') ? preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY) : null;
158158
$methods = $node->hasAttribute('methods') ? preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY) : null;
159159

160-
list($defaults, $requirements, $options) = $this->parseConfigs($node, $path);
160+
list($defaults, $requirements, $options, $condition) = $this->parseConfigs($node, $path);
161161

162162
$this->setCurrentDir(dirname($path));
163163

@@ -211,6 +211,7 @@ private function parseConfigs(\DOMElement $node, $path)
211211
$defaults = array();
212212
$requirements = array();
213213
$options = array();
214+
$condition = null;
214215

215216
foreach ($node->getElementsByTagNameNS(self::NAMESPACE_URI, '*') as $n) {
216217
switch ($n->localName) {
@@ -228,11 +229,14 @@ private function parseConfigs(\DOMElement $node, $path)
228229
case 'option':
229230
$options[$n->getAttribute('key')] = trim($n->textContent);
230231
break;
232+
case 'condition':
233+
$condition = trim($n->textContent);
234+
break;
231235
default:
232236
throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "default", "requirement" or "option".', $n->localName, $path));
233237
}
234238
}
235239

236-
return array($defaults, $requirements, $options);
240+
return array($defaults, $requirements, $options, $condition);
237241
}
238242
}

src/Symfony/Component/Routing/Loader/YamlFileLoader.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
class YamlFileLoader extends FileLoader
2929
{
3030
private static $availableKeys = array(
31-
'resource', 'type', 'prefix', 'pattern', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options',
31+
'resource', 'type', 'prefix', 'pattern', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition'
3232
);
3333
private $yamlParser;
3434

@@ -123,8 +123,9 @@ protected function parseRoute(RouteCollection $collection, $name, array $config,
123123
$host = isset($config['host']) ? $config['host'] : '';
124124
$schemes = isset($config['schemes']) ? $config['schemes'] : array();
125125
$methods = isset($config['methods']) ? $config['methods'] : array();
126+
$condition = isset($config['condition']) ? $config['condition'] : null;
126127

127-
$route = new Route($config['path'], $defaults, $requirements, $options, $host, $schemes, $methods);
128+
$route = new Route($config['path'], $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
128129

129130
$collection->add($name, $route);
130131
}

src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
<xsd:element name="default" nillable="true" type="element" />
3030
<xsd:element name="requirement" type="element" />
3131
<xsd:element name="option" type="element" />
32+
<xsd:element name="condition" type="condition" />
3233
</xsd:choice>
3334
</xsd:group>
3435

@@ -61,4 +62,9 @@
6162
</xsd:extension>
6263
</xsd:simpleContent>
6364
</xsd:complexType>
65+
66+
<xsd:simpleType name="condition">
67+
<xsd:restriction base="xsd:string">
68+
</xsd:restriction>
69+
</xsd:simpleType>
6470
</xsd:schema>

src/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ public function dump(array $options = array())
5050
$prevHostRegex = '';
5151

5252
foreach ($this->getRoutes()->all() as $name => $route) {
53+
if ($route->getCondition()) {
54+
throw new \LogicException(sprintf('Unable to dump the routes for Apache as route "%s" has a condition.', $name));
55+
}
5356

5457
$compiledRoute = $route->compile();
5558
$hostRegex = $compiledRoute->getHostRegex();

src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\Routing\Route;
1515
use Symfony\Component\Routing\RouteCollection;
16+
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
1617

1718
/**
1819
* PhpMatcherDumper creates a PHP class able to match URLs for a given set of routes.
@@ -23,6 +24,8 @@
2324
*/
2425
class PhpMatcherDumper extends MatcherDumper
2526
{
27+
private $expressionLanguage;
28+
2629
/**
2730
* Dumps a set of routes to a PHP class.
2831
*
@@ -91,6 +94,8 @@ public function match(\$pathinfo)
9194
{
9295
\$allow = array();
9396
\$pathinfo = rawurldecode(\$pathinfo);
97+
\$context = \$this->context;
98+
\$request = \$this->request;
9499
95100
$code
96101
@@ -237,6 +242,10 @@ private function compileRoute(Route $route, $name, $supportsRedirections, $paren
237242
$hostMatches = true;
238243
}
239244

245+
if ($route->getCondition()) {
246+
$conditions[] = $this->getExpressionLanguage()->compile($route->getCondition(), array('context', 'request'));
247+
}
248+
240249
$conditions = implode(' && ', $conditions);
241250

242251
$code .= <<<EOF
@@ -245,9 +254,8 @@ private function compileRoute(Route $route, $name, $supportsRedirections, $paren
245254
246255
EOF;
247256

257+
$gotoname = 'not_'.preg_replace('/[^A-Za-z0-9_]/', '', $name);
248258
if ($methods) {
249-
$gotoname = 'not_'.preg_replace('/[^A-Za-z0-9_]/', '', $name);
250-
251259
if (1 === count($methods)) {
252260
$code .= <<<EOF
253261
if (\$this->context->getMethod() != '$methods[0]') {
@@ -375,4 +383,16 @@ private function buildPrefixTree(DumperCollection $collection)
375383

376384
return $tree;
377385
}
386+
387+
private function getExpressionLanguage()
388+
{
389+
if (null === $this->expressionLanguage) {
390+
if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
391+
throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
392+
}
393+
$this->expressionLanguage = new ExpressionLanguage();
394+
}
395+
396+
return $this->expressionLanguage;
397+
}
378398
}

src/Symfony/Component/Routing/Matcher/RedirectableUrlMatcher.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ public function match($pathinfo)
5050
*/
5151
protected function handleRouteRequirements($pathinfo, $name, Route $route)
5252
{
53+
// expression condition
54+
if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), array('context' => $this->context, 'request' => $this->request))) {
55+
return array(self::REQUIREMENT_MISMATCH, null);
56+
}
57+
5358
// check HTTP scheme requirement
5459
$scheme = $route->getRequirement('_scheme');
5560
if ($scheme && $this->context->getScheme() !== $scheme) {

src/Symfony/Component/Routing/Matcher/TraceableUrlMatcher.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ protected function matchCollection($pathinfo, RouteCollection $routes)
9494
}
9595
}
9696

97+
// check condition
98+
if ($condition = $route->getCondition()) {
99+
if (!$this->getExpressionLanguage()->evaluate($condition, array('context' => $this->context, 'request' => $this->request))) {
100+
$this->addTrace(sprintf('Condition "%s" does not evaluate to "true"', $condition), self::ROUTE_ALMOST_MATCHES, $name, $route);
101+
102+
continue;
103+
}
104+
}
105+
97106
// check HTTP scheme requirement
98107
if ($scheme = $route->getRequirement('_scheme')) {
99108
if ($this->context->getScheme() !== $scheme) {

src/Symfony/Component/Routing/Matcher/UrlMatcher.php

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
use Symfony\Component\Routing\RouteCollection;
1717
use Symfony\Component\Routing\RequestContext;
1818
use Symfony\Component\Routing\Route;
19+
use Symfony\Component\HttpFoundation\Request;
20+
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
1921

2022
/**
2123
* UrlMatcher matches URL based on a set of routes.
@@ -24,7 +26,7 @@
2426
*
2527
* @api
2628
*/
27-
class UrlMatcher implements UrlMatcherInterface
29+
class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
2830
{
2931
const REQUIREMENT_MATCH = 0;
3032
const REQUIREMENT_MISMATCH = 1;
@@ -45,6 +47,9 @@ class UrlMatcher implements UrlMatcherInterface
4547
*/
4648
protected $routes;
4749

50+
protected $request;
51+
protected $expressionLanguage;
52+
4853
/**
4954
* Constructor.
5055
*
@@ -91,6 +96,20 @@ public function match($pathinfo)
9196
: new ResourceNotFoundException();
9297
}
9398

99+
/**
100+
* {@inheritdoc}
101+
*/
102+
public function matchRequest(Request $request)
103+
{
104+
$this->request = $request;
105+
106+
$ret = $this->match($request->getPathInfo());
107+
108+
$this->request = null;
109+
110+
return $ret;
111+
}
112+
94113
/**
95114
* Tries to match a URL with a set of routes.
96115
*
@@ -180,11 +199,18 @@ protected function getAttributes(Route $route, $name, array $attributes)
180199
*/
181200
protected function handleRouteRequirements($pathinfo, $name, Route $route)
182201
{
202+
// expression condition
203+
if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), array('context' => $this->context, 'request' => $this->request))) {
204+
return array(self::REQUIREMENT_MISMATCH, null);
205+
}
206+
183207
// check HTTP scheme requirement
184208
$scheme = $route->getRequirement('_scheme');
185-
$status = $scheme && $scheme !== $this->context->getScheme() ? self::REQUIREMENT_MISMATCH : self::REQUIREMENT_MATCH;
209+
if ($scheme && $scheme !== $this->context->getScheme()) {
210+
return array(self::REQUIREMENT_MISMATCH, null);
211+
}
186212

187-
return array($status, null);
213+
return array(self::REQUIREMENT_MATCH, null);
188214
}
189215

190216
/**
@@ -205,4 +231,16 @@ protected function mergeDefaults($params, $defaults)
205231

206232
return $defaults;
207233
}
234+
235+
protected function getExpressionLanguage()
236+
{
237+
if (null === $this->expressionLanguage) {
238+
if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
239+
throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
240+
}
241+
$this->expressionLanguage = new ExpressionLanguage();
242+
}
243+
244+
return $this->expressionLanguage;
245+
}
208246
}

0 commit comments

Comments
 (0)
0