Fix bug in style changes for the Use cases on the live site.
[yaffs-website] / vendor / symfony-cmf / routing / ChainRouter.php
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 }