4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\Routing\Matcher;
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;
24 * UrlMatcher matches URL based on a set of routes.
26 * @author Fabien Potencier <fabien@symfony.com>
28 class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
30 const REQUIREMENT_MATCH = 0;
31 const REQUIREMENT_MISMATCH = 1;
32 const ROUTE_MATCH = 2;
42 protected $allow = array();
45 * @var RouteCollection
50 protected $expressionLanguage;
53 * @var ExpressionFunctionProviderInterface[]
55 protected $expressionLanguageProviders = array();
60 * @param RouteCollection $routes A RouteCollection instance
61 * @param RequestContext $context The context
63 public function __construct(RouteCollection $routes, RequestContext $context)
65 $this->routes = $routes;
66 $this->context = $context;
72 public function setContext(RequestContext $context)
74 $this->context = $context;
80 public function getContext()
82 return $this->context;
88 public function match($pathinfo)
90 $this->allow = array();
92 if ($ret = $this->matchCollection(rawurldecode($pathinfo), $this->routes)) {
96 throw 0 < count($this->allow)
97 ? new MethodNotAllowedException(array_unique($this->allow))
98 : new ResourceNotFoundException(sprintf('No routes found for "%s".', $pathinfo));
104 public function matchRequest(Request $request)
106 $this->request = $request;
108 $ret = $this->match($request->getPathInfo());
110 $this->request = null;
115 public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider)
117 $this->expressionLanguageProviders[] = $provider;
121 * Tries to match a URL with a set of routes.
123 * @param string $pathinfo The path info to be parsed
124 * @param RouteCollection $routes The set of routes
126 * @return array An array of parameters
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
131 protected function matchCollection($pathinfo, RouteCollection $routes)
133 foreach ($routes as $name => $route) {
134 $compiledRoute = $route->compile();
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())) {
141 if (!preg_match($compiledRoute->getRegex(), $pathinfo, $matches)) {
145 $hostMatches = array();
146 if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) {
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()) {
157 if (!in_array($method, $requiredMethods)) {
158 $this->allow = array_merge($this->allow, $requiredMethods);
164 $status = $this->handleRouteRequirements($pathinfo, $name, $route);
166 if (self::ROUTE_MATCH === $status[0]) {
170 if (self::REQUIREMENT_MISMATCH === $status[0]) {
174 return $this->getAttributes($route, $name, array_replace($matches, $hostMatches));
179 * Returns an array of values to use as request attributes.
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).
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
189 * @return array An array of parameters
191 protected function getAttributes(Route $route, $name, array $attributes)
193 $attributes['_route'] = $name;
195 return $this->mergeDefaults($attributes, $route->getDefaults());
199 * Handles specific route requirements.
201 * @param string $pathinfo The path
202 * @param string $name The route name
203 * @param Route $route The route
205 * @return array The first element represents the status, the second contains additional information
207 protected function handleRouteRequirements($pathinfo, $name, Route $route)
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);
214 // check HTTP scheme requirement
215 $scheme = $this->context->getScheme();
216 $status = $route->getSchemes() && !$route->hasScheme($scheme) ? self::REQUIREMENT_MISMATCH : self::REQUIREMENT_MATCH;
218 return array($status, null);
222 * Get merged default parameters.
224 * @param array $params The parameters
225 * @param array $defaults The defaults
227 * @return array Merged default parameters
229 protected function mergeDefaults($params, $defaults)
231 foreach ($params as $key => $value) {
233 $defaults[$key] = $value;
240 protected function getExpressionLanguage()
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.');
246 $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders);
249 return $this->expressionLanguage;