]>
Commit | Line | Data |
---|---|---|
4f5b44bd NL |
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 | } |