class Router

Same name in other branches
  1. 9 core/lib/Drupal/Core/Routing/Router.php \Drupal\Core\Routing\Router
  2. 8.9.x core/lib/Drupal/Core/Routing/Router.php \Drupal\Core\Routing\Router
  3. 10 core/lib/Drupal/Core/Routing/Router.php \Drupal\Core\Routing\Router

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().

Hierarchy

  • class \Drupal\Core\Routing\UrlMatcher extends \Symfony\Component\Routing\Matcher\UrlMatcher
    • class \Drupal\Core\Routing\Router extends \Drupal\Core\Routing\UrlMatcher implements \Symfony\Component\Routing\Matcher\RequestMatcherInterface, \Symfony\Component\Routing\RouterInterface

Expanded class hierarchy of Router

2 files declare their use of Router
RouterTest.php in core/tests/Drupal/Tests/Core/Routing/RouterTest.php
RouterUnsupportedTest.php in core/tests/Drupal/Tests/Core/Routing/RouterUnsupportedTest.php
6 string references to 'Router'
ConfigTranslationController::create in core/modules/config_translation/src/Controller/ConfigTranslationController.php
DbDumpCommandTest::testSchemaOnly in core/modules/mysql/tests/src/Kernel/mysql/Console/DbDumpCommandTest.php
Tests schema only option.
DynamicLocalTasks::create in core/modules/content_moderation/src/Plugin/Derivative/DynamicLocalTasks.php
Creates a new class instance.
LanguageNegotiationUserAdmin::create in core/modules/user/src/Plugin/LanguageNegotiation/LanguageNegotiationUserAdmin.php
Creates an instance of the plugin.
ModuleInstallerTest::testRouteRebuild in core/tests/Drupal/KernelTests/Core/Extension/ModuleInstallerTest.php
Tests that routes are rebuilt during install and uninstall of modules.

... See full list

File

core/lib/Drupal/Core/Routing/Router.php, line 31

Namespace

Drupal\Core\Routing
View source
class Router extends UrlMatcher implements RequestMatcherInterface, RouterInterface {
    
    /**
     * The route provider responsible for the first-pass match.
     *
     * @var \Drupal\Core\Routing\RouteProviderInterface
     */
    protected $routeProvider;
    
    /**
     * The list of available enhancers.
     *
     * @var \Drupal\Core\Routing\EnhancerInterface[]
     */
    protected $enhancers = [];
    
    /**
     * The list of available route filters.
     *
     * @var \Drupal\Core\Routing\FilterInterface[]
     */
    protected $filters = [];
    
    /**
     * The URL generator.
     *
     * @var \Symfony\Component\Routing\Generator\UrlGeneratorInterface
     */
    protected $urlGenerator;
    
    /**
     * Constructs a new Router.
     *
     * @param \Drupal\Core\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(RouteProviderInterface $route_provider, CurrentPathStack $current_path, BaseUrlGeneratorInterface $url_generator) {
        parent::__construct($current_path);
        $this->routeProvider = $route_provider;
        $this->urlGenerator = $url_generator;
    }
    
    /**
     * Adds a route filter.
     *
     * @param \Drupal\Core\Routing\FilterInterface $route_filter
     *   The route filter.
     */
    public function addRouteFilter(FilterInterface $route_filter) {
        $this->filters[] = $route_filter;
    }
    
    /**
     * Adds a route enhancer.
     *
     * @param \Drupal\Core\Routing\EnhancerInterface $route_enhancer
     *   The route enhancer.
     */
    public function addRouteEnhancer(EnhancerInterface $route_enhancer) {
        $this->enhancers[] = $route_enhancer;
    }
    
    /**
     * {@inheritdoc}
     */
    public function match($pathinfo) : array {
        $request = Request::create($pathinfo);
        return $this->matchRequest($request);
    }
    
    /**
     * {@inheritdoc}
     */
    public function matchRequest(Request $request) : array {
        $collection = $this->getInitialRouteCollection($request);
        if ($collection->count() === 0) {
            throw new ResourceNotFoundException(sprintf('No routes found for "%s".', $this->currentPath
                ->getPath()));
        }
        $collection = $this->applyRouteFilters($collection, $request);
        $collection = $this->applyFitOrder($collection);
        $ret = $this->matchCollection(rawurldecode($this->currentPath
            ->getPath($request)), $collection);
        return $this->applyRouteEnhancers($ret, $request);
    }
    
