Mercurial > hg > isophonics-drupal-site
comparison vendor/symfony-cmf/routing/ChainRouter.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4c8ae668cc8c |
---|---|
1 <?php | |
2 | |
3 /* | |
4 * This file is part of the Symfony CMF package. | |
5 * | |
6 * (c) 2011-2015 Symfony CMF | |
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\Cmf\Component\Routing; | |
13 | |
14 use Symfony\Component\Routing\RouterInterface; | |
15 use Symfony\Component\Routing\Generator\UrlGeneratorInterface; | |
16 use Symfony\Component\Routing\Matcher\RequestMatcherInterface; | |
17 use Symfony\Component\Routing\RequestContext; | |
18 use Symfony\Component\Routing\RequestContextAwareInterface; | |
19 use Symfony\Component\Routing\Exception\ResourceNotFoundException; | |
20 use Symfony\Component\Routing\Exception\RouteNotFoundException; | |
21 use Symfony\Component\Routing\Exception\MethodNotAllowedException; | |
22 use Symfony\Component\Routing\RouteCollection; | |
23 use Symfony\Component\HttpFoundation\Request; | |
24 use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; | |
25 use Psr\Log\LoggerInterface; | |
26 | |
27 /** | |
28 * The ChainRouter allows to combine several routers to try in a defined order. | |
29 * | |
30 * @author Henrik Bjornskov <henrik@bjrnskov.dk> | |
31 * @author Magnus Nordlander <magnus@e-butik.se> | |
32 */ | |
33 class ChainRouter implements ChainRouterInterface, WarmableInterface | |
34 { | |
35 /** | |
36 * @var RequestContext | |
37 */ | |
38 private $context; | |
39 | |
40 /** | |
41 * Array of arrays of routers grouped by priority. | |
42 * | |
43 * @var array | |
44 */ | |
45 private $routers = array(); | |
46 | |
47 /** | |
48 * @var RouterInterface[] Array of routers, sorted by priority | |
49 */ | |
50 private $sortedRouters; | |
51 | |
52 /** | |
53 * @var RouteCollection | |
54 */ | |
55 private $routeCollection; | |
56 | |
57 /** | |
58 * @var null|LoggerInterface | |
59 */ | |
60 protected $logger; | |
61 | |
62 /** | |
63 * @param LoggerInterface $logger | |
64 */ | |
65 public function __construct(LoggerInterface $logger = null) | |
66 { | |
67 $this->logger = $logger; | |
68 } | |
69 | |
70 /** | |
71 * @return RequestContext | |
72 */ | |
73 public function getContext() | |
74 { | |
75 return $this->context; | |
76 } | |
77 | |
78 /** | |
79 * {@inheritdoc} | |
80 */ | |
81 public function add($router, $priority = 0) | |
82 { | |
83 if (!$router instanceof RouterInterface | |
84 && !($router instanceof RequestMatcherInterface && $router instanceof UrlGeneratorInterface) | |
85 ) { | |
86 throw new \InvalidArgumentException(sprintf('%s is not a valid router.', get_class($router))); | |
87 } | |
88 if (empty($this->routers[$priority])) { | |
89 $this->routers[$priority] = array(); | |
90 } | |
91 | |
92 $this->routers[$priority][] = $router; | |
93 $this->sortedRouters = array(); | |
94 } | |
95 | |
96 /** | |
97 * {@inheritdoc} | |
98 */ | |
99 public function all() | |
100 { | |
101 if (empty($this->sortedRouters)) { | |
102 $this->sortedRouters = $this->sortRouters(); | |
103 | |
104 // setContext() is done here instead of in add() to avoid fatal errors when clearing and warming up caches | |
105 // See https://github.com/symfony-cmf/Routing/pull/18 | |
106 $context = $this->getContext(); | |
107 if (null !== $context) { | |
108 foreach ($this->sortedRouters as $router) { | |
109 if ($router instanceof RequestContextAwareInterface) { | |
110 $router->setContext($context); | |
111 } | |
112 } | |
113 } | |
114 } | |
115 | |
116 return $this->sortedRouters; | |
117 } | |
118 | |
119 /** | |
120 * Sort routers by priority. | |
121 * The highest priority number is the highest priority (reverse sorting). | |
122 * | |
123 * @return RouterInterface[] | |
124 */ | |
125 protected function sortRouters() | |
126 { | |
127 $sortedRouters = array(); | |
128 krsort($this->routers); | |
129 | |
130 foreach ($this->routers as $routers) { | |
131 $sortedRouters = array_merge($sortedRouters, $routers); | |
132 } | |
133 | |
134 return $sortedRouters; | |
135 } | |
136 | |
137 /** | |
138 * {@inheritdoc} | |
139 * | |
140 * Loops through all routes and tries to match the passed url. | |
141 * | |
142 * Note: You should use matchRequest if you can. | |
143 */ | |
144 public function match($pathinfo) | |
145 { | |
146 return $this->doMatch($pathinfo); | |
147 } | |
148 | |
149 /** | |
150 * {@inheritdoc} | |
151 * | |
152 * Loops through all routes and tries to match the passed request. | |
153 */ | |
154 public function matchRequest(Request $request) | |
155 { | |
156 return $this->doMatch($request->getPathInfo(), $request); | |
157 } | |
158 | |
159 /** | |
160 * Loops through all routers and tries to match the passed request or url. | |
161 * | |
162 * At least the url must be provided, if a request is additionally provided | |
163 * the request takes precedence. | |
164 * | |
165 * @param string $pathinfo | |
166 * @param Request $request | |
167 * | |
168 * @return array An array of parameters | |
169 * | |
170 * @throws ResourceNotFoundException If no router matched. | |
171 */ | |
172 private function doMatch($pathinfo, Request $request = null) | |
173 { | |
174 $methodNotAllowed = null; | |
175 | |
176 $requestForMatching = $request; | |
177 foreach ($this->all() as $router) { | |
178 try { | |
179 // the request/url match logic is the same as in Symfony/Component/HttpKernel/EventListener/RouterListener.php | |
180 // matching requests is more powerful than matching URLs only, so try that first | |
181 if ($router instanceof RequestMatcherInterface) { | |
182 if (empty($requestForMatching)) { | |
183 $requestForMatching = $this->rebuildRequest($pathinfo); | |
184 } | |
185 | |
186 return $router->matchRequest($requestForMatching); | |
187 } | |
188 | |
189 // every router implements the match method | |
190 return $router->match($pathinfo); | |
191 } catch (ResourceNotFoundException $e) { | |
192 if ($this->logger) { | |
193 $this->logger->debug('Router '.get_class($router).' was not able to match, message "'.$e->getMessage().'"'); | |
194 } | |
195 // Needs special care | |
196 } catch (MethodNotAllowedException $e) { | |
197 if ($this->logger) { | |
198 $this->logger->debug('Router '.get_class($router).' throws MethodNotAllowedException with message "'.$e->getMessage().'"'); | |
199 } | |
200 $methodNotAllowed = $e; | |
201 } | |
202 } | |
203 | |
204 $info = $request | |
205 ? "this request\n$request" | |
206 : "url '$pathinfo'"; | |
207 throw $methodNotAllowed ?: new ResourceNotFoundException("None of the routers in the chain matched $info"); | |
208 } | |
209 | |
210 /** | |
211 * {@inheritdoc} | |
212 * | |
213 * Loops through all registered routers and returns a router if one is found. | |
214 * It will always return the first route generated. | |
215 */ | |
216 public function generate($name, $parameters = array(), $absolute = UrlGeneratorInterface::ABSOLUTE_PATH) | |
217 { | |
218 $debug = array(); | |
219 | |
220 foreach ($this->all() as $router) { | |
221 // if $router does not announce it is capable of handling | |
222 // non-string routes and $name is not a string, continue | |
223 if ($name && !is_string($name) && !$router instanceof VersatileGeneratorInterface) { | |
224 continue; | |
225 } | |
226 | |
227 // If $router is versatile and doesn't support this route name, continue | |
228 if ($router instanceof VersatileGeneratorInterface && !$router->supports($name)) { | |
229 continue; | |
230 } | |
231 | |
232 try { | |
233 return $router->generate($name, $parameters, $absolute); | |
234 } catch (RouteNotFoundException $e) { | |
235 $hint = $this->getErrorMessage($name, $router, $parameters); | |
236 $debug[] = $hint; | |
237 if ($this->logger) { | |
238 $this->logger->debug('Router '.get_class($router)." was unable to generate route. Reason: '$hint': ".$e->getMessage()); | |
239 } | |
240 } | |
241 } | |
242 | |
243 if ($debug) { | |
244 $debug = array_unique($debug); | |
245 $info = implode(', ', $debug); | |
246 } else { | |
247 $info = $this->getErrorMessage($name); | |
248 } | |
249 | |
250 throw new RouteNotFoundException(sprintf('None of the chained routers were able to generate route: %s', $info)); | |
251 } | |
252 | |
253 /** | |
254 * Rebuild the request object from a URL with the help of the RequestContext. | |
255 * | |
256 * If the request context is not set, this simply returns the request object built from $uri. | |
257 * | |
258 * @param string $pathinfo | |
259 * | |
260 * @return Request | |
261 */ | |
262 private function rebuildRequest($pathinfo) | |
263 { | |
264 if (!$this->context) { | |
265 return Request::create('http://localhost'.$pathinfo); | |
266 } | |
267 | |
268 $uri = $pathinfo; | |
269 | |
270 $server = array(); | |
271 if ($this->context->getBaseUrl()) { | |
272 $uri = $this->context->getBaseUrl().$pathinfo; | |
273 $server['SCRIPT_FILENAME'] = $this->context->getBaseUrl(); | |
274 $server['PHP_SELF'] = $this->context->getBaseUrl(); | |
275 } | |
276 $host = $this->context->getHost() ?: 'localhost'; | |
277 if ('https' === $this->context->getScheme() && 443 !== $this->context->getHttpsPort()) { | |
278 $host .= ':'.$this->context->getHttpsPort(); | |
279 } | |
280 if ('http' === $this->context->getScheme() && 80 !== $this->context->getHttpPort()) { | |
281 $host .= ':'.$this->context->getHttpPort(); | |
282 } | |
283 $uri = $this->context->getScheme().'://'.$host.$uri.'?'.$this->context->getQueryString(); | |
284 | |
285 return Request::create($uri, $this->context->getMethod(), $this->context->getParameters(), array(), array(), $server); | |
286 } | |
287 | |
288 private function getErrorMessage($name, $router = null, $parameters = null) | |
289 { | |
290 if ($router instanceof VersatileGeneratorInterface) { | |
291 $displayName = $router->getRouteDebugMessage($name, $parameters); | |
292 } elseif (is_object($name)) { | |
293 $displayName = method_exists($name, '__toString') | |
294 ? (string) $name | |
295 : get_class($name) | |
296 ; | |
297 } else { | |
298 $displayName = (string) $name; | |
299 } | |
300 | |
301 return "Route '$displayName' not found"; | |
302 } | |
303 | |
304 /** | |
305 * {@inheritdoc} | |
306 */ | |
307 public function setContext(RequestContext $context) | |
308 { | |
309 foreach ($this->all() as $router) { | |
310 if ($router instanceof RequestContextAwareInterface) { | |
311 $router->setContext($context); | |
312 } | |
313 } | |
314 | |
315 $this->context = $context; | |
316 } | |
317 | |
318 /** | |
319 * {@inheritdoc} | |
320 * | |
321 * check for each contained router if it can warmup | |
322 */ | |
323 public function warmUp($cacheDir) | |
324 { | |
325 foreach ($this->all() as $router) { | |
326 if ($router instanceof WarmableInterface) { | |
327 $router->warmUp($cacheDir); | |
328 } | |
329 } | |
330 } | |
331 | |
332 /** | |
333 * {@inheritdoc} | |
334 */ | |
335 public function getRouteCollection() | |
336 { | |
337 if (!$this->routeCollection instanceof RouteCollection) { | |
338 $this->routeCollection = new ChainRouteCollection(); | |
339 foreach ($this->all() as $router) { | |
340 $this->routeCollection->addCollection($router->getRouteCollection()); | |
341 } | |
342 } | |
343 | |
344 return $this->routeCollection; | |
345 } | |
346 | |
347 /** | |
348 * Identify if any routers have been added into the chain yet. | |
349 * | |
350 * @return bool | |
351 */ | |
352 public function hasRouters() | |
353 { | |
354 return !empty($this->routers); | |
355 } | |
356 } |