You are here

class ChainRouter in Zircon Profile 8

Same name and namespace in other branches
  1. 8.0 vendor/symfony-cmf/routing/ChainRouter.php \Symfony\Cmf\Component\Routing\ChainRouter

The ChainRouter allows to combine several routers to try in a defined order.

@author Henrik Bjornskov <henrik@bjrnskov.dk> @author Magnus Nordlander <magnus@e-butik.se>

Hierarchy

Expanded class hierarchy of ChainRouter

2 files declare their use of ChainRouter
AccessAwareRouter.php in core/lib/Drupal/Core/Routing/AccessAwareRouter.php
Contains \Drupal\Core\Routing\AccessAwareRouter.
ChainRouterTest.php in vendor/symfony-cmf/routing/Tests/Routing/ChainRouterTest.php
1 string reference to 'ChainRouter'
core.services.yml in core/core.services.yml
core/core.services.yml
1 service uses ChainRouter
router.no_access_checks in core/core.services.yml
Symfony\Cmf\Component\Routing\ChainRouter

File

vendor/symfony-cmf/routing/ChainRouter.php, line 33

Namespace

Symfony\Cmf\Component\Routing
View source
class ChainRouter implements ChainRouterInterface, WarmableInterface {

  /**
   * @var RequestContext
   */
  private $context;

  /**
   * Array of arrays of routers grouped by priority
   * @var array
   */
  private $routers = array();

  /**
   * @var RouterInterface[] Array of routers, sorted by priority
   */
  private $sortedRouters;

  /**
   * @var RouteCollection
   */
  private $routeCollection;

  /**
   * @var null|LoggerInterface
   */
  protected $logger;

  /**
   * @param LoggerInterface $logger
   */
  public function __construct(LoggerInterface $logger = null) {
    $this->logger = $logger;
  }

  /**
   * @return RequestContext
   */
  public function getContext() {
    return $this->context;
  }

  /**
   * {@inheritdoc}
   */
  public function add($router, $priority = 0) {
    if (!$router instanceof RouterInterface && !($router instanceof RequestMatcherInterface && $router instanceof UrlGeneratorInterface)) {
      throw new \InvalidArgumentException(sprintf('%s is not a valid router.', get_class($router)));
    }
    if (empty($this->routers[$priority])) {
      $this->routers[$priority] = array();
    }
    $this->routers[$priority][] = $router;
    $this->sortedRouters = array();
  }

  /**
   * {@inheritdoc}
   */
  public function all() {
    if (empty($this->sortedRouters)) {
      $this->sortedRouters = $this
        ->sortRouters();

      // setContext() is done here instead of in add() to avoid fatal errors when clearing and warming up caches
      // See https://github.com/symfony-cmf/Routing/pull/18
      $context = $this
        ->getContext();
      if (null !== $context) {
        foreach ($this->sortedRouters as $router) {
          if ($router instanceof RequestContextAwareInterface) {
            $router
              ->setContext($context);
          }
        }
      }
    }
    return $this->sortedRouters;
  }

  /**
   * Sort routers by priority.
   * The highest priority number is the highest priority (reverse sorting)
   *
   * @return RouterInterface[]
   */
  protected function sortRouters() {
    $sortedRouters = array();
    krsort($this->routers);
    foreach ($this->routers as $routers) {
      $sortedRouters = array_merge($sortedRouters, $routers);
    }
    return $sortedRouters;
  }

  /**
   * {@inheritdoc}
   *
   * Loops through all routes and tries to match the passed url.
   *
   * Note: You should use matchRequest if you can.
   */
  public function match($url) {
    return $this
      ->doMatch($url);
  }

  /**
   * {@inheritdoc}
   *
   * Loops through all routes and tries to match the passed request.
   */
  public function matchRequest(Request $request) {
    return $this
      ->doMatch($request
      ->getPathInfo(), $request);
  }

  /**
   * Loops through all routers and tries to match the passed request or url.
   *
   * At least the  url must be provided, if a request is additionally provided
   * the request takes precedence.
   *
   * @param string  $url
   * @param Request $request
   *
   * @return array An array of parameters
   *
   * @throws ResourceNotFoundException If no router matched.
   */
  private function doMatch($url, Request $request = null) {
    $methodNotAllowed = null;
    $requestForMatching = $request;
    foreach ($this
      ->all() as $router) {
      try {

        // the request/url match logic is the same as in Symfony/Component/HttpKernel/EventListener/RouterListener.php
        // matching requests is more powerful than matching URLs only, so try that first
        if ($router instanceof RequestMatcherInterface) {
          if (empty($requestForMatching)) {
            $requestForMatching = Request::create($url);
          }
          return $router
            ->matchRequest($requestForMatching);
        }

        // every router implements the match method
        return $router
          ->match($url);
      } catch (ResourceNotFoundException $e) {
        if ($this->logger) {
          $this->logger
            ->debug('Router ' . get_class($router) . ' was not able to match, message "' . $e
            ->getMessage() . '"');
        }

        // Needs special care
      } catch (MethodNotAllowedException $e) {
        if ($this->logger) {
          $this->logger
            ->debug('Router ' . get_class($router) . ' throws MethodNotAllowedException with message "' . $e
            ->getMessage() . '"');
        }
        $methodNotAllowed = $e;
      }
    }
    $info = $request ? "this request\n{$request}" : "url '{$url}'";
    throw $methodNotAllowed ?: new ResourceNotFoundException("None of the routers in the chain matched {$info}");
  }