    /**
     * {@inheritdoc}
     */
    protected function matchCollection($pathinfo, RouteCollection $routes) : array {
        // 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);
        }
        if ($match === NULL) {
            throw 0 < count($this->allow) ? new MethodNotAllowedException(array_unique($this->allow)) : new ResourceNotFoundException(sprintf('No routes found for "%s".', $this->currentPath
                ->getPath()));
        }
        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;
                }
            }
            $attributes = $this->getAttributes($route, $name, array_replace($matches, $hostMatches));
            $status = $this->handleRouteRequirements($pathinfo, $name, $route, $attributes);
            if (self::ROUTE_MATCH === $status[0]) {
                return $status[1];
            }
            if (self::REQUIREMENT_MISMATCH === $status[0]) {
                $routes->remove($name);
                continue;
            }
            return $attributes;
        }
    }
    
    /**
     * 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->enhancers as $enhancer) {
            $defaults = $enhancer->enhance($defaults, $request);
        }
        return $defaults;
    }
    
    /**
     * 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->filters as $filter) {
            $collection = $filter->filter($collection, $request);
        }
        return $collection;
    }
    
    /**
     * Reapplies the fit order to a RouteCollection object.
     *
     * Route filters can reorder route collections. For example, routes with an
     * explicit _format requirement will be preferred. This can result in a less
     * fit route being used. For example, as a result of filtering /user/% comes
     * before /user/login. In order to not break this fundamental property of
     * routes, we need to reapply the fit order. We also need to ensure that order
     * within each group of the same fit is preserved.
     *
     * @param \Symfony\Component\Routing\RouteCollection $collection
     *   The route collection.
     *
     * @return \Symfony\Component\Routing\RouteCollection
     *   The reordered route collection.
     */
    protected function applyFitOrder(RouteCollection $collection) {
        $buckets = [];
        // Sort all the routes by fit descending.
        foreach ($collection->all() as $name => $route) {
            $fit = $route->compile()
                ->getFit();
            $buckets += [
                $fit => [],
            ];
            $buckets[$fit][] = [
                $name,
                $route,
            ];
        }
        krsort($buckets);
        $flattened = array_reduce($buckets, 'array_merge', []);
        // Add them back onto a new route collection.
        $collection = new RouteCollection();
        foreach ($flattened as $pair) {
            $name = $pair[0];
            $route = $pair[1];
            $collection->add($name, $route);
        }
        return $collection;
    }
    
    /**
     * {@inheritdoc}
     */
    public function getRouteCollection() : RouteCollection {
        return new LazyRouteCollection($this->routeProvider);
    }
    
    /**
     * This method is intentionally not implemented.
     *
     * Use Drupal\Core\Url instead.
     *
     * @see https://www.drupal.org/node/2820197
     *
     * @throws \BadMethodCallException
     */
    public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH) : string {
        throw new \BadMethodCallException(__METHOD__ . '() is not supported. Use the \\Drupal\\Core\\Url object instead. See https://www.drupal.org/node/2820197');
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title
Router::$enhancers protected property The list of available enhancers.
Router::$filters protected property The list of available route filters.
Router::$routeProvider protected property The route provider responsible for the first-pass match.
Router::$urlGenerator protected property The URL generator.
Router::addRouteEnhancer public function Adds a route enhancer.
Router::addRouteFilter public function Adds a route filter.
Router::applyFitOrder protected function Reapplies the fit order to a RouteCollection object.
Router::applyRouteEnhancers protected function Apply the route enhancers to the defaults, according to priorities.
Router::applyRouteFilters protected function Applies all route filters to a given route collection.
Router::doMatchCollection protected function Tries to match a URL with a set of routes.
Router::generate public function This method is intentionally not implemented.
Router::getInitialRouteCollection protected function Returns a collection of potential matching routes for a request.
Router::getRouteCollection public function
Router::match public function
Router::matchCollection protected function
Router::matchRequest public function
Router::__construct public function Constructs a new Router. Overrides UrlMatcher::__construct
UrlMatcher::$currentPath protected property The current path.
UrlMatcher::finalMatch public function
UrlMatcher::getAttributes protected function

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.