Skip to content

Commit 8f62888

Browse files
[Routing] Add seamless support for unicode requirements
1 parent 904279e commit 8f62888

File tree

14 files changed

+266
-22
lines changed

14 files changed

+266
-22
lines changed

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

+11
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class Route
3030
private $methods = array();
3131
private $schemes = array();
3232
private $condition;
33+
private $utf8;
3334

3435
/**
3536
* Constructor.
@@ -143,4 +144,14 @@ public function getCondition()
143144
{
144145
return $this->condition;
145146
}
147+
148+
public function setUtf8($utf8)
149+
{
150+
$this->utf8 = $utf8;
151+
}
152+
153+
public function isUtf8()
154+
{
155+
return $this->utf8;
156+
}
146157
}

src/Symfony/Component/Routing/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CHANGELOG
55
-----
66

77
* Added support for `bool`, `int`, `float`, `string`, `list` and `map` defaults in XML configurations.
8+
* Added support for UTF-8 requirements
89

910
2.8.0
1011
-----

src/Symfony/Component/Routing/Generator/UrlGenerator.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ protected function doGenerate($variables, $defaults, $requirements, $tokens, $pa
158158
if ('variable' === $token[0]) {
159159
if (!$optional || !array_key_exists($token[3], $defaults) || null !== $mergedParams[$token[3]] && (string) $mergedParams[$token[3]] !== (string) $defaults[$token[3]]) {
160160
// check requirement
161-
if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#', $mergedParams[$token[3]])) {
161+
if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#'.(empty($token[4]) ? '' : 'u'), $mergedParams[$token[3]])) {
162162
if ($this->strictRequirements) {
163163
throw new InvalidParameterException(strtr($message, array('{parameter}' => $token[3], '{route}' => $name, '{expected}' => $token[2], '{given}' => $mergedParams[$token[3]])));
164164
}
@@ -212,7 +212,7 @@ protected function doGenerate($variables, $defaults, $requirements, $tokens, $pa
212212
$routeHost = '';
213213
foreach ($hostTokens as $token) {
214214
if ('variable' === $token[0]) {
215-
if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#i', $mergedParams[$token[3]])) {
215+
if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#i'.(empty($token[4]) ? '' : 'u'), $mergedParams[$token[3]])) {
216216
if ($this->strictRequirements) {
217217
throw new InvalidParameterException(strtr($message, array('{parameter}' => $token[3], '{route}' => $name, '{expected}' => $token[2], '{given}' => $mergedParams[$token[3]])));
218218
}

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

+13-3
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,12 @@ protected function addRoute(RouteCollection $collection, $annot, $globals, \Refl
158158
$condition = $globals['condition'];
159159
}
160160

161-
$route = $this->createRoute($globals['path'].$annot->getPath(), $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
161+
$utf8 = $annot->isUtf8();
162+
if (null === $utf8) {
163+
$utf8 = $globals['utf8'];
164+
}
165+
166+
$route = $this->createRoute($globals['path'].$annot->getPath(), $defaults, $requirements, $options, $host, $schemes, $methods, $condition, $utf8);
162167

163168
$this->configureRoute($route, $class, $method, $annot);
164169

@@ -217,6 +222,7 @@ protected function getGlobals(\ReflectionClass $class)
217222
'methods' => array(),
218223
'host' => '',
219224
'condition' => '',
225+
'utf8' => false,
220226
);
221227

222228
if ($annot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass)) {
@@ -251,14 +257,18 @@ protected function getGlobals(\ReflectionClass $class)
251257
if (null !== $annot->getCondition()) {
252258
$globals['condition'] = $annot->getCondition();
253259
}
260+
261+
if (null !== $annot->isUtf8()) {
262+
$globals['utf8'] = $annot->isUtf8();
263+
}
254264
}
255265

256266
return $globals;
257267
}
258268

259-
protected function createRoute($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition)
269+
protected function createRoute($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition, $utf8 = false)
260270
{
261-
return new Route($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
271+
return new Route($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition, $utf8);
262272
}
263273

264274
abstract protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, $annot);

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,11 @@ protected function parseRoute(RouteCollection $collection, \DOMElement $node, $p
113113

114114
$schemes = preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY);
115115
$methods = preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY);
116+
$utf8 = strtolower($node->getAttribute('utf8'));
116117

117118
list($defaults, $requirements, $options, $condition) = $this->parseConfigs($node, $path);
118119

119-
$route = new Route($node->getAttribute('path'), $defaults, $requirements, $options, $node->getAttribute('host'), $schemes, $methods, $condition);
120+
$route = new Route($node->getAttribute('path'), $defaults, $requirements, $options, $node->getAttribute('host'), $schemes, $methods, $condition, $utf8 && 'false' !== $utf8);
120121
$collection->add($id, $route);
121122
}
122123

@@ -141,6 +142,7 @@ protected function parseImport(RouteCollection $collection, \DOMElement $node, $
141142
$host = $node->hasAttribute('host') ? $node->getAttribute('host') : null;
142143
$schemes = $node->hasAttribute('schemes') ? preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY) : null;
143144
$methods = $node->hasAttribute('methods') ? preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY) : null;
145+
$utf8 = $node->getAttribute('utf8');
144146

145147
list($defaults, $requirements, $options, $condition) = $this->parseConfigs($node, $path);
146148

@@ -155,6 +157,9 @@ protected function parseImport(RouteCollection $collection, \DOMElement $node, $
155157
if (null !== $condition) {
156158
$subCollection->setCondition($condition);
157159
}
160+
if ($utf8 && 'false' !== strtolower($utf8)) {
161+
$subCollection->setUtf8(true);
162+
}
158163
if (null !== $schemes) {
159164
$subCollection->setSchemes($schemes);
160165
}

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

+6-4
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
class YamlFileLoader extends FileLoader
2828
{
2929
private static $availableKeys = array(
30-
'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition',
30+
'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', 'utf8',
3131
);
3232
private $yamlParser;
3333

@@ -114,8 +114,9 @@ protected function parseRoute(RouteCollection $collection, $name, array $config,
114114
$schemes = isset($config['schemes']) ? $config['schemes'] : array();
115115
$methods = isset($config['methods']) ? $config['methods'] : array();
116116
$condition = isset($config['condition']) ? $config['condition'] : null;
117+
$utf8 = isset($config['utf8']) ? $config['utf8'] : null;
117118

118-
$route = new Route($config['path'], $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
119+
$route = new Route($config['path'], $defaults, $requirements, $options, $host, $schemes, $methods, $condition, $utf8);
119120

120121
$collection->add($name, $route);
121122
}
@@ -139,6 +140,7 @@ protected function parseImport(RouteCollection $collection, array $config, $path
139140
$condition = isset($config['condition']) ? $config['condition'] : null;
140141
$schemes = isset($config['schemes']) ? $config['schemes'] : null;
141142
$methods = isset($config['methods']) ? $config['methods'] : null;
143+
$utf8 = isset($config['utf8']) ? $config['utf8'] : null;
142144

143145
$this->setCurrentDir(dirname($path));
144146

@@ -154,8 +156,8 @@ protected function parseImport(RouteCollection $collection, array $config, $path
154156
if (null !== $schemes) {
155157
$subCollection->setSchemes($schemes);
156158
}
157-
if (null !== $methods) {
158-
$subCollection->setMethods($methods);
159+
if ($utf8) {
160+
$subCollection->setUtf8(true);
159161
}
160162
$subCollection->addDefaults($defaults);
161163
$subCollection->addRequirements($requirements);

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

+2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
<xsd:attribute name="host" type="xsd:string" />
4242
<xsd:attribute name="schemes" type="xsd:string" />
4343
<xsd:attribute name="methods" type="xsd:string" />
44+
<xsd:attribute name="utf8" type="xsd:boolean" />
4445
</xsd:complexType>
4546

4647
<xsd:complexType name="import">
@@ -52,6 +53,7 @@
5253
<xsd:attribute name="host" type="xsd:string" />
5354
<xsd:attribute name="schemes" type="xsd:string" />
5455
<xsd:attribute name="methods" type="xsd:string" />
56+
<xsd:attribute name="utf8" type="xsd:boolean" />
5557
</xsd:complexType>
5658

5759
<xsd:complexType name="default" mixed="true">

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,9 @@ private function compileRoute(Route $route, $name, $supportsRedirections, $paren
223223
}
224224

225225
$supportsTrailingSlash = $supportsRedirections && (!$methods || in_array('HEAD', $methods));
226+
$regex = $compiledRoute->getRegex();
226227

227-
if (!count($compiledRoute->getPathVariables()) && false !== preg_match('#^(.)\^(?P<url>.*?)\$\1#', $compiledRoute->getRegex(), $m)) {
228+
if (!count($compiledRoute->getPathVariables()) && false !== preg_match('#^(.)\^(?P<url>.*?)\$\1#'.(substr($regex, -1) === 'u' ? 'u' : ''), $regex, $m)) {
228229
if ($supportsTrailingSlash && substr($m['url'], -1) === '/') {
229230
$conditions[] = sprintf("rtrim(\$pathinfo, '/') === %s", var_export(rtrim(str_replace('\\', '', $m['url']), '/'), true));
230231
$hasTrailingSlash = true;
@@ -236,7 +237,6 @@ private function compileRoute(Route $route, $name, $supportsRedirections, $paren
236237
$conditions[] = sprintf('0 === strpos($pathinfo, %s)', var_export($compiledRoute->getStaticPrefix(), true));
237238
}
238239

239-
$regex = $compiledRoute->getRegex();
240240
if ($supportsTrailingSlash && $pos = strpos($regex, '/$')) {
241241
$regex = substr($regex, 0, $pos).'/?$'.substr($regex, $pos + 2);
242242
$hasTrailingSlash = true;

src/Symfony/Component/Routing/Route.php

+39-1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ class Route implements \Serializable
6464
*/
6565
private $condition = '';
6666