  /**
   * {@inheritdoc}
   *
   * Loops through all registered routers and returns a router if one is found.
   * It will always return the first route generated.
   */
  public function generate($name, $parameters = array(), $absolute = false) {
    $debug = array();
    foreach ($this
      ->all() as $router) {

      // if $router does not announce it is capable of handling
      // non-string routes and $name is not a string, continue
      if ($name && !is_string($name) && !$router instanceof VersatileGeneratorInterface) {
        continue;
      }

      // If $router is versatile and doesn't support this route name, continue
      if ($router instanceof VersatileGeneratorInterface && !$router
        ->supports($name)) {
        continue;
      }
      try {
        return $router
          ->generate($name, $parameters, $absolute);
      } catch (RouteNotFoundException $e) {
        $hint = $this
          ->getErrorMessage($name, $router, $parameters);
        $debug[] = $hint;
        if ($this->logger) {
          $this->logger
            ->debug('Router ' . get_class($router) . " was unable to generate route. Reason: '{$hint}': " . $e
            ->getMessage());
        }
      }
    }
    if ($debug) {
      $debug = array_unique($debug);
      $info = implode(', ', $debug);
    }
    else {
      $info = $this
        ->getErrorMessage($name);
    }
    throw new RouteNotFoundException(sprintf('None of the chained routers were able to generate route: %s', $info));
  }
  private function getErrorMessage($name, $router = null, $parameters = null) {
    if ($router instanceof VersatileGeneratorInterface) {
      $displayName = $router
        ->getRouteDebugMessage($name, $parameters);
    }
    elseif (is_object($name)) {
      $displayName = method_exists($name, '__toString') ? (string) $name : get_class($name);
    }
    else {
      $displayName = (string) $name;
    }
    return "Route '{$displayName}' not found";
  }

  /**
   * {@inheritdoc}
   */
  public function setContext(RequestContext $context) {
    foreach ($this
      ->all() as $router) {
      if ($router instanceof RequestContextAwareInterface) {
        $router
          ->setContext($context);
      }
    }
    $this->context = $context;
  }

  /**
   * {@inheritdoc}
   *
   * check for each contained router if it can warmup
   */
  public function warmUp($cacheDir) {
    foreach ($this
      ->all() as $router) {
      if ($router instanceof WarmableInterface) {
        $router
          ->warmUp($cacheDir);
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getRouteCollection() {
    if (!$this->routeCollection instanceof RouteCollection) {
      $this->routeCollection = new ChainRouteCollection();
      foreach ($this
        ->all() as $router) {
        $this->routeCollection
          ->addCollection($router
          ->getRouteCollection());
      }
    }
    return $this->routeCollection;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
ChainRouter::$context private property
ChainRouter::$logger protected property
ChainRouter::$routeCollection private property
ChainRouter::$routers private property Array of arrays of routers grouped by priority
ChainRouter::$sortedRouters private property
ChainRouter::add public function Add a Router to the index. Overrides ChainRouterInterface::add
ChainRouter::all public function Sorts the routers and flattens them. Overrides ChainRouterInterface::all
ChainRouter::doMatch private function Loops through all routers and tries to match the passed request or url.
ChainRouter::generate public function Loops through all registered routers and returns a router if one is found. It will always return the first route generated. Overrides UrlGeneratorInterface::generate
ChainRouter::getContext public function Overrides RequestContextAwareInterface::getContext
ChainRouter::getErrorMessage private function
ChainRouter::getRouteCollection public function Gets the RouteCollection instance associated with this Router. Overrides RouterInterface::getRouteCollection
ChainRouter::match public function Loops through all routes and tries to match the passed url. Overrides UrlMatcherInterface::match
ChainRouter::matchRequest public function Loops through all routes and tries to match the passed request. Overrides RequestMatcherInterface::matchRequest
ChainRouter::setContext public function Sets the request context. Overrides RequestContextAwareInterface::setContext
ChainRouter::sortRouters protected function Sort routers by priority. The highest priority number is the highest priority (reverse sorting)
ChainRouter::warmUp public function check for each contained router if it can warmup Overrides WarmableInterface::warmUp
ChainRouter::__construct public function
UrlGeneratorInterface::ABSOLUTE_PATH constant Generates an absolute path, e.g. "/dir/file".
UrlGeneratorInterface::ABSOLUTE_URL constant Generates an absolute URL, e.g. "http://example.com/dir/file".
UrlGeneratorInterface::NETWORK_PATH constant Generates a network path, e.g. "//example.com/dir/file". Such reference reuses the current scheme but specifies the host.
UrlGeneratorInterface::RELATIVE_PATH constant Generates a relative path based on the current request path, e.g. "../parent-file".