Mercurial > hg > isophonics-drupal-site
diff core/lib/Drupal/Core/Routing/Router.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children | 1fec387a4317 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/lib/Drupal/Core/Routing/Router.php Wed Nov 29 16:09:58 2017 +0000 @@ -0,0 +1,391 @@ +<?php + +namespace Drupal\Core\Routing; + +use Drupal\Core\Path\CurrentPathStack; +use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface as BaseRouteEnhancerInterface; +use Symfony\Cmf\Component\Routing\LazyRouteCollection; +use Symfony\Cmf\Component\Routing\NestedMatcher\RouteFilterInterface as BaseRouteFilterInterface; +use Symfony\Cmf\Component\Routing\RouteProviderInterface as BaseRouteProviderInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface as BaseUrlGeneratorInterface; +use Symfony\Component\Routing\Matcher\RequestMatcherInterface; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RouterInterface; + +/** + * Router implementation in Drupal. + * + * A router determines, for an incoming request, the active controller, which is + * a callable that creates a response. + * + * It consists of several steps, of which each are explained in more details + * below: + * 1. Get a collection of routes which potentially match the current request. + * This is done by the route provider. See ::getInitialRouteCollection(). + * 2. Filter the collection down further more. For example this filters out + * routes applying to other formats: See ::applyRouteFilters() + * 3. Find the best matching route out of the remaining ones, by applying a + * regex. See ::matchCollection(). + * 4. Enhance the list of route attributes, for example loading entity objects. + * See ::applyRouteEnhancers(). + * + * This implementation uses ideas of the following routers: + * - \Symfony\Cmf\Component\Routing\DynamicRouter + * - \Drupal\Core\Routing\UrlMatcher + * - \Symfony\Cmf\Component\Routing\NestedMatcher\NestedMatcher + * + * @see \Symfony\Cmf\Component\Routing\DynamicRouter + * @see \Drupal\Core\Routing\UrlMatcher + * @see \Symfony\Cmf\Component\Routing\NestedMatcher\NestedMatcher + */ +class Router extends UrlMatcher implements RequestMatcherInterface, RouterInterface { + + /** + * The route provider responsible for the first-pass match. + * + * @var \Symfony\Cmf\Component\Routing\RouteProviderInterface + */ + protected $routeProvider; + + /** + * The list of available enhancers. + * + * @var \Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface[] + */ + protected $enhancers = []; + + /** + * Cached sorted list of enhancers. + * + * @var \Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface[] + */ + protected $sortedEnhancers; + + /** + * The list of available route filters. + * + * @var \Symfony\Cmf\Component\Routing\NestedMatcher\RouteFilterInterface[] + */ + protected $filters = []; + + /** + * Cached sorted list route filters. + * + * @var \Symfony\Cmf\Component\Routing\NestedMatcher\RouteFilterInterface[] + */ + protected $sortedFilters; + + /** + * The URL generator. + * + * @var \Symfony\Component\Routing\Generator\UrlGeneratorInterface + */ + protected $urlGenerator; + + /** + * Constructs a new Router. + * + * @param \Symfony\Cmf\Component\Routing\RouteProviderInterface $route_provider + * The route provider. + * @param \Drupal\Core\Path\CurrentPathStack $current_path + * The current path stack. + * @param \Symfony\Component\Routing\Generator\UrlGeneratorInterface $url_generator + * The URL generator. + */ + public function __construct(BaseRouteProviderInterface $route_provider, CurrentPathStack $current_path, BaseUrlGeneratorInterface $url_generator) { + parent::__construct($current_path); + $this->routeProvider = $route_provider; + $this->urlGenerator = $url_generator; + } + + /** + * Adds a route enhancer to the list of used route enhancers. + * + * @param \Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface $route_enhancer + * A route enhancer. + * @param int $priority + * (optional) The priority of the enhancer. Higher number enhancers will be + * used first. + * + * @return $this + */ + public function addRouteEnhancer(BaseRouteEnhancerInterface $route_enhancer, $priority = 0) { + $this->enhancers[$priority][] = $route_enhancer; + return $this; + } + + /** + * Adds a route filter to the list of used route filters. + * + * @param \Symfony\Cmf\Component\Routing\NestedMatcher\RouteFilterInterface $route_filter + * A route filter. + * @param int $priority + * (optional) The priority of the filter. Higher number filters will be used + * first. + * + * @return $this + */ + public function addRouteFilter(BaseRouteFilterInterface $route_filter, $priority = 0) { + $this->filters[$priority][] = $route_filter; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function match($pathinfo) { + $request = Request::create($pathinfo); + + return $this->matchRequest($request); + } + + /** + * {@inheritdoc} + */ + public function matchRequest(Request $request) { + $collection = $this->getInitialRouteCollection($request); + $collection = $this->applyRouteFilters($collection, $request); + + if ($ret = $this->matchCollection(rawurldecode($this->currentPath->getPath($request)), $collection)) { + return $this->applyRouteEnhancers($ret, $request); + } + + throw 0 < count($this->allow) + ? new MethodNotAllowedException(array_unique($this->allow)) + : new ResourceNotFoundException(sprintf('No routes found for "%s".', $this->currentPath->getPath())); + } + + /** + * Tries to match a URL with a set of routes. + * + * @param string $pathinfo + * The path info to be parsed + * @param \Symfony\Component\Routing\RouteCollection $routes + * The set of routes. + * + * @return array|null + * An array of parameters. NULL when there is no match. + */ + protected function matchCollection($pathinfo, RouteCollection $routes) { + // Try a case-sensitive match. + $match = $this->doMatchCollection($pathinfo, $routes, TRUE); + // Try a case-insensitive match. + if ($match === NULL && $routes->count() > 0) { + $match = $this->doMatchCollection($pathinfo, $routes, FALSE); + } + return $match; + } + + /** + * Tries to match a URL with a set of routes. + * + * This code is very similar to Symfony's UrlMatcher::matchCollection() but it + * supports case-insensitive matching. The static prefix optimization is + * removed as this duplicates work done by the query in + * RouteProvider::getRoutesByPath(). + * + * @param string $pathinfo + * The path info to be parsed + * @param \Symfony\Component\Routing\RouteCollection $routes + * The set of routes. + * @param bool $case_sensitive + * Determines if the match should be case-sensitive of not. + * + * @return array|null + * An array of parameters. NULL when there is no match. + * + * @see \Symfony\Component\Routing\Matcher\UrlMatcher::matchCollection() + * @see \Drupal\Core\Routing\RouteProvider::getRoutesByPath() + */ + protected function doMatchCollection($pathinfo, RouteCollection $routes, $case_sensitive) { + foreach ($routes as $name => $route) { + $compiledRoute = $route->compile(); + + // Set the regex to use UTF-8. + $regex = $compiledRoute->getRegex() . 'u'; + if (!$case_sensitive) { + $regex = $regex . 'i'; + } + if (!preg_match($regex, $pathinfo, $matches)) { + continue; + } + + $hostMatches = []; + if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) { + $routes->remove($name); + 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); + $routes->remove($name); + continue; + } + } + + $status = $this->handleRouteRequirements($pathinfo, $name, $route); + + if (self::ROUTE_MATCH === $status[0]) { + return $status[1]; + } + + if (self::REQUIREMENT_MISMATCH === $status[0]) { + $routes->remove($name); + continue; + } + + return $this->getAttributes($route, $name, array_replace($matches, $hostMatches)); + } + } + + /** + * Returns a collection of potential matching routes for a request. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The current request. + * + * @return \Symfony\Component\Routing\RouteCollection + * The initial fetched route collection. + */ + protected function getInitialRouteCollection(Request $request) { + return $this->routeProvider->getRouteCollectionForRequest($request); + } + + /** + * Apply the route enhancers to the defaults, according to priorities. + * + * @param array $defaults + * The defaults coming from the final matched route. + * @param \Symfony\Component\HttpFoundation\Request $request + * The request. + * + * @return array + * The request attributes after applying the enhancers. This might consist + * raw values from the URL but also upcasted values, like entity objects, + * from route enhancers. + */ + protected function applyRouteEnhancers($defaults, Request $request) { + foreach ($this->getRouteEnhancers() as $enhancer) { + $defaults = $enhancer->enhance($defaults, $request); + } + + return $defaults; + } + + /** + * Sorts the enhancers and flattens them. + * + * @return \Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface[] + * The enhancers ordered by priority. + */ + public function getRouteEnhancers() { + if (!isset($this->sortedEnhancers)) { + $this->sortedEnhancers = $this->sortRouteEnhancers(); + } + + return $this->sortedEnhancers; + } + + /** + * Sort enhancers by priority. + * + * The highest priority number is the highest priority (reverse sorting). + * + * @return \Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface[] + * The sorted enhancers. + */ + protected function sortRouteEnhancers() { + $sortedEnhancers = []; + krsort($this->enhancers); + + foreach ($this->enhancers as $enhancers) { + $sortedEnhancers = array_merge($sortedEnhancers, $enhancers); + } + + return $sortedEnhancers; + } + + /** + * Applies all route filters to a given route collection. + * + * This method reduces the sets of routes further down, for example by + * checking the HTTP method. + * + * @param \Symfony\Component\Routing\RouteCollection $collection + * The route collection. + * @param \Symfony\Component\HttpFoundation\Request $request + * The request. + * + * @return \Symfony\Component\Routing\RouteCollection + * The filtered/sorted route collection. + */ + protected function applyRouteFilters(RouteCollection $collection, Request $request) { + // Route filters are expected to throw an exception themselves if they + // end up filtering the list down to 0. + foreach ($this->getRouteFilters() as $filter) { + $collection = $filter->filter($collection, $request); + } + + return $collection; + } + + /** + * Sorts the filters and flattens them. + * + * @return \Symfony\Cmf\Component\Routing\NestedMatcher\RouteFilterInterface[] + * The filters ordered by priority + */ + public function getRouteFilters() { + if (!isset($this->sortedFilters)) { + $this->sortedFilters = $this->sortFilters(); + } + + return $this->sortedFilters; + } + + /** + * Sort filters by priority. + * + * The highest priority number is the highest priority (reverse sorting). + * + * @return \Symfony\Cmf\Component\Routing\NestedMatcher\RouteFilterInterface[] + * The sorted filters. + */ + protected function sortFilters() { + $sortedFilters = []; + krsort($this->filters); + + foreach ($this->filters as $filters) { + $sortedFilters = array_merge($sortedFilters, $filters); + } + + return $sortedFilters; + } + + /** + * {@inheritdoc} + */ + public function getRouteCollection() { + return new LazyRouteCollection($this->routeProvider); + } + + /** + * {@inheritdoc} + */ + public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH) { + @trigger_error('Use the \Drupal\Core\Url object instead', E_USER_DEPRECATED); + return $this->urlGenerator->generate($name, $parameters, $referenceType); + } + +}