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