comparison vendor/symfony/routing/Matcher/UrlMatcher.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 7a779792577d
comparison
equal deleted inserted replaced
-1:000000000000 0:4c8ae668cc8c
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 use Symfony\Component\HttpFoundation\Request;
20 use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
21 use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
22
23 /**
24 * UrlMatcher matches URL based on a set of routes.
25 *
26 * @author Fabien Potencier <fabien@symfony.com>
27 */
28 class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
29 {
30 const REQUIREMENT_MATCH = 0;
31 const REQUIREMENT_MISMATCH = 1;
32 const ROUTE_MATCH = 2;
33
34 /**
35 * @var RequestContext
36 */
37 protected $context;
38
39 /**
40 * @var array
41 */
42 protected $allow = array();
43
44 /**
45 * @var RouteCollection
46 */
47 protected $routes;
48
49 protected $request;
50 protected $expressionLanguage;
51
52 /**
53 * @var ExpressionFunctionProviderInterface[]
54 */
55 protected $expressionLanguageProviders = array();
56
57 /**
58 * Constructor.
59 *
60 * @param RouteCollection $routes A RouteCollection instance
61 * @param RequestContext $context The context
62 */
63 public function __construct(RouteCollection $routes, RequestContext $context)
64 {
65 $this->routes = $routes;
66 $this->context = $context;
67 }
68
69 /**
70 * {@inheritdoc}
71 */
72 public function setContext(RequestContext $context)
73 {
74 $this->context = $context;
75 }
76
77 /**
78 * {@inheritdoc}
79 */
80 public function getContext()
81 {
82 return $this->context;
83 }
84
85 /**
86 * {@inheritdoc}
87 */
88 public function match($pathinfo)
89 {
90 $this->allow = array();
91
92 if ($ret = $this->matchCollection(rawurldecode($pathinfo), $this->routes)) {
93 return $ret;
94 }
95
96 throw 0 < count($this->allow)
97 ? new MethodNotAllowedException(array_unique($this->allow))
98 : new ResourceNotFoundException(sprintf('No routes found for "%s".', $pathinfo));
99 }
100
101 /**
102 * {@inheritdoc}
103 */
104 public function matchRequest(Request $request)
105 {
106 $this->request = $request;
107
108 $ret = $this->match($request->getPathInfo());
109
110 $this->request = null;
111
112 return $ret;
113 }
114
115 public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider)
116 {
117 $this->expressionLanguageProviders[] = $provider;
118 }
119
120 /**
121 * Tries to match a URL with a set of routes.
122 *
123 * @param string $pathinfo The path info to be parsed
124 * @param RouteCollection $routes The set of routes
125 *
126 * @return array An array of parameters
127 *
128 * @throws ResourceNotFoundException If the resource could not be found
129 * @throws MethodNotAllowedException If the resource was found but the request method is not allowed
130 */
131 protected function matchCollection($pathinfo, RouteCollection $routes)
132 {
133 foreach ($routes as $name => $route) {
134 $compiledRoute = $route->compile();
135
136 // check the static prefix of the URL first. Only use the more expensive preg_match when it matches
137 if ('' !== $compiledRoute->getStaticPrefix() && 0 !== strpos($pathinfo, $compiledRoute->getStaticPrefix())) {
138 continue;
139 }
140
141 if (!preg_match($compiledRoute->getRegex(), $pathinfo, $matches)) {
142 continue;
143 }
144
145 $hostMatches = array();
146 if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) {
147 continue;
148 }
149
150 // check HTTP method requirement
151 if ($requiredMethods = $route->getMethods()) {
152 // HEAD and GET are equivalent as per RFC
153 if ('HEAD' === $method = $this->context->getMethod()) {
154 $method = 'GET';
155 }
156
157 if (!in_array($method, $requiredMethods)) {
158 $this->allow = array_merge($this->allow, $requiredMethods);
159
160 continue;
161 }
162 }
163
164 $status = $this->handleRouteRequirements($pathinfo, $name, $route);
165
166 if (self::ROUTE_MATCH === $status[0]) {
167 return $status[1];
168 }
169
170 if (self::REQUIREMENT_MISMATCH === $status[0]) {
171 continue;
172 }
173
174 return $this->getAttributes($route, $name, array_replace($matches, $hostMatches));
175 }
176 }
177
178 /**
179 * Returns an array of values to use as request attributes.
180 *
181 * As this method requires the Route object, it is not available
182 * in matchers that do not have access to the matched Route instance
183 * (like the PHP and Apache matcher dumpers).
184 *
185 * @param Route $route The route we are matching against
186 * @param string $name The name of the route
187 * @param array $attributes An array of attributes from the matcher
188 *
189 * @return array An array of parameters
190 */
191 protected function getAttributes(Route $route, $name, array $attributes)
192 {
193 $attributes['_route'] = $name;
194
195 return $this->mergeDefaults($attributes, $route->getDefaults());
196 }
197
198 /**
199 * Handles specific route requirements.
200 *
201 * @param string $pathinfo The path
202 * @param string $name The route name
203 * @param Route $route The route
204 *
205 * @return array The first element represents the status, the second contains additional information
206 */
207 protected function handleRouteRequirements($pathinfo, $name, Route $route)
208 {
209 // expression condition
210 if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), array('context' => $this->context, 'request' => $this->request))) {
211 return array(self::REQUIREMENT_MISMATCH, null);
212 }
213
214 // check HTTP scheme requirement
215 $scheme = $this->context->getScheme();
216 $status = $route->getSchemes() && !$route->hasScheme($scheme) ? self::REQUIREMENT_MISMATCH : self::REQUIREMENT_MATCH;
217
218 return array($status, null);
219 }
220
221 /**
222 * Get merged default parameters.
223 *
224 * @param array $params The parameters
225 * @param array $defaults The defaults
226 *
227 * @return array Merged default parameters
228 */
229 protected function mergeDefaults($params, $defaults)
230 {
231 foreach ($params as $key => $value) {
232 if (!is_int($key)) {
233 $defaults[$key] = $value;
234 }
235 }
236
237 return $defaults;
238 }
239
240 protected function getExpressionLanguage()
241 {
242 if (null === $this->expressionLanguage) {
243 if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
244 throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
245 }
246 $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders);
247 }
248
249 return $this->expressionLanguage;
250 }
251 }