67+
/**
68+
* @var bool
69+
*/
70+
private $utf8 = false;
71+
6772
/**
6873
* Constructor.
6974
*
@@ -79,8 +84,9 @@ class Route implements \Serializable
7984
* @param string|array $schemes A required URI scheme or an array of restricted schemes
8085
* @param string|array $methods A required HTTP method or an array of restricted methods
8186
* @param string $condition A condition that should evaluate to true for the route to match
87+
* @param bool $utf8 Whether UTF-8 matching is enforced ot not
8288
*/
83-
public function __construct($path, array $defaults = array(), array $requirements = array(), array $options = array(), $host = '', $schemes = array(), $methods = array(), $condition = '')
89+
public function __construct($path, array $defaults = array(), array $requirements = array(), array $options = array(), $host = '', $schemes = array(), $methods = array(), $condition = '', $utf8 = false)
8490
{
8591
$this->setPath($path);
8692
$this->setDefaults($defaults);
@@ -90,6 +96,7 @@ public function __construct($path, array $defaults = array(), array $requirement
9096
$this->setSchemes($schemes);
9197
$this->setMethods($methods);
9298
$this->setCondition($condition);
99+
$this->setUtf8($utf8);
93100
}
94101

95102
/**
@@ -107,6 +114,7 @@ public function serialize()
107114
'methods' => $this->methods,
108115
'condition' => $this->condition,
109116
'compiled' => $this->compiled,
117+
'utf8' => $this->utf8,
110118
));
111119
}
112120

@@ -130,6 +138,9 @@ public function unserialize($serialized)
130138
if (isset($data['compiled'])) {
131139
$this->compiled = $data['compiled'];
132140
}
141+
if (isset($data['utf8'])) {
142+
$this->utf8 = $data['utf8'];
143+
}
133144
}
134145

135146
/**
@@ -544,6 +555,33 @@ public function setCondition($condition)
544555
return $this;
545556
}
546557

558+
/**
559+
* Returns the UTF-8 enforcement status.
560+
*
561+
* @return bool Whether UTF-8 matching is enforced or not
562+
*/
563+
public function isUtf8()
564+
{
565+
return $this->utf8;
566+
}
567+
568+
/**
569+
* Sets the UTF-8 enforcement status.
570+
*
571+
* This method implements a fluent interface.
572+
*
573+
* @param bool Whether UTF-8 matching is enforced or not
574+
*
575+
* @return Route The current Route instance
576+
*/
577+
public function setUtf8($utf8)
578+
{
579+
$this->utf8 = (bool) $utf8;
580+
$this->compiled = null;
581+
582+
return $this;
583+
}
584+
547585
/**
548586
* Compiles the route.
549587
*

src/Symfony/Component/Routing/RouteCollection.php

+14
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,20 @@ public function addDefaults(array $defaults)
199199
}
200200
}
201201

202+
/**
203+
* Enforces UTF-8 matching on all routes.
204+
*
205+
* Existing settings will be overridden.
206+
*
207+
* @param bool $utf8 Whether UTF-8 matching is enforced or not
208+
*/
209+
public function setUtf8($utf8)
210+
{
211+
foreach ($this->routes as $route) {
212+
$route->setUtf8($utf8);
213+
}
214+
}
215+
202216
/**
203217
* Adds requirements to all routes.
204218
*

0 commit comments

Comments
 (0)