9786a9b42cc609918ec0bd50e8eecab6a250853d
[yaffs-website] / vendor / symfony / routing / Matcher / UrlMatcher.php
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 }