360 lines
10 KiB
PHP
360 lines
10 KiB
PHP
<?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
|
|
{
|
|
/**
|
|
* @var (RouteCollection|Route)[]
|
|
*/
|
|
private $routes = array();
|
|
|
|
/**
|
|
* @var array
|
|
*/
|
|
private $resources = array();
|
|
|
|
/**
|
|
* @var string
|
|
*/
|
|
private $prefix = '';
|
|
|
|
/**
|
|
* @var RouteCollection|null
|
|
*/
|
|
private $parent;
|
|
|
|
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
|
|
*
|
|
* @api
|
|
*/
|
|
public function add($name, Route $route)
|
|
{
|
|
$this->remove($name);
|
|
|
|
$this->routes[$name] = $route;
|
|
}
|
|
|
|
/**
|
|
* Returns all routes in this collection and its children.
|
|
*
|
|
* @return Route[] 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
|
|
* @param string $hostnamePattern Hostname pattern
|
|
*
|
|
* @throws \InvalidArgumentException When the RouteCollection already exists in the tree
|
|
*
|
|
* @api
|
|
*/
|
|
public function addCollection(RouteCollection $collection, $prefix = '', $defaults = array(), $requirements = array(), $options = array(), $hostnamePattern = '')
|
|
{
|
|
// 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);
|
|
|
|
if ('' !== $hostnamePattern) {
|
|
$collection->setHostnamePattern($hostnamePattern);
|
|
}
|
|
|
|
$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())
|
|
{
|
|
$prefix = trim(trim($prefix), '/');
|
|
|
|
if ('' === $prefix && empty($defaults) && empty($requirements) && empty($options)) {
|
|
return;
|
|
}
|
|
|
|
// a prefix must start with a single slash and must not end with a slash
|
|
if ('' !== $prefix) {
|
|
$this->prefix = '/' . $prefix . $this->prefix;
|
|
}
|
|
|
|
foreach ($this->routes as $route) {
|
|
if ($route instanceof RouteCollection) {
|
|
// we add the slashes so the prefix is not lost by trimming in the sub-collection
|
|
$route->addPrefix('/' . $prefix . '/', $defaults, $requirements, $options);
|
|
} else {
|
|
if ('' !== $prefix) {
|
|
$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;
|
|
}
|
|
|
|
/**
|
|
* Sets the hostname pattern on all child routes.
|
|
*
|
|
* @param string $pattern The pattern
|
|
*/
|
|
public function setHostnamePattern($pattern)
|
|
{
|
|
foreach ($this->routes as $route) {
|
|
$route->setHostnamePattern($pattern);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns an array of resources loaded to build this collection.
|
|
*
|
|
* @return ResourceInterface[] An array of resources
|
|
*/
|
|
public function getResources()
|
|
{
|
|
$resources = $this->resources;
|
|
foreach ($this->routes 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;
|
|
}
|
|
}
|