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 }