Skip to content

Commit e804e02

Browse files
committed
Merge pull request symfony#2339 from matthiasnoback/cookbook_custom_route_loader
[Cookbook] [Routing] Article about custom route loaders
2 parents 9ef8a4c + f23d136 commit e804e02

File tree

3 files changed

+257
-0
lines changed

3 files changed

+257
-0
lines changed

cookbook/map.rst.inc

+1
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
* :doc:`/cookbook/routing/slash_in_parameter`
105105
* :doc:`/cookbook/routing/redirect_in_config`
106106
* :doc:`/cookbook/routing/method_parameters`
107+
* :doc:`/cookbook/routing/custom_route_loader`
107108

108109
* :doc:`/cookbook/security/index`
109110

+255
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
.. index::
2+
single: Routing; Custom route loader
3+
4+
How to Create a Custom Route Loader
5+
===================================
6+
7+
A custom route loader allows you to add routes to an application, without
8+
writing them down in for instance a Yaml file. This comes in handy when
9+
you have made a bundle and don't want to manually add the routes for the
10+
bundle to ``app/config/routing.yml``. Especially when you want to make the
11+
bundle reusable, or when you have open-sourced it, this would slow down
12+
the installation process and make it error-prone.
13+
14+
But you can also use a custom route loader when your routes correspond
15+
to a certain pattern and you don't want to or can't write them all out by
16+
hand. For instance when you have CRUD controllers of which the routes all
17+
correspond to a pattern like ``*_new``, ``*_edit``, etc.
18+
19+
.. note::
20+
21+
There are many bundles out there that use their own route loaders to
22+
accomplish cases like those described above, for instance
23+
`FOSRestBundle`_, `KnpRadBundle`_ and `SonataAdminBundle`_.
24+
25+
Loading Routes
26+
--------------
27+
28+
The routes in a Symfony application are loaded by the
29+
:class:`Symfony\\Bundle\\FrameworkBundle\\Routing\\DelegatingLoader`.
30+
This loader uses several other loaders (delegates) to load resources of
31+
different types, for instance Yaml files or ``@Route`` and ``@Method`` annotations
32+
in controller files. The specialized loaders implement
33+
:class:`Symfony\\Component\\Config\\Loader\\LoaderInterface`
34+
and therefore have two important methods:
35+
:method:`Symfony\\Component\\Config\\Loader\\LoaderInterface::supports`
36+
and :method:`Symfony\\Component\\Config\\Loader\\LoaderInterface::load`.
37+
38+
Take these lines from ``routing.yml``:
39+
40+
.. code-block:: yaml
41+
42+
_demo:
43+
resource: "@AcmeDemoBundle/Controller/DemoController.php"
44+
type: annotation
45+
prefix: /demo
46+
47+
The main loader tries all the delegate loaders and calls their
48+
:method:`Symfony\\Component\\Config\\Loader\\LoaderInterface::supports`
49+
with the given resource (``@AcmeDemoBundle/Controller/DemoController.php``)
50+
and type ("annotation") as arguments. When one of the loader returns ``true``,
51+
its method :method:`Symfony\\Component\\Config\\Loader\\LoaderInterface::load`
52+
will be called, and the loader returns a :class:`Symfony\\Component\\Routing\\RouteCollection`
53+
containing :class:`Symfony\\Component\\Routing\\Route` objects.
54+
55+
Creating a Custom Loader
56+
------------------------
57+
58+
To load routes in another way than using annotations, Yaml or XML files,
59+
you need to create a custom route loader. This loader should implement
60+
:class:`Symfony\\Component\\Config\\Loader\\LoaderInterface`.
61+
62+
The sample loader below supports resources of type "extra". The resource
63+
name itself is not used in the example::
64+
65+
namespace Acme\DemoBundle\Routing;
66+
67+
use Symfony\Component\Config\Loader\LoaderInterface;
68+
use Symfony\Component\Config\Loader\LoaderResolver;
69+
use Symfony\Component\Routing\Route;
70+
use Symfony\Component\Routing\RouteCollection;
71+
72+
class ExtraLoader implements LoaderInterface
73+
{
74+
private $loaded = false;
75+
76+
public function load($resource, $type = null)
77+
{
78+
if (true === $this->loaded) {
79+
throw new \RuntimeException('Do not add the "extra" loader twice');
80+
}
81+
82+
$routes = new RouteCollection();
83+
84+
// prepare a new route
85+
$pattern = '/extra/{parameter}';
86+
$defaults = array(
87+
'_controller' => 'AcmeDemoBundle:Demo:extra',
88+
);
89+
$requirements = array(
90+
'parameter' => '\d+',
91+
);
92+
$route = new Route($pattern, $defaults, $requirements);
93+
94+
// add the new route to the route collection:
95+
$routeName = 'extraRoute';
96+
$routes->add($routeName, $route);
97+
98+
return $routes;
99+
}
100+
101+
public function supports($resource, $type = null)
102+
{
103+
return 'extra' === $type;
104+
}
105+
106+
public function getResolver()
107+
{
108+
// will be explained later
109+
}
110+
111+
public function setResolver(LoaderResolver $resolver)
112+
{
113+
// same here
114+
}
115+
}
116+
117+
.. note::
118+
119+
Make sure the controller you specify really exists.
120+
121+
Now define a service for the ``ExtraLoader``:
122+
123+
.. configuration-block::
124+
125+
.. code-block:: yaml
126+
127+
services:
128+
acme_demo.routing_loader:
129+
class: Acme\DemoBundle\Routing\ExtraLoader
130+
tags:
131+
- { name: routing.loader }
132+
133+
.. code-block:: xml
134+
135+
<?xml version="1.0" ?>
136+
<container xmlns="http://symfony.com/schema/dic/services"
137+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
138+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
139+
140+
<services>
141+
<service id="acme_demo.routing_loader" class="Acme\DemoBundle\Routing\ExtraLoader">
142+
<tag name="routing.loader" />
143+
</service>
144+
</services>
145+
</container>
146+
147+
.. code-block:: php
148+
149+
use Symfony\Component\DependencyInjection\Definition;
150+
151+
$container
152+
->setDefinition(
153+
'acme_demo.routing_loader',
154+
new Definition('Acme\DemoBundle\Routing\ExtraLoader')
155+
)
156+
->addTag('routing.loader')
157+
;
158+
159+
Notice the tag ``routing.loader``. All services with this tag will be marked
160+
as potential route loaders and added as specialized routers to the
161+
:class:`Symfony\\Bundle\\FrameworkBundle\\Routing\\DelegatingLoader`.
162+
163+
Finally, we only need to add a few extra lines to the routing configuration:
164+
165+
.. configuration-block::
166+
167+
.. code-block:: yaml
168+
169+
AcmeDemoBundle_Extra:
170+
resource: .
171+
type: extra
172+
173+
.. code-block:: xml
174+
175+
<?xml version="1.0" encoding="UTF-8" ?>
176+
<routes xmlns="http://symfony.com/schema/routing"
177+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
178+
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
179+
180+
<import resource="." type="extra" />
181+
</routes>
182+
183+
.. code-block:: php
184+
185+
use Symfony\Component\Routing\RouteCollection;
186+
187+
$collection = new RouteCollection();
188+
$collection->addCollection($loader->import('.', 'extra'));
189+
190+
return $collection;
191+
192+
The important part here is the ``type`` key. Its value should be "extra".
193+
This is the type which our ``ExtraLoader`` supports and this will make sure
194+
its ``load()`` method gets called. The ``resource`` key is insignificant
195+
for the ``ExtraLoader``, so we set it to ".".
196+
197+
.. note::
198+
199+
The routes defined using custom route loaders will be automatically
200+
cached by the framework. So whenever you change something to the behavior
201+
of the loader, don't forget to clear the cache.
202+
203+
204+
More Advanced Loaders
205+
---------------------
206+
207+
In most cases it's better not to implement
208+
:class:`Symfony\\Component\\Config\\Loader\\LoaderInterface`
209+
yourself, but extend from :class:`Symfony\\Component\\Config\\Loader\\Loader`.
210+
This class knows how to use a :class:`Symfony\\Component\\Config\\Loader\\LoaderResolver`
211+
to load secondary routing resources.
212+
213+
Of course you still need to implement
214+
:method:`Symfony\\Component\\Config\\Loader\\LoaderInterface::supports`
215+
and :method:`Symfony\\Component\\Config\\Loader\\LoaderInterface::load`.
216+
Whenever you want to load another resource, for instance a Yaml routing
217+
configuration file, you can call the
218+
:method:`Symfony\\Component\\Config\\Loader\\Loader::import` method::
219+
220+
namespace Acme\DemoBundle\Routing;
221+
222+
use Symfony\Component\Config\Loader\Loader;
223+
use Symfony\Component\Routing\RouteCollection;
224+
225+
class AdvancedLoader extends Loader
226+
{
227+
public function load($resource, $type = null)
228+
{
229+
$collection = new RouteCollection();
230+
231+
$resource = '@AcmeDemoBundle/Resources/config/import_routing.yml';
232+
$type = 'yaml';
233+
234+
$importedRoutes = $this->import($resource, $type);
235+
236+
$collection->addCollection($importedRoutes);
237+
238+
return $collection;
239+
}
240+
241+
public function supports($resource, $type = null)
242+
{
243+
return $type === 'advanced_extra';
244+
}
245+
}
246+
247+
.. note::
248+
249+
The resource name and type of the imported routing configuration can
250+
be anything that would normally be supported by the routing configuration
251+
loader (Yaml, XML, PHP, annotation, etc.).
252+
253+
.. _`FOSRestBundle`: https://github.com/FriendsOfSymfony/FOSRestBundle
254+
.. _`KnpRadBundle`: https://github.com/KnpLabs/KnpRadBundle
255+
.. _`SonataAdminBundle`: https://github.com/sonata-project/SonataAdminBundle

cookbook/routing/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ Routing
88
slash_in_parameter
99
redirect_in_config
1010
method_parameters
11+
custom_route_loader

0 commit comments

Comments
 (0)