Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 /*
|
Chris@0
|
4 * This file is part of the Symfony package.
|
Chris@0
|
5 *
|
Chris@0
|
6 * (c) Fabien Potencier <fabien@symfony.com>
|
Chris@0
|
7 *
|
Chris@0
|
8 * For the full copyright and license information, please view the LICENSE
|
Chris@0
|
9 * file that was distributed with this source code.
|
Chris@0
|
10 */
|
Chris@0
|
11
|
Chris@0
|
12 namespace Symfony\Component\Routing;
|
Chris@0
|
13
|
Chris@0
|
14 use Symfony\Component\Config\Exception\FileLoaderLoadException;
|
Chris@0
|
15 use Symfony\Component\Config\Loader\LoaderInterface;
|
Chris@0
|
16 use Symfony\Component\Config\Resource\ResourceInterface;
|
Chris@0
|
17
|
Chris@0
|
18 /**
|
Chris@0
|
19 * Helps add and import routes into a RouteCollection.
|
Chris@0
|
20 *
|
Chris@0
|
21 * @author Ryan Weaver <ryan@knpuniversity.com>
|
Chris@0
|
22 */
|
Chris@0
|
23 class RouteCollectionBuilder
|
Chris@0
|
24 {
|
Chris@0
|
25 /**
|
Chris@0
|
26 * @var Route[]|RouteCollectionBuilder[]
|
Chris@0
|
27 */
|
Chris@17
|
28 private $routes = [];
|
Chris@0
|
29
|
Chris@0
|
30 private $loader;
|
Chris@17
|
31 private $defaults = [];
|
Chris@0
|
32 private $prefix;
|
Chris@0
|
33 private $host;
|
Chris@0
|
34 private $condition;
|
Chris@17
|
35 private $requirements = [];
|
Chris@17
|
36 private $options = [];
|
Chris@0
|
37 private $schemes;
|
Chris@0
|
38 private $methods;
|
Chris@17
|
39 private $resources = [];
|
Chris@0
|
40
|
Chris@0
|
41 public function __construct(LoaderInterface $loader = null)
|
Chris@0
|
42 {
|
Chris@0
|
43 $this->loader = $loader;
|
Chris@0
|
44 }
|
Chris@0
|
45
|
Chris@0
|
46 /**
|
Chris@0
|
47 * Import an external routing resource and returns the RouteCollectionBuilder.
|
Chris@0
|
48 *
|
Chris@17
|
49 * $routes->import('blog.yml', '/blog');
|
Chris@0
|
50 *
|
Chris@0
|
51 * @param mixed $resource
|
Chris@0
|
52 * @param string|null $prefix
|
Chris@0
|
53 * @param string $type
|
Chris@0
|
54 *
|
Chris@0
|
55 * @return self
|
Chris@0
|
56 *
|
Chris@0
|
57 * @throws FileLoaderLoadException
|
Chris@0
|
58 */
|
Chris@0
|
59 public function import($resource, $prefix = '/', $type = null)
|
Chris@0
|
60 {
|
Chris@14
|
61 /** @var RouteCollection[] $collection */
|
Chris@14
|
62 $collections = $this->load($resource, $type);
|
Chris@0
|
63
|
Chris@0
|
64 // create a builder from the RouteCollection
|
Chris@0
|
65 $builder = $this->createBuilder();
|
Chris@0
|
66
|
Chris@14
|
67 foreach ($collections as $collection) {
|
Chris@14
|
68 if (null === $collection) {
|
Chris@14
|
69 continue;
|
Chris@14
|
70 }
|
Chris@14
|
71
|
Chris@14
|
72 foreach ($collection->all() as $name => $route) {
|
Chris@14
|
73 $builder->addRoute($route, $name);
|
Chris@14
|
74 }
|
Chris@14
|
75
|
Chris@14
|
76 foreach ($collection->getResources() as $resource) {
|
Chris@14
|
77 $builder->addResource($resource);
|
Chris@14
|
78 }
|
Chris@0
|
79 }
|
Chris@0
|
80
|
Chris@0
|
81 // mount into this builder
|
Chris@0
|
82 $this->mount($prefix, $builder);
|
Chris@0
|
83
|
Chris@0
|
84 return $builder;
|
Chris@0
|
85 }
|
Chris@0
|
86
|
Chris@0
|
87 /**
|
Chris@0
|
88 * Adds a route and returns it for future modification.
|
Chris@0
|
89 *
|
Chris@0
|
90 * @param string $path The route path
|
Chris@0
|
91 * @param string $controller The route's controller
|
Chris@0
|
92 * @param string|null $name The name to give this route
|
Chris@0
|
93 *
|
Chris@0
|
94 * @return Route
|
Chris@0
|
95 */
|
Chris@0
|
96 public function add($path, $controller, $name = null)
|
Chris@0
|
97 {
|
Chris@0
|
98 $route = new Route($path);
|
Chris@0
|
99 $route->setDefault('_controller', $controller);
|
Chris@0
|
100 $this->addRoute($route, $name);
|
Chris@0
|
101
|
Chris@0
|
102 return $route;
|
Chris@0
|
103 }
|
Chris@0
|
104
|
Chris@0
|
105 /**
|
Chris@0
|
106 * Returns a RouteCollectionBuilder that can be configured and then added with mount().
|
Chris@0
|
107 *
|
Chris@0
|
108 * @return self
|
Chris@0
|
109 */
|
Chris@0
|
110 public function createBuilder()
|
Chris@0
|
111 {
|
Chris@0
|
112 return new self($this->loader);
|
Chris@0
|
113 }
|
Chris@0
|
114
|
Chris@0
|
115 /**
|
Chris@0
|
116 * Add a RouteCollectionBuilder.
|
Chris@0
|
117 *
|
Chris@0
|
118 * @param string $prefix
|
Chris@0
|
119 * @param RouteCollectionBuilder $builder
|
Chris@0
|
120 */
|
Chris@16
|
121 public function mount($prefix, self $builder)
|
Chris@0
|
122 {
|
Chris@0
|
123 $builder->prefix = trim(trim($prefix), '/');
|
Chris@0
|
124 $this->routes[] = $builder;
|
Chris@0
|
125 }
|
Chris@0
|
126
|
Chris@0
|
127 /**
|
Chris@0
|
128 * Adds a Route object to the builder.
|
Chris@0
|
129 *
|
Chris@0
|
130 * @param Route $route
|
Chris@0
|
131 * @param string|null $name
|
Chris@0
|
132 *
|
Chris@0
|
133 * @return $this
|
Chris@0
|
134 */
|
Chris@0
|
135 public function addRoute(Route $route, $name = null)
|
Chris@0
|
136 {
|
Chris@0
|
137 if (null === $name) {
|
Chris@0
|
138 // used as a flag to know which routes will need a name later
|
Chris@0
|
139 $name = '_unnamed_route_'.spl_object_hash($route);
|
Chris@0
|
140 }
|
Chris@0
|
141
|
Chris@0
|
142 $this->routes[$name] = $route;
|
Chris@0
|
143
|
Chris@0
|
144 return $this;
|
Chris@0
|
145 }
|
Chris@0
|
146
|
Chris@0
|
147 /**
|
Chris@0
|
148 * Sets the host on all embedded routes (unless already set).
|
Chris@0
|
149 *
|
Chris@0
|
150 * @param string $pattern
|
Chris@0
|
151 *
|
Chris@0
|
152 * @return $this
|
Chris@0
|
153 */
|
Chris@0
|
154 public function setHost($pattern)
|
Chris@0
|
155 {
|
Chris@0
|
156 $this->host = $pattern;
|
Chris@0
|
157
|
Chris@0
|
158 return $this;
|
Chris@0
|
159 }
|
Chris@0
|
160
|
Chris@0
|
161 /**
|
Chris@0
|
162 * Sets a condition on all embedded routes (unless already set).
|
Chris@0
|
163 *
|
Chris@0
|
164 * @param string $condition
|
Chris@0
|
165 *
|
Chris@0
|
166 * @return $this
|
Chris@0
|
167 */
|
Chris@0
|
168 public function setCondition($condition)
|
Chris@0
|
169 {
|
Chris@0
|
170 $this->condition = $condition;
|
Chris@0
|
171
|
Chris@0
|
172 return $this;
|
Chris@0
|
173 }
|
Chris@0
|
174
|
Chris@0
|
175 /**
|
Chris@0
|
176 * Sets a default value that will be added to all embedded routes (unless that
|
Chris@0
|
177 * default value is already set).
|
Chris@0
|
178 *
|
Chris@0
|
179 * @param string $key
|
Chris@0
|
180 * @param mixed $value
|
Chris@0
|
181 *
|
Chris@0
|
182 * @return $this
|
Chris@0
|
183 */
|
Chris@0
|
184 public function setDefault($key, $value)
|
Chris@0
|
185 {
|
Chris@0
|
186 $this->defaults[$key] = $value;
|
Chris@0
|
187
|
Chris@0
|
188 return $this;
|
Chris@0
|
189 }
|
Chris@0
|
190
|
Chris@0
|
191 /**
|
Chris@0
|
192 * Sets a requirement that will be added to all embedded routes (unless that
|
Chris@0
|
193 * requirement is already set).
|
Chris@0
|
194 *
|
Chris@0
|
195 * @param string $key
|
Chris@0
|
196 * @param mixed $regex
|
Chris@0
|
197 *
|
Chris@0
|
198 * @return $this
|
Chris@0
|
199 */
|
Chris@0
|
200 public function setRequirement($key, $regex)
|
Chris@0
|
201 {
|
Chris@0
|
202 $this->requirements[$key] = $regex;
|
Chris@0
|
203
|
Chris@0
|
204 return $this;
|
Chris@0
|
205 }
|
Chris@0
|
206
|
Chris@0
|
207 /**
|
Chris@14
|
208 * Sets an option that will be added to all embedded routes (unless that
|
Chris@0
|
209 * option is already set).
|
Chris@0
|
210 *
|
Chris@0
|
211 * @param string $key
|
Chris@0
|
212 * @param mixed $value
|
Chris@0
|
213 *
|
Chris@0
|
214 * @return $this
|
Chris@0
|
215 */
|
Chris@0
|
216 public function setOption($key, $value)
|
Chris@0
|
217 {
|
Chris@0
|
218 $this->options[$key] = $value;
|
Chris@0
|
219
|
Chris@0
|
220 return $this;
|
Chris@0
|
221 }
|
Chris@0
|
222
|
Chris@0
|
223 /**
|
Chris@0
|
224 * Sets the schemes on all embedded routes (unless already set).
|
Chris@0
|
225 *
|
Chris@0
|
226 * @param array|string $schemes
|
Chris@0
|
227 *
|
Chris@0
|
228 * @return $this
|
Chris@0
|
229 */
|
Chris@0
|
230 public function setSchemes($schemes)
|
Chris@0
|
231 {
|
Chris@0
|
232 $this->schemes = $schemes;
|
Chris@0
|
233
|
Chris@0
|
234 return $this;
|
Chris@0
|
235 }
|
Chris@0
|
236
|
Chris@0
|
237 /**
|
Chris@0
|
238 * Sets the methods on all embedded routes (unless already set).
|
Chris@0
|
239 *
|
Chris@0
|
240 * @param array|string $methods
|
Chris@0
|
241 *
|
Chris@0
|
242 * @return $this
|
Chris@0
|
243 */
|
Chris@0
|
244 public function setMethods($methods)
|
Chris@0
|
245 {
|
Chris@0
|
246 $this->methods = $methods;
|
Chris@0
|
247
|
Chris@0
|
248 return $this;
|
Chris@0
|
249 }
|
Chris@0
|
250
|
Chris@0
|
251 /**
|
Chris@0
|
252 * Adds a resource for this collection.
|
Chris@0
|
253 *
|
Chris@0
|
254 * @return $this
|
Chris@0
|
255 */
|
Chris@0
|
256 private function addResource(ResourceInterface $resource)
|
Chris@0
|
257 {
|
Chris@0
|
258 $this->resources[] = $resource;
|
Chris@0
|
259
|
Chris@0
|
260 return $this;
|
Chris@0
|
261 }
|
Chris@0
|
262
|
Chris@0
|
263 /**
|
Chris@0
|
264 * Creates the final RouteCollection and returns it.
|
Chris@0
|
265 *
|
Chris@0
|
266 * @return RouteCollection
|
Chris@0
|
267 */
|
Chris@0
|
268 public function build()
|
Chris@0
|
269 {
|
Chris@0
|
270 $routeCollection = new RouteCollection();
|
Chris@0
|
271
|
Chris@0
|
272 foreach ($this->routes as $name => $route) {
|
Chris@0
|
273 if ($route instanceof Route) {
|
Chris@0
|
274 $route->setDefaults(array_merge($this->defaults, $route->getDefaults()));
|
Chris@0
|
275 $route->setOptions(array_merge($this->options, $route->getOptions()));
|
Chris@0
|
276
|
Chris@0
|
277 foreach ($this->requirements as $key => $val) {
|
Chris@0
|
278 if (!$route->hasRequirement($key)) {
|
Chris@0
|
279 $route->setRequirement($key, $val);
|
Chris@0
|
280 }
|
Chris@0
|
281 }
|
Chris@0
|
282
|
Chris@0
|
283 if (null !== $this->prefix) {
|
Chris@0
|
284 $route->setPath('/'.$this->prefix.$route->getPath());
|
Chris@0
|
285 }
|
Chris@0
|
286
|
Chris@0
|
287 if (!$route->getHost()) {
|
Chris@0
|
288 $route->setHost($this->host);
|
Chris@0
|
289 }
|
Chris@0
|
290
|
Chris@0
|
291 if (!$route->getCondition()) {
|
Chris@0
|
292 $route->setCondition($this->condition);
|
Chris@0
|
293 }
|
Chris@0
|
294
|
Chris@0
|
295 if (!$route->getSchemes()) {
|
Chris@0
|
296 $route->setSchemes($this->schemes);
|
Chris@0
|
297 }
|
Chris@0
|
298
|
Chris@0
|
299 if (!$route->getMethods()) {
|
Chris@0
|
300 $route->setMethods($this->methods);
|
Chris@0
|
301 }
|
Chris@0
|
302
|
Chris@0
|
303 // auto-generate the route name if it's been marked
|
Chris@0
|
304 if ('_unnamed_route_' === substr($name, 0, 15)) {
|
Chris@0
|
305 $name = $this->generateRouteName($route);
|
Chris@0
|
306 }
|
Chris@0
|
307
|
Chris@0
|
308 $routeCollection->add($name, $route);
|
Chris@0
|
309 } else {
|
Chris@0
|
310 /* @var self $route */
|
Chris@0
|
311 $subCollection = $route->build();
|
Chris@0
|
312 $subCollection->addPrefix($this->prefix);
|
Chris@0
|
313
|
Chris@0
|
314 $routeCollection->addCollection($subCollection);
|
Chris@0
|
315 }
|
Chris@14
|
316 }
|
Chris@0
|
317
|
Chris@14
|
318 foreach ($this->resources as $resource) {
|
Chris@14
|
319 $routeCollection->addResource($resource);
|
Chris@0
|
320 }
|
Chris@0
|
321
|
Chris@0
|
322 return $routeCollection;
|
Chris@0
|
323 }
|
Chris@0
|
324
|
Chris@0
|
325 /**
|
Chris@0
|
326 * Generates a route name based on details of this route.
|
Chris@0
|
327 *
|
Chris@0
|
328 * @return string
|
Chris@0
|
329 */
|
Chris@0
|
330 private function generateRouteName(Route $route)
|
Chris@0
|
331 {
|
Chris@0
|
332 $methods = implode('_', $route->getMethods()).'_';
|
Chris@0
|
333
|
Chris@0
|
334 $routeName = $methods.$route->getPath();
|
Chris@17
|
335 $routeName = str_replace(['/', ':', '|', '-'], '_', $routeName);
|
Chris@0
|
336 $routeName = preg_replace('/[^a-z0-9A-Z_.]+/', '', $routeName);
|
Chris@0
|
337
|
Chris@0
|
338 // Collapse consecutive underscores down into a single underscore.
|
Chris@0
|
339 $routeName = preg_replace('/_+/', '_', $routeName);
|
Chris@0
|
340
|
Chris@0
|
341 return $routeName;
|
Chris@0
|
342 }
|
Chris@0
|
343
|
Chris@0
|
344 /**
|
Chris@0
|
345 * Finds a loader able to load an imported resource and loads it.
|
Chris@0
|
346 *
|
Chris@0
|
347 * @param mixed $resource A resource
|
Chris@0
|
348 * @param string|null $type The resource type or null if unknown
|
Chris@0
|
349 *
|
Chris@14
|
350 * @return RouteCollection[]
|
Chris@0
|
351 *
|
Chris@0
|
352 * @throws FileLoaderLoadException If no loader is found
|
Chris@0
|
353 */
|
Chris@0
|
354 private function load($resource, $type = null)
|
Chris@0
|
355 {
|
Chris@0
|
356 if (null === $this->loader) {
|
Chris@0
|
357 throw new \BadMethodCallException('Cannot import other routing resources: you must pass a LoaderInterface when constructing RouteCollectionBuilder.');
|
Chris@0
|
358 }
|
Chris@0
|
359
|
Chris@0
|
360 if ($this->loader->supports($resource, $type)) {
|
Chris@14
|
361 $collections = $this->loader->load($resource, $type);
|
Chris@14
|
362
|
Chris@17
|
363 return \is_array($collections) ? $collections : [$collections];
|
Chris@0
|
364 }
|
Chris@0
|
365
|
Chris@0
|
366 if (null === $resolver = $this->loader->getResolver()) {
|
Chris@14
|
367 throw new FileLoaderLoadException($resource, null, null, null, $type);
|
Chris@0
|
368 }
|
Chris@0
|
369
|
Chris@0
|
370 if (false === $loader = $resolver->resolve($resource, $type)) {
|
Chris@14
|
371 throw new FileLoaderLoadException($resource, null, null, null, $type);
|
Chris@0
|
372 }
|
Chris@0
|
373
|
Chris@14
|
374 $collections = $loader->load($resource, $type);
|
Chris@14
|
375
|
Chris@17
|
376 return \is_array($collections) ? $collections : [$collections];
|
Chris@0
|
377 }
|
Chris@0
|
378 }
|