You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
251 lines
7.4 KiB
251 lines
7.4 KiB
<?php |
|
|
|
/* |
|
* This file is part of the Symfony package. |
|
* |
|
* (c) Fabien Potencier <fabien@symfony.com> |
|
* |
|
* For the full copyright and license information, please view the LICENSE |
|
* file that was distributed with this source code. |
|
*/ |
|
|
|
namespace Symfony\Component\Routing\Matcher; |
|
|
|
use Symfony\Component\Routing\Exception\MethodNotAllowedException; |
|
use Symfony\Component\Routing\Exception\ResourceNotFoundException; |
|
use Symfony\Component\Routing\RouteCollection; |
|
use Symfony\Component\Routing\RequestContext; |
|
use Symfony\Component\Routing\Route; |
|
use Symfony\Component\HttpFoundation\Request; |
|
use Symfony\Component\ExpressionLanguage\ExpressionLanguage; |
|
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; |
|
|
|
/** |
|
* UrlMatcher matches URL based on a set of routes. |
|
* |
|
* @author Fabien Potencier <fabien@symfony.com> |
|
*/ |
|
class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface |
|
{ |
|
const REQUIREMENT_MATCH = 0; |
|
const REQUIREMENT_MISMATCH = 1; |
|
const ROUTE_MATCH = 2; |
|
|
|
/** |
|
* @var RequestContext |
|
*/ |
|
protected $context; |
|
|
|
/** |
|
* @var array |
|
*/ |
|
protected $allow = array(); |
|
|
|
/** |
|
* @var RouteCollection |
|
*/ |
|
protected $routes; |
|
|
|
protected $request; |
|
protected $expressionLanguage; |
|
|
|
/** |
|
* @var ExpressionFunctionProviderInterface[] |
|
*/ |
|
protected $expressionLanguageProviders = array(); |
|
|
|
/** |
|
* Constructor. |
|
* |
|
* @param RouteCollection $routes A RouteCollection instance |
|
* @param RequestContext $context The context |
|
*/ |
|
public function __construct(RouteCollection $routes, RequestContext $context) |
|
{ |
|
$this->routes = $routes; |
|
$this->context = $context; |
|
} |
|
|
|
/** |
|
* {@inheritdoc} |
|
*/ |
|
public function setContext(RequestContext $context) |
|
{ |
|
$this->context = $context; |
|
} |
|
|
|
/** |
|
* {@inheritdoc} |
|
*/ |
|
public function getContext() |
|
{ |
|
return $this->context; |
|
} |
|
|
|
/** |
|
* {@inheritdoc} |
|
*/ |
|
public function match($pathinfo) |
|
{ |
|
$this->allow = array(); |
|
|
|
if ($ret = $this->matchCollection(rawurldecode($pathinfo), $this->routes)) { |
|
return $ret; |
|
} |
|
|
|
throw 0 < count($this->allow) |
|
? new MethodNotAllowedException(array_unique($this->allow)) |
|
: new ResourceNotFoundException(sprintf('No routes found for "%s".', $pathinfo)); |
|
} |
|
|
|
/** |
|
* {@inheritdoc} |
|
*/ |
|
public function matchRequest(Request $request) |
|
{ |
|
$this->request = $request; |
|
|
|
$ret = $this->match($request->getPathInfo()); |
|
|
|
$this->request = null; |
|
|
|
return $ret; |
|
} |
|
|
|
public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) |
|
{ |
|
$this->expressionLanguageProviders[] = $provider; |
|
} |
|
|
|
/** |
|
* Tries to match a URL with a set of routes. |
|
* |
|
* @param string $pathinfo The path info to be parsed |
|
* @param RouteCollection $routes The set of routes |
|
* |
|
* @return array An array of parameters |
|
* |
|
* @throws ResourceNotFoundException If the resource could not be found |
|
* @throws MethodNotAllowedException If the resource was found but the request method is not allowed |
|
*/ |
|
protected function matchCollection($pathinfo, RouteCollection $routes) |
|
{ |
|
foreach ($routes as $name => $route) { |
|
$compiledRoute = $route->compile(); |
|
|
|
// check the static prefix of the URL first. Only use the more expensive preg_match when it matches |
|
if ('' !== $compiledRoute->getStaticPrefix() && 0 !== strpos($pathinfo, $compiledRoute->getStaticPrefix())) { |
|
continue; |
|
} |
|
|
|
if (!preg_match($compiledRoute->getRegex(), $pathinfo, $matches)) { |
|
continue; |
|
} |
|
|
|
$hostMatches = array(); |
|
if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) { |
|
continue; |
|
} |
|
|
|
// check HTTP method requirement |
|
if ($requiredMethods = $route->getMethods()) { |
|
// HEAD and GET are equivalent as per RFC |
|
if ('HEAD' === $method = $this->context->getMethod()) { |
|
$method = 'GET'; |
|
} |
|
|
|
if (!in_array($method, $requiredMethods)) { |
|
$this->allow = array_merge($this->allow, $requiredMethods); |
|
|
|
continue; |
|
} |
|
} |
|
|
|
$status = $this->handleRouteRequirements($pathinfo, $name, $route); |
|
|
|
if (self::ROUTE_MATCH === $status[0]) { |
|
return $status[1]; |
|
} |
|
|
|
if (self::REQUIREMENT_MISMATCH === $status[0]) { |
|
continue; |
|
} |
|
|
|
return $this->getAttributes($route, $name, array_replace($matches, $hostMatches)); |
|
} |
|
} |
|
|
|
/** |
|
* Returns an array of values to use as request attributes. |
|
* |
|
* As this method requires the Route object, it is not available |
|
* in matchers that do not have access to the matched Route instance |
|
* (like the PHP and Apache matcher dumpers). |
|
* |
|
* @param Route $route The route we are matching against |
|
* @param string $name The name of the route |
|
* @param array $attributes An array of attributes from the matcher |
|
* |
|
* @return array An array of parameters |
|
*/ |
|
protected function getAttributes(Route $route, $name, array $attributes) |
|
{ |
|
$attributes['_route'] = $name; |
|
|
|
return $this->mergeDefaults($attributes, $route->getDefaults()); |
|
} |
|
|
|
/** |
|
* Handles specific route requirements. |
|
* |
|
* @param string $pathinfo The path |
|
* @param string $name The route name |
|
* @param Route $route The route |
|
* |
|
* @return array The first element represents the status, the second contains additional information |
|
*/ |
|
protected function handleRouteRequirements($pathinfo, $name, Route $route) |
|
{ |
|
// expression condition |
|
if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), array('context' => $this->context, 'request' => $this->request))) { |
|
return array(self::REQUIREMENT_MISMATCH, null); |
|
} |
|
|
|
// check HTTP scheme requirement |
|
$scheme = $this->context->getScheme(); |
|
$status = $route->getSchemes() && !$route->hasScheme($scheme) ? self::REQUIREMENT_MISMATCH : self::REQUIREMENT_MATCH; |
|
|
|
return array($status, null); |
|
} |
|
|
|
/** |
|
* Get merged default parameters. |
|
* |
|
* @param array $params The parameters |
|
* @param array $defaults The defaults |
|
* |
|
* @return array Merged default parameters |
|
*/ |
|
protected function mergeDefaults($params, $defaults) |
|
{ |
|
foreach ($params as $key => $value) { |
|
if (!is_int($key)) { |
|
$defaults[$key] = $value; |
|
} |
|
} |
|
|
|
return $defaults; |
|
} |
|
|
|
protected function getExpressionLanguage() |
|
{ |
|
if (null === $this->expressionLanguage) { |
|
if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { |
|
throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); |
|
} |
|
$this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders); |
|
} |
|
|
|
return $this->expressionLanguage; |
|
} |
|
}
|
|
|