Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 15ba2e8

Browse filesBrowse files
weaverryanfabpot
authored andcommitted
Fluid interface for building routes in PHP
1 parent 00dffe7 commit 15ba2e8
Copy full SHA for 15ba2e8

File tree

Expand file treeCollapse file tree

2 files changed

+665
-0
lines changed
Filter options
Expand file treeCollapse file tree

2 files changed

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

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.