aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/symfony/routing/Symfony/Component/Routing/Matcher
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/symfony/routing/Symfony/Component/Routing/Matcher')
-rw-r--r--vendor/symfony/routing/Symfony/Component/Routing/Matcher/ApacheUrlMatcher.php94
-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
-rw-r--r--vendor/symfony/routing/Symfony/Component/Routing/Matcher/RedirectableUrlMatcher.php61
-rw-r--r--vendor/symfony/routing/Symfony/Component/Routing/Matcher/RedirectableUrlMatcherInterface.php35
-rw-r--r--vendor/symfony/routing/Symfony/Component/Routing/Matcher/RequestMatcherInterface.php39
-rw-r--r--vendor/symfony/routing/Symfony/Component/Routing/Matcher/TraceableUrlMatcher.php121
-rw-r--r--vendor/symfony/routing/Symfony/Component/Routing/Matcher/UrlMatcher.php208
-rw-r--r--vendor/symfony/routing/Symfony/Component/Routing/Matcher/UrlMatcherInterface.php43
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
12namespace Symfony\Component\Routing\Matcher;
13
14use 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 */
22class 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
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
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
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
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
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
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
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
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
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
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
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
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
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}
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
12namespace Symfony\Component\Routing\Matcher;
13
14use Symfony\Component\Routing\Exception\ResourceNotFoundException;
15use Symfony\Component\Routing\Route;
16
17/**
18 * @author Fabien Potencier <fabien@symfony.com>
19 *
20 * @api
21 */
22abstract 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
12namespace 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 */
21interface 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
12namespace Symfony\Component\Routing\Matcher;
13
14use Symfony\Component\HttpFoundation\Request;
15use Symfony\Component\Routing\Exception\ResourceNotFoundException;
16use 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 */
23interface 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
12namespace Symfony\Component\Routing\Matcher;
13
14use Symfony\Component\Routing\Exception\ExceptionInterface;
15use Symfony\Component\Routing\Route;
16use Symfony\Component\Routing\RouteCollection;
17use 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 */
24class 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
12namespace Symfony\Component\Routing\Matcher;
13
14use Symfony\Component\Routing\Exception\MethodNotAllowedException;
15use Symfony\Component\Routing\Exception\ResourceNotFoundException;
16use Symfony\Component\Routing\RouteCollection;
17use Symfony\Component\Routing\RequestContext;
18use 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 */
27class 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
12namespace Symfony\Component\Routing\Matcher;
13
14use Symfony\Component\Routing\RequestContextAwareInterface;
15use Symfony\Component\Routing\Exception\ResourceNotFoundException;
16use 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 */
25interface 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}