aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper')
-rw-r--r--vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php252
-rw-r--r--vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php159
-rw-r--r--vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/DumperPrefixCollection.php108
-rw-r--r--vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/DumperRoute.php64
-rw-r--r--vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/MatcherDumper.php45
-rw-r--r--vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/MatcherDumperInterface.php37
-rw-r--r--vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php378
7 files changed, 0 insertions, 1043 deletions
diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php
deleted file mode 100644
index 804da19d..00000000
--- a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php
+++ /dev/null
@@ -1,252 +0,0 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Routing\Matcher\Dumper;
13
14use Symfony\Component\Routing\Route;
15
16/**
17 * Dumps a set of Apache mod_rewrite rules.
18 *
19 * @author Fabien Potencier <fabien@symfony.com>
20 * @author Kris Wallsmith <kris@symfony.com>
21 */
22class ApacheMatcherDumper extends MatcherDumper
23{
24 /**
25 * Dumps a set of Apache mod_rewrite rules.
26 *
27 * Available options:
28 *
29 * * script_name: The script name (app.php by default)
30 * * base_uri: The base URI ("" by default)
31 *
32 * @param array $options An array of options
33 *
34 * @return string A string to be used as Apache rewrite rules
35 *
36 * @throws \LogicException When the route regex is invalid
37 */
38 public function dump(array $options = array())
39 {
40 $options = array_merge(array(
41 'script_name' => 'app.php',
42 'base_uri' => '',
43 ), $options);
44
45 $options['script_name'] = self::escape($options['script_name'], ' ', '\\');
46
47 $rules = array("# skip \"real\" requests\nRewriteCond %{REQUEST_FILENAME} -f\nRewriteRule .* - [QSA,L]");
48 $methodVars = array();
49 $hostRegexUnique = 0;
50 $prevHostRegex = '';
51
52 foreach ($this->getRoutes()->all() as $name => $route) {
53
54 $compiledRoute = $route->compile();
55 $hostRegex = $compiledRoute->getHostRegex();
56
57 if (null !== $hostRegex && $prevHostRegex !== $hostRegex) {
58 $prevHostRegex = $hostRegex;
59 $hostRegexUnique++;
60
61 $rule = array();
62
63 $regex = $this->regexToApacheRegex($hostRegex);
64 $regex = self::escape($regex, ' ', '\\');
65
66 $rule[] = sprintf('RewriteCond %%{HTTP:Host} %s', $regex);
67
68 $variables = array();
69 $variables[] = sprintf('E=__ROUTING_host_%s:1', $hostRegexUnique);
70
71 foreach ($compiledRoute->getHostVariables() as $i => $variable) {
72 $variables[] = sprintf('E=__ROUTING_host_%s_%s:%%%d', $hostRegexUnique, $variable, $i+1);
73 }
74
75 $variables = implode(',', $variables);
76
77 $rule[] = sprintf('RewriteRule .? - [%s]', $variables);
78
79 $rules[] = implode("\n", $rule);
80 }
81
82 $rules[] = $this->dumpRoute($name, $route, $options, $hostRegexUnique);
83
84 if ($req = $route->getRequirement('_method')) {
85 $methods = explode('|', strtoupper($req));
86 $methodVars = array_merge($methodVars, $methods);
87 }
88 }
89 if (0 < count($methodVars)) {
90 $rule = array('# 405 Method Not Allowed');
91 $methodVars = array_values(array_unique($methodVars));
92 if (in_array('GET', $methodVars) && !in_array('HEAD', $methodVars)) {
93 $methodVars[] = 'HEAD';
94 }
95 foreach ($methodVars as $i => $methodVar) {
96 $rule[] = sprintf('RewriteCond %%{ENV:_ROUTING__allow_%s} =1%s', $methodVar, isset($methodVars[$i + 1]) ? ' [OR]' : '');
97 }
98 $rule[] = sprintf('RewriteRule .* %s [QSA,L]', $options['script_name']);
99
100 $rules[] = implode("\n", $rule);
101 }
102
103 return implode("\n\n", $rules)."\n";
104 }
105
106 /**
107 * Dumps a single route
108 *
109 * @param string $name Route name
110 * @param Route $route The route
111 * @param array $options Options
112 * @param bool $hostRegexUnique Unique identifier for the host regex
113 *
114 * @return string The compiled route
115 */
116 private function dumpRoute($name, $route, array $options, $hostRegexUnique)
117 {
118 $compiledRoute = $route->compile();
119
120 // prepare the apache regex
121 $regex = $this->regexToApacheRegex($compiledRoute->getRegex());
122 $regex = '^'.self::escape(preg_quote($options['base_uri']).substr($regex, 1), ' ', '\\');
123
124 $methods = $this->getRouteMethods($route);
125
126 $hasTrailingSlash = (!$methods || in_array('HEAD', $methods)) && '/$' === substr($regex, -2) && '^/$' !== $regex;
127
128 $variables = array('E=_ROUTING_route:'.$name);
129 foreach ($compiledRoute->getHostVariables() as $variable) {
130 $variables[] = sprintf('E=_ROUTING_param_%s:%%{ENV:__ROUTING_host_%s_%s}', $variable, $hostRegexUnique, $variable);
131 }
132 foreach ($compiledRoute->getPathVariables() as $i => $variable) {
133 $variables[] = 'E=_ROUTING_param_'.$variable.':%'.($i + 1);
134 }
135 foreach ($route->getDefaults() as $key => $value) {
136 $variables[] = 'E=_ROUTING_default_'.$key.':'.strtr($value, array(
137 ':' => '\\:',
138 '=' => '\\=',
139 '\\' => '\\\\',
140 ' ' => '\\ ',
141 ));
142 }
143 $variables = implode(',', $variables);
144
145 $rule = array("# $name");
146
147 // method mismatch
148 if (0 < count($methods)) {
149 $allow = array();
150 foreach ($methods as $method) {
151 $allow[] = 'E=_ROUTING_allow_'.$method.':1';
152 }
153
154 if ($hostRegex = $compiledRoute->getHostRegex()) {
155 $rule[] = sprintf("RewriteCond %%{ENV:__ROUTING_host_%s} =1", $hostRegexUnique);
156 }
157
158 $rule[] = "RewriteCond %{REQUEST_URI} $regex";
159 $rule[] = sprintf("RewriteCond %%{REQUEST_METHOD} !^(%s)$ [NC]", implode('|', $methods));
160 $rule[] = sprintf('RewriteRule .* - [S=%d,%s]', $hasTrailingSlash ? 2 : 1, implode(',', $allow));
161 }
162
163 // redirect with trailing slash appended
164 if ($hasTrailingSlash) {
165
166 if ($hostRegex = $compiledRoute->getHostRegex()) {
167 $rule[] = sprintf("RewriteCond %%{ENV:__ROUTING_host_%s} =1", $hostRegexUnique);
168 }
169
170 $rule[] = 'RewriteCond %{REQUEST_URI} '.substr($regex, 0, -2).'$';
171 $rule[] = 'RewriteRule .* $0/ [QSA,L,R=301]';
172 }
173
174 // the main rule
175
176 if ($hostRegex = $compiledRoute->getHostRegex()) {
177 $rule[] = sprintf("RewriteCond %%{ENV:__ROUTING_host_%s} =1", $hostRegexUnique);
178 }
179
180 $rule[] = "RewriteCond %{REQUEST_URI} $regex";
181 $rule[] = "RewriteRule .* {$options['script_name']} [QSA,L,$variables]";
182
183 return implode("\n", $rule);
184 }
185
186 /**
187 * Returns methods allowed for a route
188 *
189 * @param Route $route The route
190 *
191 * @return array The methods
192 */
193 private function getRouteMethods(Route $route)
194 {
195 $methods = array();
196 if ($req = $route->getRequirement('_method')) {
197 $methods = explode('|', strtoupper($req));
198 // GET and HEAD are equivalent
199 if (in_array('GET', $methods) && !in_array('HEAD', $methods)) {
200 $methods[] = 'HEAD';
201 }
202 }
203
204 return $methods;
205 }
206
207 /**
208 * Converts a regex to make it suitable for mod_rewrite
209 *
210 * @param string $regex The regex
211 *
212 * @return string The converted regex
213 */
214 private function regexToApacheRegex($regex)
215 {
216 $regexPatternEnd = strrpos($regex, $regex[0]);
217
218 return preg_replace('/\?P<.+?>/', '', substr($regex, 1, $regexPatternEnd - 1));
219 }
220
221 /**
222 * Escapes a string.
223 *
224 * @param string $string The string to be escaped
225 * @param string $char The character to be escaped
226 * @param string $with The character to be used for escaping
227 *
228 * @return string The escaped string
229 */
230 private static function escape($string, $char, $with)
231 {
232 $escaped = false;
233 $output = '';
234 foreach (str_split($string) as $symbol) {
235 if ($escaped) {
236 $output .= $symbol;
237 $escaped = false;
238 continue;
239 }
240 if ($symbol === $char) {
241 $output .= $with.$char;
242 continue;
243 }
244 if ($symbol === $with) {
245 $escaped = true;
246 }
247 $output .= $symbol;
248 }
249
250 return $output;
251 }
252}
diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php
deleted file mode 100644
index 612ac0d2..00000000
--- a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php
+++ /dev/null
@@ -1,159 +0,0 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Routing\Matcher\Dumper;
13
14/**
15 * Collection of routes.
16 *
17 * @author Arnaud Le Blanc <arnaud.lb@gmail.com>
18 */
19class DumperCollection implements \IteratorAggregate
20{
21 /**
22 * @var DumperCollection|null
23 */
24 private $parent;
25
26 /**
27 * @var (DumperCollection|DumperRoute)[]
28 */
29 private $children = array();
30
31 /**
32 * @var array
33 */
34 private $attributes = array();
35
36 /**
37 * Returns the children routes and collections.
38 *
39 * @return (DumperCollection|DumperRoute)[] Array of DumperCollection|DumperRoute
40 */
41 public function all()
42 {
43 return $this->children;
44 }
45
46 /**
47 * Adds a route or collection
48 *
49 * @param DumperRoute|DumperCollection The route or collection
50 */
51 public function add($child)
52 {
53 if ($child instanceof DumperCollection) {
54 $child->setParent($this);
55 }
56 $this->children[] = $child;
57 }
58
59 /**
60 * Sets children.
61 *
62 * @param array $children The children
63 */
64 public function setAll(array $children)
65 {
66 foreach ($children as $child) {
67 if ($child instanceof DumperCollection) {
68 $child->setParent($this);
69 }
70 }
71 $this->children = $children;
72 }
73
74 /**
75 * Returns an iterator over the children.
76 *
77 * @return \Iterator The iterator
78 */
79 public function getIterator()
80 {
81 return new \ArrayIterator($this->children);
82 }
83
84 /**
85 * Returns the root of the collection.
86 *
87 * @return DumperCollection The root collection
88 */
89 public function getRoot()
90 {
91 return (null !== $this->parent) ? $this->parent->getRoot() : $this;
92 }
93
94 /**
95 * Returns the parent collection.
96 *
97 * @return DumperCollection|null The parent collection or null if the collection has no parent
98 */
99 protected function getParent()
100 {
101 return $this->parent;
102 }
103
104 /**
105 * Sets the parent collection.
106 *
107 * @param DumperCollection $parent The parent collection
108 */
109 protected function setParent(DumperCollection $parent)
110 {
111 $this->parent = $parent;
112 }
113
114 /**
115 * Returns true if the attribute is defined.
116 *
117 * @param string $name The attribute name
118 *
119 * @return Boolean true if the attribute is defined, false otherwise
120 */
121 public function hasAttribute($name)
122 {
123 return array_key_exists($name, $this->attributes);
124 }
125
126 /**
127 * Returns an attribute by name.
128 *
129 * @param string $name The attribute name
130 * @param mixed $default Default value is the attribute doesn't exist
131 *
132 * @return mixed The attribute value
133 */
134 public function getAttribute($name, $default = null)
135 {
136 return $this->hasAttribute($name) ? $this->attributes[$name] : $default;
137 }
138
139 /**
140 * Sets an attribute by name.
141 *
142 * @param string $name The attribute name
143 * @param mixed $value The attribute value
144 */
145 public function setAttribute($name, $value)
146 {
147 $this->attributes[$name] = $value;
148 }
149
150 /**
151 * Sets multiple attributes.
152 *
153 * @param array $attributes The attributes
154 */
155 public function setAttributes($attributes)
156 {
157 $this->attributes = $attributes;
158 }
159}
diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/DumperPrefixCollection.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/DumperPrefixCollection.php
deleted file mode 100644
index 26382b0b..00000000
--- a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/DumperPrefixCollection.php
+++ /dev/null
@@ -1,108 +0,0 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Routing\Matcher\Dumper;
13
14/**
15 * Prefix tree of routes preserving routes order.
16 *
17 * @author Arnaud Le Blanc <arnaud.lb@gmail.com>
18 */
19class DumperPrefixCollection extends DumperCollection
20{
21 /**
22 * @var string
23 */
24 private $prefix = '';
25
26 /**
27 * Returns the prefix.
28 *
29 * @return string The prefix
30 */
31 public function getPrefix()
32 {
33 return $this->prefix;
34 }
35
36 /**
37 * Sets the prefix.
38 *
39 * @param string $prefix The prefix
40 */
41 public function setPrefix($prefix)
42 {
43 $this->prefix = $prefix;
44 }
45
46 /**
47 * Adds a route in the tree.
48 *
49 * @param DumperRoute $route The route
50 *
51 * @return DumperPrefixCollection The node the route was added to
52 *
53 * @throws \LogicException
54 */
55 public function addPrefixRoute(DumperRoute $route)
56 {
57 $prefix = $route->getRoute()->compile()->getStaticPrefix();
58
59 // Same prefix, add to current leave
60 if ($this->prefix === $prefix) {
61 $this->add($route);
62
63 return $this;
64 }
65
66 // Prefix starts with route's prefix
67 if ('' === $this->prefix || 0 === strpos($prefix, $this->prefix)) {
68 $collection = new DumperPrefixCollection();
69 $collection->setPrefix(substr($prefix, 0, strlen($this->prefix)+1));
70 $this->add($collection);
71
72 return $collection->addPrefixRoute($route);
73 }
74
75 // No match, fallback to parent (recursively)
76
77 if (null === $parent = $this->getParent()) {
78 throw new \LogicException("The collection root must not have a prefix");
79 }
80
81 return $parent->addPrefixRoute($route);
82 }
83
84 /**
85 * Merges nodes whose prefix ends with a slash
86 *
87 * Children of a node whose prefix ends with a slash are moved to the parent node
88 */
89 public function mergeSlashNodes()
90 {
91 $children = array();
92
93 foreach ($this as $child) {
94 if ($child instanceof self) {
95 $child->mergeSlashNodes();
96 if ('/' === substr($child->prefix, -1)) {
97 $children = array_merge($children, $child->all());
98 } else {
99 $children[] = $child;
100 }
101 } else {
102 $children[] = $child;
103 }
104 }
105
106 $this->setAll($children);
107 }
108}
diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/DumperRoute.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/DumperRoute.php
deleted file mode 100644
index 2928cdcc..00000000
--- a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/DumperRoute.php
+++ /dev/null
@@ -1,64 +0,0 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Routing\Matcher\Dumper;
13
14use Symfony\Component\Routing\Route;
15
16/**
17 * Container for a Route.
18 *
19 * @author Arnaud Le Blanc <arnaud.lb@gmail.com>
20 */
21class DumperRoute
22{
23 /**
24 * @var string
25 */
26 private $name;
27
28 /**
29 * @var Route
30 */
31 private $route;
32
33 /**
34 * Constructor.
35 *
36 * @param string $name The route name
37 * @param Route $route The route
38 */
39 public function __construct($name, Route $route)
40 {
41 $this->name = $name;
42 $this->route = $route;
43 }
44
45 /**
46 * Returns the route name.
47 *
48 * @return string The route name
49 */
50 public function getName()
51 {
52 return $this->name;
53 }
54
55 /**
56 * Returns the route.
57 *
58 * @return Route The route
59 */
60 public function getRoute()
61 {
62 return $this->route;
63 }
64}
diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/MatcherDumper.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/MatcherDumper.php
deleted file mode 100644
index 52edc017..00000000
--- a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/MatcherDumper.php
+++ /dev/null
@@ -1,45 +0,0 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Routing\Matcher\Dumper;
13
14use Symfony\Component\Routing\RouteCollection;
15
16/**
17 * MatcherDumper is the abstract class for all built-in matcher dumpers.
18 *
19 * @author Fabien Potencier <fabien@symfony.com>
20 */
21abstract class MatcherDumper implements MatcherDumperInterface
22{
23 /**
24 * @var RouteCollection
25 */
26 private $routes;
27
28 /**
29 * Constructor.
30 *
31 * @param RouteCollection $routes The RouteCollection to dump
32 */
33 public function __construct(RouteCollection $routes)
34 {
35 $this->routes = $routes;
36 }
37
38 /**
39 * {@inheritdoc}
40 */
41 public function getRoutes()
42 {
43 return $this->routes;
44 }
45}
diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/MatcherDumperInterface.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/MatcherDumperInterface.php
deleted file mode 100644
index f85e4cef..00000000
--- a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/MatcherDumperInterface.php
+++ /dev/null
@@ -1,37 +0,0 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Routing\Matcher\Dumper;
13
14/**
15 * MatcherDumperInterface is the interface that all matcher dumper classes must implement.
16 *
17 * @author Fabien Potencier <fabien@symfony.com>
18 */
19interface MatcherDumperInterface
20{
21 /**
22 * Dumps a set of routes to a string representation of executable code
23 * that can then be used to match a request against these routes.
24 *
25 * @param array $options An array of options
26 *
27 * @return string Executable code
28 */
29 public function dump(array $options = array());
30
31 /**
32 * Gets the routes to dump.
33 *
34 * @return RouteCollection A RouteCollection instance
35 */
36 public function getRoutes();
37}
diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php
deleted file mode 100644
index dc17ffbe..00000000
--- a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php
+++ /dev/null
@@ -1,378 +0,0 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Routing\Matcher\Dumper;
13
14use Symfony\Component\Routing\Route;
15use Symfony\Component\Routing\RouteCollection;
16
17/**
18 * PhpMatcherDumper creates a PHP class able to match URLs for a given set of routes.
19 *
20 * @author Fabien Potencier <fabien@symfony.com>
21 * @author Tobias Schultze <http://tobion.de>
22 * @author Arnaud Le Blanc <arnaud.lb@gmail.com>
23 */
24class PhpMatcherDumper extends MatcherDumper
25{
26 /**
27 * Dumps a set of routes to a PHP class.
28 *
29 * Available options:
30 *
31 * * class: The class name
32 * * base_class: The base class name
33 *
34 * @param array $options An array of options
35 *
36 * @return string A PHP class representing the matcher class
37 */
38 public function dump(array $options = array())
39 {
40 $options = array_replace(array(
41 'class' => 'ProjectUrlMatcher',
42 'base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher',
43 ), $options);
44
45 // trailing slash support is only enabled if we know how to redirect the user
46 $interfaces = class_implements($options['base_class']);
47 $supportsRedirections = isset($interfaces['Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcherInterface']);
48
49 return <<<EOF
50<?php
51
52use Symfony\Component\Routing\Exception\MethodNotAllowedException;
53use Symfony\Component\Routing\Exception\ResourceNotFoundException;
54use Symfony\Component\Routing\RequestContext;
55
56/**
57 * {$options['class']}
58 *
59 * This class has been auto-generated
60 * by the Symfony Routing Component.
61 */
62class {$options['class']} extends {$options['base_class']}
63{
64 /**
65 * Constructor.
66 */
67 public function __construct(RequestContext \$context)
68 {
69 \$this->context = \$context;
70 }
71
72{$this->generateMatchMethod($supportsRedirections)}
73}
74
75EOF;
76 }
77
78 /**
79 * Generates the code for the match method implementing UrlMatcherInterface.
80 *
81 * @param Boolean $supportsRedirections Whether redirections are supported by the base class
82 *
83 * @return string Match method as PHP code
84 */
85 private function generateMatchMethod($supportsRedirections)
86 {
87 $code = rtrim($this->compileRoutes($this->getRoutes(), $supportsRedirections), "\n");
88
89 return <<<EOF
90 public function match(\$pathinfo)
91 {
92 \$allow = array();
93 \$pathinfo = rawurldecode(\$pathinfo);
94
95$code
96
97 throw 0 < count(\$allow) ? new MethodNotAllowedException(array_unique(\$allow)) : new ResourceNotFoundException();
98 }
99EOF;
100 }
101
102 /**
103 * Generates PHP code to match a RouteCollection with all its routes.
104 *
105 * @param RouteCollection $routes A RouteCollection instance
106 * @param Boolean $supportsRedirections Whether redirections are supported by the base class
107 *
108 * @return string PHP code
109 */
110 private function compileRoutes(RouteCollection $routes, $supportsRedirections)
111 {
112 $fetchedHost = false;
113
114 $groups = $this->groupRoutesByHostRegex($routes);
115 $code = '';
116
117 foreach ($groups as $collection) {
118 if (null !== $regex = $collection->getAttribute('host_regex')) {
119 if (!$fetchedHost) {
120 $code .= " \$host = \$this->context->getHost();\n\n";
121 $fetchedHost = true;
122 }
123
124 $code .= sprintf(" if (preg_match(%s, \$host, \$hostMatches)) {\n", var_export($regex, true));
125 }
126
127 $tree = $this->buildPrefixTree($collection);
128 $groupCode = $this->compilePrefixRoutes($tree, $supportsRedirections);
129
130 if (null !== $regex) {
131 // apply extra indention at each line (except empty ones)
132 $groupCode = preg_replace('/^.{2,}$/m', ' $0', $groupCode);
133 $code .= $groupCode;
134 $code .= " }\n\n";
135 } else {
136 $code .= $groupCode;
137 }
138 }
139
140 return $code;
141 }
142
143 /**
144 * Generates PHP code recursively to match a tree of routes
145 *
146 * @param DumperPrefixCollection $collection A DumperPrefixCollection instance
147 * @param Boolean $supportsRedirections Whether redirections are supported by the base class
148 * @param string $parentPrefix Prefix of the parent collection
149 *
150 * @return string PHP code
151 */
152 private function compilePrefixRoutes(DumperPrefixCollection $collection, $supportsRedirections, $parentPrefix = '')
153 {
154 $code = '';
155 $prefix = $collection->getPrefix();
156 $optimizable = 1 < strlen($prefix) && 1 < count($collection->all());
157 $optimizedPrefix = $parentPrefix;
158
159 if ($optimizable) {
160 $optimizedPrefix = $prefix;
161
162 $code .= sprintf(" if (0 === strpos(\$pathinfo, %s)) {\n", var_export($prefix, true));
163 }
164
165 foreach ($collection as $route) {
166 if ($route instanceof DumperCollection) {
167 $code .= $this->compilePrefixRoutes($route, $supportsRedirections, $optimizedPrefix);
168 } else {
169 $code .= $this->compileRoute($route->getRoute(), $route->getName(), $supportsRedirections, $optimizedPrefix)."\n";
170 }
171 }
172
173 if ($optimizable) {
174 $code .= " }\n\n";
175 // apply extra indention at each line (except empty ones)
176 $code = preg_replace('/^.{2,}$/m', ' $0', $code);
177 }
178
179 return $code;
180 }
181
182 /**
183 * Compiles a single Route to PHP code used to match it against the path info.
184 *
185 * @param Route $route A Route instance
186 * @param string $name The name of the Route
187 * @param Boolean $supportsRedirections Whether redirections are supported by the base class
188 * @param string|null $parentPrefix The prefix of the parent collection used to optimize the code
189 *
190 * @return string PHP code
191 *
192 * @throws \LogicException
193 */
194 private function compileRoute(Route $route, $name, $supportsRedirections, $parentPrefix = null)
195 {
196 $code = '';
197 $compiledRoute = $route->compile();
198 $conditions = array();
199 $hasTrailingSlash = false;
200 $matches = false;
201 $hostMatches = false;
202 $methods = array();
203
204 if ($req = $route->getRequirement('_method')) {
205 $methods = explode('|', strtoupper($req));
206 // GET and HEAD are equivalent
207 if (in_array('GET', $methods) && !in_array('HEAD', $methods)) {
208 $methods[] = 'HEAD';
209 }
210 }
211
212 $supportsTrailingSlash = $supportsRedirections && (!$methods || in_array('HEAD', $methods));
213
214 if (!count($compiledRoute->getPathVariables()) && false !== preg_match('#^(.)\^(?P<url>.*?)\$\1#', $compiledRoute->getRegex(), $m)) {
215 if ($supportsTrailingSlash && substr($m['url'], -1) === '/') {
216 $conditions[] = sprintf("rtrim(\$pathinfo, '/') === %s", var_export(rtrim(str_replace('\\', '', $m['url']), '/'), true));
217 $hasTrailingSlash = true;
218 } else {
219 $conditions[] = sprintf("\$pathinfo === %s", var_export(str_replace('\\', '', $m['url']), true));
220 }
221 } else {
222 if ($compiledRoute->getStaticPrefix() && $compiledRoute->getStaticPrefix() !== $parentPrefix) {
223 $conditions[] = sprintf("0 === strpos(\$pathinfo, %s)", var_export($compiledRoute->getStaticPrefix(), true));
224 }
225
226 $regex = $compiledRoute->getRegex();
227 if ($supportsTrailingSlash && $pos = strpos($regex, '/$')) {
228 $regex = substr($regex, 0, $pos).'/?$'.substr($regex, $pos + 2);
229 $hasTrailingSlash = true;
230 }
231 $conditions[] = sprintf("preg_match(%s, \$pathinfo, \$matches)", var_export($regex, true));
232
233 $matches = true;
234 }
235
236 if ($compiledRoute->getHostVariables()) {
237 $hostMatches = true;
238 }
239
240 $conditions = implode(' && ', $conditions);
241
242 $code .= <<<EOF
243 // $name
244 if ($conditions) {
245
246EOF;
247
248 if ($methods) {
249 $gotoname = 'not_'.preg_replace('/[^A-Za-z0-9_]/', '', $name);
250
251 if (1 === count($methods)) {
252 $code .= <<<EOF
253 if (\$this->context->getMethod() != '$methods[0]') {
254 \$allow[] = '$methods[0]';
255 goto $gotoname;
256 }
257
258
259EOF;
260 } else {
261 $methods = implode("', '", $methods);
262 $code .= <<<EOF
263 if (!in_array(\$this->context->getMethod(), array('$methods'))) {
264 \$allow = array_merge(\$allow, array('$methods'));
265 goto $gotoname;
266 }
267
268
269EOF;
270 }
271 }
272
273 if ($hasTrailingSlash) {
274 $code .= <<<EOF
275 if (substr(\$pathinfo, -1) !== '/') {
276 return \$this->redirect(\$pathinfo.'/', '$name');
277 }
278
279
280EOF;
281 }
282
283 if ($scheme = $route->getRequirement('_scheme')) {
284 if (!$supportsRedirections) {
285 throw new \LogicException('The "_scheme" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface.');
286 }
287
288 $code .= <<<EOF
289 if (\$this->context->getScheme() !== '$scheme') {
290 return \$this->redirect(\$pathinfo, '$name', '$scheme');
291 }
292
293
294EOF;
295 }
296
297 // optimize parameters array
298 if ($matches || $hostMatches) {
299 $vars = array();
300 if ($hostMatches) {
301 $vars[] = '$hostMatches';
302 }
303 if ($matches) {
304 $vars[] = '$matches';
305 }
306 $vars[] = "array('_route' => '$name')";
307
308 $code .= sprintf(" return \$this->mergeDefaults(array_replace(%s), %s);\n"
309 , implode(', ', $vars), str_replace("\n", '', var_export($route->getDefaults(), true)));
310
311 } elseif ($route->getDefaults()) {
312 $code .= sprintf(" return %s;\n", str_replace("\n", '', var_export(array_replace($route->getDefaults(), array('_route' => $name)), true)));
313 } else {
314 $code .= sprintf(" return array('_route' => '%s');\n", $name);
315 }
316 $code .= " }\n";
317
318 if ($methods) {
319 $code .= " $gotoname:\n";
320 }
321
322 return $code;
323 }
324
325 /**
326 * Groups consecutive routes having the same host regex.
327 *
328 * The result is a collection of collections of routes having the same host regex.
329 *
330 * @param RouteCollection $routes A flat RouteCollection
331 *
332 * @return DumperCollection A collection with routes grouped by host regex in sub-collections
333 */
334 private function groupRoutesByHostRegex(RouteCollection $routes)
335 {
336 $groups = new DumperCollection();
337
338 $currentGroup = new DumperCollection();
339 $currentGroup->setAttribute('host_regex', null);
340 $groups->add($currentGroup);
341
342 foreach ($routes as $name => $route) {
343 $hostRegex = $route->compile()->getHostRegex();
344 if ($currentGroup->getAttribute('host_regex') !== $hostRegex) {
345 $currentGroup = new DumperCollection();
346 $currentGroup->setAttribute('host_regex', $hostRegex);
347 $groups->add($currentGroup);
348 }
349 $currentGroup->add(new DumperRoute($name, $route));
350 }
351
352 return $groups;
353 }
354
355 /**
356 * Organizes the routes into a prefix tree.
357 *
358 * Routes order is preserved such that traversing the tree will traverse the
359 * routes in the origin order.
360 *
361 * @param DumperCollection $collection A collection of routes
362 *
363 * @return DumperPrefixCollection
364 */
365 private function buildPrefixTree(DumperCollection $collection)
366 {
367 $tree = new DumperPrefixCollection();
368 $current = $tree;
369
370 foreach ($collection as $route) {
371 $current = $current->addPrefixRoute($route);
372 }
373
374 $tree->mergeSlashNodes();
375
376 return $tree;
377 }
378}