4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\Routing;
14 use Symfony\Component\Config\Exception\FileLoaderLoadException;
15 use Symfony\Component\Config\Loader\LoaderInterface;
16 use Symfony\Component\Config\Resource\ResourceInterface;
19 * Helps add and import routes into a RouteCollection.
21 * @author Ryan Weaver <ryan@knpuniversity.com>
23 class RouteCollectionBuilder
26 * @var Route[]|RouteCollectionBuilder[]
28 private $routes = array();
31 private $defaults = array();
35 private $requirements = array();
36 private $options = array();
39 private $resources = array();
41 public function __construct(LoaderInterface $loader = null)
43 $this->loader = $loader;
47 * Import an external routing resource and returns the RouteCollectionBuilder.
49 * $routes->import('blog.yml', '/blog');
51 * @param mixed $resource
52 * @param string|null $prefix
57 * @throws FileLoaderLoadException
59 public function import($resource, $prefix = '/', $type = null)
61 /** @var RouteCollection[] $collection */
62 $collections = $this->load($resource, $type);
64 // create a builder from the RouteCollection
65 $builder = $this->createBuilder();
67 foreach ($collections as $collection) {
68 if (null === $collection) {
72 foreach ($collection->all() as $name => $route) {
73 $builder->addRoute($route, $name);
76 foreach ($collection->getResources() as $resource) {
77 $builder->addResource($resource);
81 // mount into this builder
82 $this->mount($prefix, $builder);
88 * Adds a route and returns it for future modification.
90 * @param string $path The route path
91 * @param string $controller The route's controller
92 * @param string|null $name The name to give this route
96 public function add($path, $controller, $name = null)
98 $route = new Route($path);
99 $route->setDefault('_controller', $controller);
100 $this->addRoute($route, $name);
106 * Returns a RouteCollectionBuilder that can be configured and then added with mount().
110 public function createBuilder()
112 return new self($this->loader);
116 * Add a RouteCollectionBuilder.
118 * @param string $prefix
119 * @param RouteCollectionBuilder $builder
121 public function mount($prefix, self $builder)
123 $builder->prefix = trim(trim($prefix), '/');
124 $this->routes[] = $builder;
128 * Adds a Route object to the builder.
130 * @param Route $route
131 * @param string|null $name
135 public function addRoute(Route $route, $name = null)
137 if (null === $name) {
138 // used as a flag to know which routes will need a name later
139 $name = '_unnamed_route_'.spl_object_hash($route);
142 $this->routes[$name] = $route;
148 * Sets the host on all embedded routes (unless already set).
150 * @param string $pattern
154 public function setHost($pattern)
156 $this->host = $pattern;
162 * Sets a condition on all embedded routes (unless already set).
164 * @param string $condition
168 public function setCondition($condition)
170 $this->condition = $condition;
176 * Sets a default value that will be added to all embedded routes (unless that
177 * default value is already set).
180 * @param mixed $value
184 public function setDefault($key, $value)
186 $this->defaults[$key] = $value;
192 * Sets a requirement that will be added to all embedded routes (unless that
193 * requirement is already set).
196 * @param mixed $regex
200 public function setRequirement($key, $regex)
202 $this->requirements[$key] = $regex;
208 * Sets an option that will be added to all embedded routes (unless that
209 * option is already set).
212 * @param mixed $value
216 public function setOption($key, $value)
218 $this->options[$key] = $value;
224 * Sets the schemes on all embedded routes (unless already set).
226 * @param array|string $schemes
230 public function setSchemes($schemes)
232 $this->schemes = $schemes;
238 * Sets the methods on all embedded routes (unless already set).
240 * @param array|string $methods
244 public function setMethods($methods)
246 $this->methods = $methods;
252 * Adds a resource for this collection.
256 private function addResource(ResourceInterface $resource)
258 $this->resources[] = $resource;
264 * Creates the final RouteCollection and returns it.
266 * @return RouteCollection
268 public function build()
270 $routeCollection = new RouteCollection();
272 foreach ($this->routes as $name => $route) {
273 if ($route instanceof Route) {
274 $route->setDefaults(array_merge($this->defaults, $route->getDefaults()));
275 $route->setOptions(array_merge($this->options, $route->getOptions()));
277 foreach ($this->requirements as $key => $val) {
278 if (!$route->hasRequirement($key)) {
279 $route->setRequirement($key, $val);
283 if (null !== $this->prefix) {
284 $route->setPath('/'.$this->prefix.$route->getPath());
287 if (!$route->getHost()) {
288 $route->setHost($this->host);
291 if (!$route->getCondition()) {
292 $route->setCondition($this->condition);
295 if (!$route->getSchemes()) {
296 $route->setSchemes($this->schemes);
299 if (!$route->getMethods()) {
300 $route->setMethods($this->methods);
303 // auto-generate the route name if it's been marked
304 if ('_unnamed_route_' === substr($name, 0, 15)) {
305 $name = $this->generateRouteName($route);
308 $routeCollection->add($name, $route);
310 /* @var self $route */
311 $subCollection = $route->build();
312 $subCollection->addPrefix($this->prefix);
314 $routeCollection->addCollection($subCollection);
318 foreach ($this->resources as $resource) {
319 $routeCollection->addResource($resource);
322 return $routeCollection;
326 * Generates a route name based on details of this route.
330 private function generateRouteName(Route $route)
332 $methods = implode('_', $route->getMethods()).'_';
334 $routeName = $methods.$route->getPath();
335 $routeName = str_replace(array('/', ':', '|', '-'), '_', $routeName);
336 $routeName = preg_replace('/[^a-z0-9A-Z_.]+/', '', $routeName);
338 // Collapse consecutive underscores down into a single underscore.
339 $routeName = preg_replace('/_+/', '_', $routeName);
345 * Finds a loader able to load an imported resource and loads it.
347 * @param mixed $resource A resource
348 * @param string|null $type The resource type or null if unknown
350 * @return RouteCollection[]
352 * @throws FileLoaderLoadException If no loader is found
354 private function load($resource, $type = null)
356 if (null === $this->loader) {
357 throw new \BadMethodCallException('Cannot import other routing resources: you must pass a LoaderInterface when constructing RouteCollectionBuilder.');
360 if ($this->loader->supports($resource, $type)) {
361 $collections = $this->loader->load($resource, $type);
363 return \is_array($collections) ? $collections : array($collections);
366 if (null === $resolver = $this->loader->getResolver()) {
367 throw new FileLoaderLoadException($resource, null, null, null, $type);
370 if (false === $loader = $resolver->resolve($resource, $type)) {
371 throw new FileLoaderLoadException($resource, null, null, null, $type);
374 $collections = $loader->load($resource, $type);
376 return \is_array($collections) ? $collections : array($collections);