You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

306 lines
8.4 KiB

3 years ago
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Routing;
  11. use Symfony\Component\Config\Resource\ResourceInterface;
  12. /**
  13. * A RouteCollection represents a set of Route instances.
  14. *
  15. * When adding a route at the end of the collection, an existing route
  16. * with the same name is removed first. So there can only be one route
  17. * with a given name.
  18. *
  19. * @author Fabien Potencier <fabien@symfony.com>
  20. * @author Tobias Schultze <http://tobion.de>
  21. */
  22. class RouteCollection implements \IteratorAggregate, \Countable
  23. {
  24. /**
  25. * @var Route[]
  26. */
  27. private $routes = [];
  28. /**
  29. * @var array
  30. */
  31. private $resources = [];
  32. /**
  33. * @var int[]
  34. */
  35. private $priorities = [];
  36. public function __clone()
  37. {
  38. foreach ($this->routes as $name => $route) {
  39. $this->routes[$name] = clone $route;
  40. }
  41. }
  42. /**
  43. * Gets the current RouteCollection as an Iterator that includes all routes.
  44. *
  45. * It implements \IteratorAggregate.
  46. *
  47. * @see all()
  48. *
  49. * @return \ArrayIterator|Route[] An \ArrayIterator object for iterating over routes
  50. */
  51. public function getIterator()
  52. {
  53. return new \ArrayIterator($this->all());
  54. }
  55. /**
  56. * Gets the number of Routes in this collection.
  57. *
  58. * @return int The number of routes
  59. */
  60. public function count()
  61. {
  62. return \count($this->routes);
  63. }
  64. /**
  65. * @param int $priority
  66. */
  67. public function add(string $name, Route $route/*, int $priority = 0*/)
  68. {
  69. if (\func_num_args() < 3 && __CLASS__ !== static::class && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName() && !$this instanceof \PHPUnit\Framework\MockObject\MockObject && !$this instanceof \Prophecy\Prophecy\ProphecySubjectInterface && !$this instanceof \Mockery\MockInterface) {
  70. trigger_deprecation('symfony/routing', '5.1', 'The "%s()" method will have a new "int $priority = 0" argument in version 6.0, not defining it is deprecated.', __METHOD__);
  71. }
  72. unset($this->routes[$name], $this->priorities[$name]);
  73. $this->routes[$name] = $route;
  74. if ($priority = 3 <= \func_num_args() ? func_get_arg(2) : 0) {
  75. $this->priorities[$name] = $priority;
  76. }
  77. }
  78. /**
  79. * Returns all routes in this collection.
  80. *
  81. * @return Route[] An array of routes
  82. */
  83. public function all()
  84. {
  85. if ($this->priorities) {
  86. $priorities = $this->priorities;
  87. $keysOrder = array_flip(array_keys($this->routes));
  88. uksort($this->routes, static function ($n1, $n2) use ($priorities, $keysOrder) {
  89. return (($priorities[$n2] ?? 0) <=> ($priorities[$n1] ?? 0)) ?: ($keysOrder[$n1] <=> $keysOrder[$n2]);
  90. });
  91. }
  92. return $this->routes;
  93. }
  94. /**
  95. * Gets a route by name.
  96. *
  97. * @return Route|null A Route instance or null when not found
  98. */
  99. public function get(string $name)
  100. {
  101. return $this->routes[$name] ?? null;
  102. }
  103. /**
  104. * Removes a route or an array of routes by name from the collection.
  105. *
  106. * @param string|string[] $name The route name or an array of route names
  107. */
  108. public function remove($name)
  109. {
  110. foreach ((array) $name as $n) {
  111. unset($this->routes[$n], $this->priorities[$n]);
  112. }
  113. }
  114. /**
  115. * Adds a route collection at the end of the current set by appending all
  116. * routes of the added collection.
  117. */
  118. public function addCollection(self $collection)
  119. {
  120. // we need to remove all routes with the same names first because just replacing them
  121. // would not place the new route at the end of the merged array
  122. foreach ($collection->all() as $name => $route) {
  123. unset($this->routes[$name], $this->priorities[$name]);
  124. $this->routes[$name] = $route;
  125. if (isset($collection->priorities[$name])) {
  126. $this->priorities[$name] = $collection->priorities[$name];
  127. }
  128. }
  129. foreach ($collection->getResources() as $resource) {
  130. $this->addResource($resource);
  131. }
  132. }
  133. /**
  134. * Adds a prefix to the path of all child routes.
  135. */
  136. public function addPrefix(string $prefix, array $defaults = [], array $requirements = [])
  137. {
  138. $prefix = trim(trim($prefix), '/');
  139. if ('' === $prefix) {
  140. return;
  141. }
  142. foreach ($this->routes as $route) {
  143. $route->setPath('/'.$prefix.$route->getPath());
  144. $route->addDefaults($defaults);
  145. $route->addRequirements($requirements);
  146. }
  147. }
  148. /**
  149. * Adds a prefix to the name of all the routes within in the collection.
  150. */
  151. public function addNamePrefix(string $prefix)
  152. {
  153. $prefixedRoutes = [];
  154. $prefixedPriorities = [];
  155. foreach ($this->routes as $name => $route) {
  156. $prefixedRoutes[$prefix.$name] = $route;
  157. if (null !== $canonicalName = $route->getDefault('_canonical_route')) {
  158. $route->setDefault('_canonical_route', $prefix.$canonicalName);
  159. }
  160. if (isset($this->priorities[$name])) {
  161. $prefixedPriorities[$prefix.$name] = $this->priorities[$name];
  162. }
  163. }
  164. $this->routes = $prefixedRoutes;
  165. $this->priorities = $prefixedPriorities;
  166. }
  167. /**
  168. * Sets the host pattern on all routes.
  169. */
  170. public function setHost(?string $pattern, array $defaults = [], array $requirements = [])
  171. {
  172. foreach ($this->routes as $route) {
  173. $route->setHost($pattern);
  174. $route->addDefaults($defaults);
  175. $route->addRequirements($requirements);
  176. }
  177. }
  178. /**
  179. * Sets a condition on all routes.
  180. *
  181. * Existing conditions will be overridden.
  182. */
  183. public function setCondition(?string $condition)
  184. {
  185. foreach ($this->routes as $route) {
  186. $route->setCondition($condition);
  187. }
  188. }
  189. /**
  190. * Adds defaults to all routes.
  191. *
  192. * An existing default value under the same name in a route will be overridden.
  193. */
  194. public function addDefaults(array $defaults)
  195. {
  196. if ($defaults) {
  197. foreach ($this->routes as $route) {
  198. $route->addDefaults($defaults);
  199. }
  200. }
  201. }
  202. /**
  203. * Adds requirements to all routes.
  204. *
  205. * An existing requirement under the same name in a route will be overridden.
  206. */
  207. public function addRequirements(array $requirements)
  208. {
  209. if ($requirements) {
  210. foreach ($this->routes as $route) {
  211. $route->addRequirements($requirements);
  212. }
  213. }
  214. }
  215. /**
  216. * Adds options to all routes.
  217. *
  218. * An existing option value under the same name in a route will be overridden.
  219. */
  220. public function addOptions(array $options)
  221. {
  222. if ($options) {
  223. foreach ($this->routes as $route) {
  224. $route->addOptions($options);
  225. }
  226. }
  227. }
  228. /**
  229. * Sets the schemes (e.g. 'https') all child routes are restricted to.
  230. *
  231. * @param string|string[] $schemes The scheme or an array of schemes
  232. */
  233. public function setSchemes($schemes)
  234. {
  235. foreach ($this->routes as $route) {
  236. $route->setSchemes($schemes);
  237. }
  238. }
  239. /**
  240. * Sets the HTTP methods (e.g. 'POST') all child routes are restricted to.
  241. *
  242. * @param string|string[] $methods The method or an array of methods
  243. */
  244. public function setMethods($methods)
  245. {
  246. foreach ($this->routes as $route) {
  247. $route->setMethods($methods);
  248. }
  249. }
  250. /**
  251. * Returns an array of resources loaded to build this collection.
  252. *
  253. * @return ResourceInterface[] An array of resources
  254. */
  255. public function getResources()
  256. {
  257. return array_values($this->resources);
  258. }
  259. /**
  260. * Adds a resource for this collection. If the resource already exists
  261. * it is not added.
  262. */
  263. public function addResource(ResourceInterface $resource)
  264. {
  265. $key = (string) $resource;
  266. if (!isset($this->resources[$key])) {
  267. $this->resources[$key] = $resource;
  268. }
  269. }
  270. }