RouteCollection.php

Namespace

Symfony\Component\Routing

File

drupal/core/vendor/symfony/routing/Symfony/Component/Routing/RouteCollection.php
View source
<?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;

use Symfony\Component\Config\Resource\ResourceInterface;

/**
 * A RouteCollection represents a set of Route instances as a tree structure.
 *
 * When adding a route, it overrides existing routes with the
 * same name defined in the instance or its children and parents.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Tobias Schultze <http://tobion.de>
 *
 * @api
 */
class RouteCollection implements \IteratorAggregate, \Countable {
  private $routes;
  private $resources;
  private $prefix;
  private $parent;

  /**
   * Constructor.
   *
   * @api
   */
  public function __construct() {
    $this->routes = array();
    $this->resources = array();
    $this->prefix = '';
  }
  public function __clone() {
    foreach ($this->routes as $name => $route) {
      $this->routes[$name] = clone $route;
      if ($route instanceof RouteCollection) {
        $this->routes[$name]
          ->setParent($this);
      }
    }
  }

  /**
   * Gets the parent RouteCollection.
   *
   * @return RouteCollection|null The parent RouteCollection or null when it's the root
   */
  public function getParent() {
    return $this->parent;
  }

  /**
   * Gets the root RouteCollection of the tree.
   *
   * @return RouteCollection The root RouteCollection
   */
  public function getRoot() {
    $parent = $this;
    while ($parent
      ->getParent()) {
      $parent = $parent
        ->getParent();
    }
    return $parent;
  }

  /**
   * Gets the current RouteCollection as an Iterator that includes all routes and child route collections.
   *
   * @return \ArrayIterator An \ArrayIterator interface
   */
  public function getIterator() {
    return new \ArrayIterator($this->routes);
  }

  /**
   * Gets the number of Routes in this collection.
   *
   * @return int The number of routes in this collection, including nested collections
   */
  public function count() {
    $count = 0;
    foreach ($this->routes as $route) {
      $count += $route instanceof RouteCollection ? count($route) : 1;
    }
    return $count;
  }

  /**
   * Adds a route.
   *
   * @param string $name  The route name
   * @param Route  $route A Route instance
   *
   * @throws \InvalidArgumentException When route name contains non valid characters
   *
   * @api
   */
  public function add($name, Route $route) {
    if (!preg_match('/^[a-z0-9A-Z_.]+$/', $name)) {
      throw new \InvalidArgumentException(sprintf('The provided route name "%s" contains non valid characters. A route name must only contain digits (0-9), letters (a-z and A-Z), underscores (_) and dots (.).', $name));
    }
    $this
      ->remove($name);
    $this->routes[$name] = $route;
  }

  /**
   * Returns all routes in this collection and its children.
   *
   * @return array An array of routes
   */
  public function all() {
    $routes = array();
    foreach ($this->routes as $name => $route) {
      if ($route instanceof RouteCollection) {
        $routes = array_merge($routes, $route
          ->all());
      }
      else {
        $routes[$name] = $route;
      }
    }
    return $routes;
  }

  /**
   * Gets a route by name defined in this collection or its children.
   *
   * @param string $name The route name
   *
   * @return Route|null A Route instance or null when not found
   */
  public function get($name) {
    if (isset($this->routes[$name])) {
      return $this->routes[$name] instanceof RouteCollection ? null : $this->routes[$name];
    }
    foreach ($this->routes as $routes) {
      if ($routes instanceof RouteCollection && null !== ($route = $routes
        ->get($name))) {
        return $route;
      }
    }
    return null;
  }

  /**
   * Removes a route or an array of routes by name from all connected
   * collections (this instance and all parents and children).
   *
   * @param string|array $name The route name or an array of route names
   */
  public function remove($name) {
    $root = $this
      ->getRoot();
    foreach ((array) $name as $n) {
      $root
        ->removeRecursively($n);
    }
  }

