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