diff options
Diffstat (limited to 'vendor/symfony/routing/Symfony/Component/Routing/Matcher')
14 files changed, 1644 insertions, 0 deletions
diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/ApacheUrlMatcher.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/ApacheUrlMatcher.php new file mode 100644 index 00000000..76612e61 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/ApacheUrlMatcher.php | |||
@@ -0,0 +1,94 @@ | |||
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 | |||
12 | namespace Symfony\Component\Routing\Matcher; | ||
13 | |||
14 | use Symfony\Component\Routing\Exception\MethodNotAllowedException; | ||
15 | |||
16 | /** | ||
17 | * ApacheUrlMatcher matches URL based on Apache mod_rewrite matching (see ApacheMatcherDumper). | ||
18 | * | ||
19 | * @author Fabien Potencier <fabien@symfony.com> | ||
20 | * @author Arnaud Le Blanc <arnaud.lb@gmail.com> | ||
21 | */ | ||
22 | class ApacheUrlMatcher extends UrlMatcher | ||
23 | { | ||
24 | /** | ||
25 | * Tries to match a URL based on Apache mod_rewrite matching. | ||
26 | * | ||
27 | * Returns false if no route matches the URL. | ||
28 | * | ||
29 | * @param string $pathinfo The pathinfo to be parsed | ||
30 | * | ||
31 | * @return array An array of parameters | ||
32 | * | ||
33 | * @throws MethodNotAllowedException If the current method is not allowed | ||
34 | */ | ||
35 | public function match($pathinfo) | ||
36 | { | ||
37 | $parameters = array(); | ||
38 | $defaults = array(); | ||
39 | $allow = array(); | ||
40 | $route = null; | ||
41 | |||
42 | foreach ($_SERVER as $key => $value) { | ||
43 | $name = $key; | ||
44 | |||
45 | // skip non-routing variables | ||
46 | // this improves performance when $_SERVER contains many usual | ||
47 | // variables like HTTP_*, DOCUMENT_ROOT, REQUEST_URI, ... | ||
48 | if (false === strpos($name, '_ROUTING_')) { | ||
49 | continue; | ||
50 | } | ||
51 | |||
52 | while (0 === strpos($name, 'REDIRECT_')) { | ||
53 | $name = substr($name, 9); | ||
54 | } | ||
55 | |||
56 | // expect _ROUTING_<type>_<name> | ||
57 | // or _ROUTING_<type> | ||
58 | |||
59 | if (0 !== strpos($name, '_ROUTING_')) { | ||
60 | continue; | ||
61 | } | ||
62 | if (false !== $pos = strpos($name, '_', 9)) { | ||
63 | $type = substr($name, 9, $pos-9); | ||
64 | $name = substr($name, $pos+1); | ||
65 | } else { | ||
66 | $type = substr($name, 9); | ||
67 | } | ||
68 | |||
69 | if ('param' === $type) { | ||
70 | if ('' !== $value) { | ||
71 | $parameters[$name] = $value; | ||
72 | } | ||
73 | } elseif ('default' === $type) { | ||
74 | $defaults[$name] = $value; | ||
75 | } elseif ('route' === $type) { | ||
76 | $route = $value; | ||
77 | } elseif ('allow' === $type) { | ||
78 | $allow[] = $name; | ||
79 | } | ||
80 | |||
81 | unset($_SERVER[$key]); | ||
82 | } | ||
83 | |||
84 | if (null !== $route) { | ||
85 | $parameters['_route'] = $route; | ||
86 | |||
87 | return $this->mergeDefaults($parameters, $defaults); | ||
88 | } elseif (0 < count($allow)) { | ||
89 | throw new MethodNotAllowedException($allow); | ||
90 | } else { | ||
91 | return parent::match($pathinfo); | ||
92 | } | ||
93 | } | ||
94 | } | ||
diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php new file mode 100644 index 00000000..804da19d --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php | |||
@@ -0,0 +1,252 @@ | |||
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 | |||
12 | namespace Symfony\Component\Routing\Matcher\Dumper; | ||
13 | |||
14 | use 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 | */ | ||
22 | class 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 new file mode 100644 index 00000000..612ac0d2 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php | |||
@@ -0,0 +1,159 @@ | |||
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 | |||
12 | namespace Symfony\Component\Routing\Matcher\Dumper; | ||
13 | |||
14 | /** | ||
15 | * Collection of routes. | ||
16 | * | ||
17 | * @author Arnaud Le Blanc <arnaud.lb@gmail.com> | ||
18 | */ | ||
19 | class 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 new file mode 100644 index 00000000..26382b0b --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/DumperPrefixCollection.php | |||
@@ -0,0 +1,108 @@ | |||
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 | |||
12 | namespace 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 | */ | ||
19 | class 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 new file mode 100644 index 00000000..2928cdcc --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/DumperRoute.php | |||
@@ -0,0 +1,64 @@ | |||
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 | |||
12 | namespace Symfony\Component\Routing\Matcher\Dumper; | ||
13 | |||
14 | use Symfony\Component\Routing\Route; | ||
15 | |||
16 | /** | ||
17 | * Container for a Route. | ||
18 | * | ||
19 | * @author Arnaud Le Blanc <arnaud.lb@gmail.com> | ||
20 | */ | ||
21 | class 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 new file mode 100644 index 00000000..52edc017 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/MatcherDumper.php | |||
@@ -0,0 +1,45 @@ | |||
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 | |||
12 | namespace Symfony\Component\Routing\Matcher\Dumper; | ||
13 | |||
14 | use 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 | */ | ||
21 | abstract 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 new file mode 100644 index 00000000..f85e4cef --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/MatcherDumperInterface.php | |||
@@ -0,0 +1,37 @@ | |||
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 | |||
12 | namespace 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 | */ | ||
19 | interface 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 new file mode 100644 index 00000000..dc17ffbe --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php | |||
@@ -0,0 +1,378 @@ | |||
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 | |||
12 | namespace Symfony\Component\Routing\Matcher\Dumper; | ||
13 | |||
14 | use Symfony\Component\Routing\Route; | ||
15 | use 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 | */ | ||
24 | class 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 | |||
52 | use Symfony\Component\Routing\Exception\MethodNotAllowedException; | ||
53 | use Symfony\Component\Routing\Exception\ResourceNotFoundException; | ||
54 | use Symfony\Component\Routing\RequestContext; | ||
55 | |||
56 | /** | ||
57 | * {$options['class']} | ||
58 | * | ||
59 | * This class has been auto-generated | ||
60 | * by the Symfony Routing Component. | ||
61 | */ | ||
62 | class {$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 | |||
75 | EOF; | ||
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 | } | ||
99 | EOF; | ||
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 | |||
246 | EOF; | ||
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 | |||
259 | EOF; | ||
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 | |||
269 | EOF; | ||
270 | } | ||
271 | } | ||
272 | |||
273 | if ($hasTrailingSlash) { | ||
274 | $code .= <<<EOF | ||
275 | if (substr(\$pathinfo, -1) !== '/') { | ||
276 | return \$this->redirect(\$pathinfo.'/', '$name'); | ||
277 | } | ||
278 | |||
279 | |||
280 | EOF; | ||
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 | |||
294 | EOF; | ||
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 | } | ||
diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/RedirectableUrlMatcher.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/RedirectableUrlMatcher.php new file mode 100644 index 00000000..51e80057 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/RedirectableUrlMatcher.php | |||
@@ -0,0 +1,61 @@ | |||
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 | |||
12 | namespace Symfony\Component\Routing\Matcher; | ||
13 | |||
14 | use Symfony\Component\Routing\Exception\ResourceNotFoundException; | ||
15 | use Symfony\Component\Routing\Route; | ||
16 | |||
17 | /** | ||
18 | * @author Fabien Potencier <fabien@symfony.com> | ||
19 | * | ||
20 | * @api | ||
21 | */ | ||
22 | abstract class RedirectableUrlMatcher extends UrlMatcher implements RedirectableUrlMatcherInterface | ||
23 | { | ||
24 | /** | ||
25 | * {@inheritdoc} | ||
26 | */ | ||
27 | public function match($pathinfo) | ||
28 | { | ||
29 | try { | ||
30 | $parameters = parent::match($pathinfo); | ||
31 | } catch (ResourceNotFoundException $e) { | ||
32 | if ('/' === substr($pathinfo, -1) || !in_array($this->context->getMethod(), array('HEAD', 'GET'))) { | ||
33 | throw $e; | ||
34 | } | ||
35 | |||
36 | try { | ||
37 | parent::match($pathinfo.'/'); | ||
38 | |||
39 | return $this->redirect($pathinfo.'/', null); | ||
40 | } catch (ResourceNotFoundException $e2) { | ||
41 | throw $e; | ||
42 | } | ||
43 | } | ||
44 | |||
45 | return $parameters; | ||
46 | } | ||
47 | |||
48 | /** | ||
49 | * {@inheritDoc} | ||
50 | */ | ||
51 | protected function handleRouteRequirements($pathinfo, $name, Route $route) | ||
52 | { | ||
53 | // check HTTP scheme requirement | ||
54 | $scheme = $route->getRequirement('_scheme'); | ||
55 | if ($scheme && $this->context->getScheme() !== $scheme) { | ||
56 | return array(self::ROUTE_MATCH, $this->redirect($pathinfo, $name, $scheme)); | ||
57 | } | ||
58 | |||
59 | return array(self::REQUIREMENT_MATCH, null); | ||
60 | } | ||
61 | } | ||
diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/RedirectableUrlMatcherInterface.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/RedirectableUrlMatcherInterface.php new file mode 100644 index 00000000..ea91e075 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/RedirectableUrlMatcherInterface.php | |||
@@ -0,0 +1,35 @@ | |||
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 | |||
12 | namespace Symfony\Component\Routing\Matcher; | ||
13 | |||
14 | /** | ||
15 | * RedirectableUrlMatcherInterface knows how to redirect the user. | ||
16 | * | ||
17 | * @author Fabien Potencier <fabien@symfony.com> | ||
18 | * | ||
19 | * @api | ||
20 | */ | ||
21 | interface RedirectableUrlMatcherInterface | ||
22 | { | ||
23 | /** | ||
24 | * Redirects the user to another URL. | ||
25 | * | ||
26 | * @param string $path The path info to redirect to. | ||
27 | * @param string $route The route name that matched | ||
28 | * @param string|null $scheme The URL scheme (null to keep the current one) | ||
29 | * | ||
30 | * @return array An array of parameters | ||
31 | * | ||
32 | * @api | ||
33 | */ | ||
34 | public function redirect($path, $route, $scheme = null); | ||
35 | } | ||
diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/RequestMatcherInterface.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/RequestMatcherInterface.php new file mode 100644 index 00000000..b5def3d4 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/RequestMatcherInterface.php | |||
@@ -0,0 +1,39 @@ | |||
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 | |||
12 | namespace Symfony\Component\Routing\Matcher; | ||
13 | |||
14 | use Symfony\Component\HttpFoundation\Request; | ||
15 | use Symfony\Component\Routing\Exception\ResourceNotFoundException; | ||
16 | use Symfony\Component\Routing\Exception\MethodNotAllowedException; | ||
17 | |||
18 | /** | ||
19 | * RequestMatcherInterface is the interface that all request matcher classes must implement. | ||
20 | * | ||
21 | * @author Fabien Potencier <fabien@symfony.com> | ||
22 | */ | ||
23 | interface RequestMatcherInterface | ||
24 | { | ||
25 | /** | ||
26 | * Tries to match a request with a set of routes. | ||
27 | * | ||
28 | * If the matcher can not find information, it must throw one of the exceptions documented | ||
29 | * below. | ||
30 | * | ||
31 | * @param Request $request The request to match | ||
32 | * | ||
33 | * @return array An array of parameters | ||
34 | * | ||
35 | * @throws ResourceNotFoundException If no matching resource could be found | ||
36 | * @throws MethodNotAllowedException If a matching resource was found but the request method is not allowed | ||
37 | */ | ||
38 | public function matchRequest(Request $request); | ||
39 | } | ||
diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/TraceableUrlMatcher.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/TraceableUrlMatcher.php new file mode 100644 index 00000000..c09f83e8 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/TraceableUrlMatcher.php | |||
@@ -0,0 +1,121 @@ | |||
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 | |||
12 | namespace Symfony\Component\Routing\Matcher; | ||
13 | |||
14 | use Symfony\Component\Routing\Exception\ExceptionInterface; | ||
15 | use Symfony\Component\Routing\Route; | ||
16 | use Symfony\Component\Routing\RouteCollection; | ||
17 | use Symfony\Component\Routing\Matcher\UrlMatcher; | ||
18 | |||
19 | /** | ||
20 | * TraceableUrlMatcher helps debug path info matching by tracing the match. | ||
21 | * | ||
22 | * @author Fabien Potencier <fabien@symfony.com> | ||
23 | */ | ||
24 | class TraceableUrlMatcher extends UrlMatcher | ||
25 | { | ||
26 | const ROUTE_DOES_NOT_MATCH = 0; | ||
27 | const ROUTE_ALMOST_MATCHES = 1; | ||
28 | const ROUTE_MATCHES = 2; | ||
29 | |||
30 | protected $traces; | ||
31 | |||
32 | public function getTraces($pathinfo) | ||
33 | { | ||
34 | $this->traces = array(); | ||
35 | |||
36 | try { | ||
37 | $this->match($pathinfo); | ||
38 | } catch (ExceptionInterface $e) { | ||
39 | } | ||
40 | |||
41 | return $this->traces; | ||
42 | } | ||
43 | |||
44 | protected function matchCollection($pathinfo, RouteCollection $routes) | ||
45 | { | ||
46 | foreach ($routes as $name => $route) { | ||
47 | $compiledRoute = $route->compile(); | ||
48 | |||
49 | if (!preg_match($compiledRoute->getRegex(), $pathinfo, $matches)) { | ||
50 | // does it match without any requirements? | ||
51 | $r = new Route($route->getPath(), $route->getDefaults(), array(), $route->getOptions()); | ||
52 | $cr = $r->compile(); | ||
53 | if (!preg_match($cr->getRegex(), $pathinfo)) { | ||
54 | $this->addTrace(sprintf('Path "%s" does not match', $route->getPath()), self::ROUTE_DOES_NOT_MATCH, $name, $route); | ||
55 | |||
56 | continue; | ||
57 | } | ||
58 | |||
59 | foreach ($route->getRequirements() as $n => $regex) { | ||
60 | $r = new Route($route->getPath(), $route->getDefaults(), array($n => $regex), $route->getOptions()); | ||
61 | $cr = $r->compile(); | ||
62 | |||
63 | if (in_array($n, $cr->getVariables()) && !preg_match($cr->getRegex(), $pathinfo)) { | ||
64 | $this->addTrace(sprintf('Requirement for "%s" does not match (%s)', $n, $regex), self::ROUTE_ALMOST_MATCHES, $name, $route); | ||
65 | |||
66 | continue 2; | ||
67 | } | ||
68 | } | ||
69 | |||
70 | continue; | ||
71 | } | ||
72 | |||
73 | // check host requirement | ||
74 | $hostMatches = array(); | ||
75 | if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) { | ||
76 | $this->addTrace(sprintf('Host "%s" does not match the requirement ("%s")', $this->context->getHost(), $route->getHost()), self::ROUTE_ALMOST_MATCHES, $name, $route); | ||
77 | |||
78 | return true; | ||
79 | } | ||
80 | |||
81 | // check HTTP method requirement | ||
82 | if ($req = $route->getRequirement('_method')) { | ||
83 | // HEAD and GET are equivalent as per RFC | ||
84 | if ('HEAD' === $method = $this->context->getMethod()) { | ||
85 | $method = 'GET'; | ||
86 | } | ||
87 | |||
88 | if (!in_array($method, $req = explode('|', strtoupper($req)))) { | ||
89 | $this->allow = array_merge($this->allow, $req); | ||
90 | |||
91 | $this->addTrace(sprintf('Method "%s" does not match the requirement ("%s")', $this->context->getMethod(), implode(', ', $req)), self::ROUTE_ALMOST_MATCHES, $name, $route); | ||
92 | |||
93 | continue; | ||
94 | } | ||
95 | } | ||
96 | |||
97 | // check HTTP scheme requirement | ||
98 | if ($scheme = $route->getRequirement('_scheme')) { | ||
99 | if ($this->context->getScheme() !== $scheme) { | ||
100 | $this->addTrace(sprintf('Scheme "%s" does not match the requirement ("%s"); the user will be redirected', $this->context->getScheme(), $scheme), self::ROUTE_ALMOST_MATCHES, $name, $route); | ||
101 | |||
102 | return true; | ||
103 | } | ||
104 | } | ||
105 | |||
106 | $this->addTrace('Route matches!', self::ROUTE_MATCHES, $name, $route); | ||
107 | |||
108 | return true; | ||
109 | } | ||
110 | } | ||
111 | |||
112 | private function addTrace($log, $level = self::ROUTE_DOES_NOT_MATCH, $name = null, $route = null) | ||
113 | { | ||
114 | $this->traces[] = array( | ||
115 | 'log' => $log, | ||
116 | 'name' => $name, | ||
117 | 'level' => $level, | ||
118 | 'path' => null !== $route ? $route->getPath() : null, | ||
119 | ); | ||
120 | } | ||
121 | } | ||
diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/UrlMatcher.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/UrlMatcher.php new file mode 100644 index 00000000..db18ec4e --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/UrlMatcher.php | |||
@@ -0,0 +1,208 @@ | |||
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 | |||
12 | namespace Symfony\Component\Routing\Matcher; | ||
13 | |||
14 | use Symfony\Component\Routing\Exception\MethodNotAllowedException; | ||
15 | use Symfony\Component\Routing\Exception\ResourceNotFoundException; | ||
16 | use Symfony\Component\Routing\RouteCollection; | ||
17 | use Symfony\Component\Routing\RequestContext; | ||
18 | use Symfony\Component\Routing\Route; | ||
19 | |||
20 | /** | ||
21 | * UrlMatcher matches URL based on a set of routes. | ||
22 | * | ||
23 | * @author Fabien Potencier <fabien@symfony.com> | ||
24 | * | ||
25 | * @api | ||
26 | */ | ||
27 | class UrlMatcher implements UrlMatcherInterface | ||
28 | { | ||
29 | const REQUIREMENT_MATCH = 0; | ||
30 | const REQUIREMENT_MISMATCH = 1; | ||
31 | const ROUTE_MATCH = 2; | ||
32 | |||
33 | /** | ||
34 | * @var RequestContext | ||
35 | */ | ||
36 | protected $context; | ||
37 | |||
38 | /** | ||
39 | * @var array | ||
40 | */ | ||
41 | protected $allow = array(); | ||
42 | |||
43 | /** | ||
44 | * @var RouteCollection | ||
45 | */ | ||
46 | protected $routes; | ||
47 | |||
48 | /** | ||
49 | * Constructor. | ||
50 | * | ||
51 | * @param RouteCollection $routes A RouteCollection instance | ||
52 | * @param RequestContext $context The context | ||
53 | * | ||
54 | * @api | ||
55 | */ | ||
56 | public function __construct(RouteCollection $routes, RequestContext $context) | ||
57 | { | ||
58 | $this->routes = $routes; | ||
59 | $this->context = $context; | ||
60 | } | ||
61 | |||
62 | /** | ||
63 | * {@inheritdoc} | ||
64 | */ | ||
65 | public function setContext(RequestContext $context) | ||
66 | { | ||
67 | $this->context = $context; | ||
68 | } | ||
69 | |||
70 | /** | ||
71 | * {@inheritdoc} | ||
72 | */ | ||
73 | public function getContext() | ||
74 | { | ||
75 | return $this->context; | ||
76 | } | ||
77 | |||
78 | /** | ||
79 | * {@inheritdoc} | ||
80 | */ | ||
81 | public function match($pathinfo) | ||
82 | { | ||
83 | $this->allow = array(); | ||
84 | |||
85 | if ($ret = $this->matchCollection(rawurldecode($pathinfo), $this->routes)) { | ||
86 | return $ret; | ||
87 | } | ||
88 | |||
89 | throw 0 < count($this->allow) | ||
90 | ? new MethodNotAllowedException(array_unique(array_map('strtoupper', $this->allow))) | ||
91 | : new ResourceNotFoundException(); | ||
92 | } | ||
93 | |||
94 | /** | ||
95 | * Tries to match a URL with a set of routes. | ||
96 | * | ||
97 | * @param string $pathinfo The path info to be parsed | ||
98 | * @param RouteCollection $routes The set of routes | ||
99 | * | ||
100 | * @return array An array of parameters | ||
101 | * | ||
102 | * @throws ResourceNotFoundException If the resource could not be found | ||
103 | * @throws MethodNotAllowedException If the resource was found but the request method is not allowed | ||
104 | */ | ||
105 | protected function matchCollection($pathinfo, RouteCollection $routes) | ||
106 | { | ||
107 | foreach ($routes as $name => $route) { | ||
108 | $compiledRoute = $route->compile(); | ||
109 | |||
110 | // check the static prefix of the URL first. Only use the more expensive preg_match when it matches | ||
111 | if ('' !== $compiledRoute->getStaticPrefix() && 0 !== strpos($pathinfo, $compiledRoute->getStaticPrefix())) { | ||
112 | continue; | ||
113 | } | ||
114 | |||
115 | if (!preg_match($compiledRoute->getRegex(), $pathinfo, $matches)) { | ||
116 | continue; | ||
117 | } | ||
118 | |||
119 | $hostMatches = array(); | ||
120 | if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) { | ||
121 | continue; | ||
122 | } | ||
123 | |||
124 | // check HTTP method requirement | ||
125 | if ($req = $route->getRequirement('_method')) { | ||
126 | // HEAD and GET are equivalent as per RFC | ||
127 | if ('HEAD' === $method = $this->context->getMethod()) { | ||
128 | $method = 'GET'; | ||
129 | } | ||
130 | |||
131 | if (!in_array($method, $req = explode('|', strtoupper($req)))) { | ||
132 | $this->allow = array_merge($this->allow, $req); | ||
133 | |||
134 | continue; | ||
135 | } | ||
136 | } | ||
137 | |||
138 | $status = $this->handleRouteRequirements($pathinfo, $name, $route); | ||
139 | |||
140 | if (self::ROUTE_MATCH === $status[0]) { | ||
141 | return $status[1]; | ||
142 | } | ||
143 | |||
144 | if (self::REQUIREMENT_MISMATCH === $status[0]) { | ||
145 | continue; | ||
146 | } | ||
147 | |||
148 | return $this->getAttributes($route, $name, array_replace($matches, $hostMatches)); | ||
149 | } | ||
150 | } | ||
151 | |||
152 | /** | ||
153 | * Returns an array of values to use as request attributes. | ||
154 | * | ||
155 | * As this method requires the Route object, it is not available | ||
156 | * in matchers that do not have access to the matched Route instance | ||
157 | * (like the PHP and Apache matcher dumpers). | ||
158 | * | ||
159 | * @param Route $route The route we are matching against | ||
160 | * @param string $name The name of the route | ||
161 | * @param array $attributes An array of attributes from the matcher | ||
162 | * | ||
163 | * @return array An array of parameters | ||
164 | */ | ||
165 | protected function getAttributes(Route $route, $name, array $attributes) | ||
166 | { | ||
167 | $attributes['_route'] = $name; | ||
168 | |||
169 | return $this->mergeDefaults($attributes, $route->getDefaults()); | ||
170 | } | ||
171 | |||
172 | /** | ||
173 | * Handles specific route requirements. | ||
174 | * | ||
175 | * @param string $pathinfo The path | ||
176 | * @param string $name The route name | ||
177 | * @param Route $route The route | ||
178 | * | ||
179 | * @return array The first element represents the status, the second contains additional information | ||
180 | */ | ||
181 | protected function handleRouteRequirements($pathinfo, $name, Route $route) | ||
182 | { | ||
183 | // check HTTP scheme requirement | ||
184 | $scheme = $route->getRequirement('_scheme'); | ||
185 | $status = $scheme && $scheme !== $this->context->getScheme() ? self::REQUIREMENT_MISMATCH : self::REQUIREMENT_MATCH; | ||
186 | |||
187 | return array($status, null); | ||
188 | } | ||
189 | |||
190 | /** | ||
191 | * Get merged default parameters. | ||
192 | * | ||
193 | * @param array $params The parameters | ||
194 | * @param array $defaults The defaults | ||
195 | * | ||
196 | * @return array Merged default parameters | ||
197 | */ | ||
198 | protected function mergeDefaults($params, $defaults) | ||
199 | { | ||
200 | foreach ($params as $key => $value) { | ||
201 | if (!is_int($key)) { | ||
202 | $defaults[$key] = $value; | ||
203 | } | ||
204 | } | ||
205 | |||
206 | return $defaults; | ||
207 | } | ||
208 | } | ||
diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/UrlMatcherInterface.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/UrlMatcherInterface.php new file mode 100644 index 00000000..dd718b15 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/UrlMatcherInterface.php | |||
@@ -0,0 +1,43 @@ | |||
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 | |||
12 | namespace Symfony\Component\Routing\Matcher; | ||
13 | |||
14 | use Symfony\Component\Routing\RequestContextAwareInterface; | ||
15 | use Symfony\Component\Routing\Exception\ResourceNotFoundException; | ||
16 | use Symfony\Component\Routing\Exception\MethodNotAllowedException; | ||
17 | |||
18 | /** | ||
19 | * UrlMatcherInterface is the interface that all URL matcher classes must implement. | ||
20 | * | ||
21 | * @author Fabien Potencier <fabien@symfony.com> | ||
22 | * | ||
23 | * @api | ||
24 | */ | ||
25 | interface UrlMatcherInterface extends RequestContextAwareInterface | ||
26 | { | ||
27 | /** | ||
28 | * Tries to match a URL path with a set of routes. | ||
29 | * | ||
30 | * If the matcher can not find information, it must throw one of the exceptions documented | ||
31 | * below. | ||
32 | * | ||
33 | * @param string $pathinfo The path info to be parsed (raw format, i.e. not urldecoded) | ||
34 | * | ||
35 | * @return array An array of parameters | ||
36 | * | ||
37 | * @throws ResourceNotFoundException If the resource could not be found | ||
38 | * @throws MethodNotAllowedException If the resource was found but the request method is not allowed | ||
39 | * | ||
40 | * @api | ||
41 | */ | ||
42 | public function match($pathinfo); | ||
43 | } | ||