  /**
   * Adds a route collection to the current set of routes (at the end of the current set).
   *
   * @param RouteCollection $collection   A RouteCollection instance
   * @param string          $prefix       An optional prefix to add before each pattern of the route collection
   * @param array           $defaults     An array of default values
   * @param array           $requirements An array of requirements
   * @param array           $options      An array of options
   *
   * @throws \InvalidArgumentException When the RouteCollection already exists in the tree
   *
   * @api
   */
  public function addCollection(RouteCollection $collection, $prefix = '', $defaults = array(), $requirements = array(), $options = array()) {

    // prevent infinite loops by recursive referencing
    $root = $this
      ->getRoot();
    if ($root === $collection || $root
      ->hasCollection($collection)) {
      throw new \InvalidArgumentException('The RouteCollection already exists in the tree.');
    }

    // remove all routes with the same names in all existing collections
    $this
      ->remove(array_keys($collection
      ->all()));
    $collection
      ->setParent($this);

    // the sub-collection must have the prefix of the parent (current instance) prepended because it does not
    // necessarily already have it applied (depending on the order RouteCollections are added to each other)
    $collection
      ->addPrefix($this
      ->getPrefix() . $prefix, $defaults, $requirements, $options);
    $this->routes[] = $collection;
  }

  /**
   * Adds a prefix to all routes in the current set.
   *
   * @param string $prefix       An optional prefix to add before each pattern of the route collection
   * @param array  $defaults     An array of default values
   * @param array  $requirements An array of requirements
   * @param array  $options      An array of options
   *
   * @api
   */
  public function addPrefix($prefix, $defaults = array(), $requirements = array(), $options = array()) {

    // a prefix must not end with a slash
    $prefix = rtrim($prefix, '/');
    if ('' === $prefix && empty($defaults) && empty($requirements) && empty($options)) {
      return;
    }

    // a prefix must start with a slash
    if ('' !== $prefix && '/' !== $prefix[0]) {
      $prefix = '/' . $prefix;
    }
    $this->prefix = $prefix . $this->prefix;
    foreach ($this->routes as $route) {
      if ($route instanceof RouteCollection) {
        $route
          ->addPrefix($prefix, $defaults, $requirements, $options);
      }
      else {
        $route
          ->setPattern($prefix . $route
          ->getPattern());
        $route
          ->addDefaults($defaults);
        $route
          ->addRequirements($requirements);
        $route
          ->addOptions($options);
      }
    }
  }

  /**
   * Returns the prefix that may contain placeholders.
   *
   * @return string The prefix
   */
  public function getPrefix() {
    return $this->prefix;
  }

  /**
   * Returns an array of resources loaded to build this collection.
   *
   * @return ResourceInterface[] An array of resources
   */
  public function getResources() {
    $resources = $this->resources;
    foreach ($this as $routes) {
      if ($routes instanceof RouteCollection) {
        $resources = array_merge($resources, $routes
          ->getResources());
      }
    }
    return array_unique($resources);
  }

  /**
   * Adds a resource for this collection.
   *
   * @param ResourceInterface $resource A resource instance
   */
  public function addResource(ResourceInterface $resource) {
    $this->resources[] = $resource;
  }

  /**
   * Sets the parent RouteCollection. It's only used internally from one RouteCollection
   * to another. It makes no sense to be available as part of the public API.
   *
   * @param RouteCollection $parent The parent RouteCollection
   */
  private function setParent(RouteCollection $parent) {
    $this->parent = $parent;
  }

  /**
   * Removes a route by name from this collection and its children recursively.
   *
   * @param string $name The route name
   *
   * @return Boolean true when found
   */
  private function removeRecursively($name) {

    // It is ensured by the adders (->add and ->addCollection) that there can
    // only be one route per name in all connected collections. So we can stop
    // iterating recursively on the first hit.
    if (isset($this->routes[$name])) {
      unset($this->routes[$name]);
      return true;
    }
    foreach ($this->routes as $routes) {
      if ($routes instanceof RouteCollection && $routes
        ->removeRecursively($name)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Checks whether the given RouteCollection is already set in any child of the current instance.
   *
   * @param RouteCollection $collection A RouteCollection instance
   *
   * @return Boolean
   */
  private function hasCollection(RouteCollection $collection) {
    foreach ($this->routes as $routes) {
      if ($routes === $collection || $routes instanceof RouteCollection && $routes
        ->hasCollection($collection)) {
        return true;
      }
    }
    return false;
  }

}

Classes

Namesort descending Description
RouteCollection A RouteCollection represents a set of Route instances as a tree structure.