Earlier I was looking for an alternative to Symfony1′s routing.load_configuration event, so I could add some extra routes on-the-fly. This may be useful, when routes change in more ways than only variable request parameters as part of routes do (you know, like /blog/{id}). I got it completely wrong in my previous post about this subject. Of course, adding extra routes is a matter of creating a custom routing loader, and tell the framework about it using the service container. So, there we go.

First we have to create a custom route loader. This class should implement LoaderInterface, which is part of Symfony2′s Config component. It has two methods that are relevant here: supports() and load(). The loader resolver will ask our custom route loader if it can handle a resource (like “@AcmeDemoBundle/Controller/DemoController.php”) of a certain type (like “annotation”). We return true, if the type is “extra”. When this is the case, the resolver will call the load() method. It’s $resource element is irrelevant, since we want to add our extra routes anyway. The load() method should return a RouteCollection containing the new routes we want to add. namespace Acme\RoutingBundle\Routing; use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\Config\Loader\LoaderResolver; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; class ExtraLoader implements LoaderInterface { private$loaded = false;

public function load($resource,$type = null)
{
if (true === $this->loaded) { throw new \RuntimeException('Do not add this loader twice'); }$routes = new RouteCollection();

$pattern = '/extra';$defaults = array(
'_controller' => 'AcmeRoutingBundle:Demo:extraRoute',
);

$route = new Route($pattern, $defaults);$routes->add('extraRoute', $route); return$routes;
}

public function supports($resource,$type = null)
{
return 'extra' === $type; } public function getResolver() { } public function setResolver(LoaderResolver$resolver)
{
// irrelevant to us, since we don't need a resolver
}
}


Note: make sure the controller you specify really exists.

Now we make a service for our ExtraLoader.

<!-- in /src/Acme/RoutingBundle/Resources/config/services.xml -->
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<services>
</service>
</services>
</container>


Notice the tag called “routing.loader”. The delegating routing loader which is used by the framework, will look for all services with the tag “routing.loader” and add them as potential loaders. As said before, when one of these loaders gets a call to supports() and returns true, the load() method will be called and the loader is allowed to add some routes.

The last thing we need, is a few extra lines in /app/config/routing.yml:

AcmeRoutingBundle:
resource: .
type: extra


The “resource” key is irrelevant, but required. The important part here is “extra”. This is the “type” which our ExtraLoader supports and thus a call to it’s load() method will definitely be made.

Oh, don’t forget to clear the cache!

56 Responses to Symfony2: dynamically add routes

• # January 6, 2012 at 14:00

hi, great solution, but, i can’t get, why there is this line: ‘resource: .’. That dot as a value means null? I thought null is in yaml represented via null or ~.

rmo

• # January 6, 2012 at 16:02

Hi, the dot means nothing, but I think using a null value might throw an exception, I will try this later. I just copied the dot notation from the assetic bundle.

Matthias Noback

• # February 28, 2013 at 08:16

hi Matthias Noback ,, i m new to symfony2 with php i just want to know how to make a index.php inside web dir ,which contain the routing of spefic bundle routing from inside itself
.. can u give me ur gmail id or any other where i can contact to u and can do live chat with u ?

ramkesh

• # January 7, 2012 at 11:29

I guess this is the way in which assetic routes are loaded on the fly when using the controller

cordoval

• # January 26, 2012 at 00:57

Great post! Was able to use this information to fix my Doctrine ODM route loader. The “resource: .” was exactly what I needed. Thanks!

Chris Jones

• # February 26, 2012 at 05:53

can you give an example, where the custom route loader could be useful? some some practical usage?

Mark

• # February 26, 2012 at 10:43

Hi Mark,
You can use a custom route loader for adding routes that can be generated automatically, or when you have a bundle and don’t want to ask your users to add the routes to routing.yml manually.

Matthias Noback

• # February 26, 2012 at 11:39

Right now I’m trying to implement some kind of dynamic prefix route, but I want it to be optional so that my app will work with or without it:
localhost/{PREFIX}/app-routes
localhost/app-routes

maybe you know what is the best way to do this?
app:
prefix: /my-prefix
but this makes the prefix mandatory, and I want it to be optional.

Thanks.

Mark

• # February 28, 2012 at 10:16

Hi, Mark.
Try to use multiple routes which point to the same controller:
dattaya_main_test:
pattern: /{PREFIX}/app-routes
defaults: { _controller: DattayaMainBundle:Main:test }
dattaya_main_test1:
pattern: /app-routes
defaults: { _controller: DattayaMainBundle:Main:test }
Or if you’re using annotation:
* @Route (“/{PREFIX}/app-routes”)
* @Route (“/app-routes”)

If you want {PREFIX} to match “/” character also, read http://symfony.com/doc/2.0/cookbook/routing/slash_in_parameter.html

Dattaya

• # May 4, 2012 at 10:43

Hi,
in my case the route rull exist
/**
* @Route(“/{extension}/{_locale}”, name=”homepage”, defaults={“_locale” = “fr”, “extension” = “mon-extension”}, requirements={“extension” = “mon-extension”, “_locale” = “en|fr|de|it|es|pl”})
*/

But all my route params are dynamique
(i have a service that extract an acount by extension from the databse)
teh acount have:
– unique extension
– default language
how to custumizethis route with dynamique data (requirement and default shhould be dynamique not only the {extension} ans {_local} params)

Ahmed

• # June 22, 2012 at 08:48

Great article.

My only problem is that I need to clear the cache with every change even when using the production environment. Any suggestions?

Daniel

• # June 26, 2012 at 13:07

I’m sure it must be possible to clear the cache from within a running application (after the change was made), though I wouldn’t know from the top of my head how to do that.

Matthias Noback

• # July 13, 2012 at 15:32

You can use a “/{anything}” route, with requirement “anything” = “.+” and than programmatically parse the URL. You can then create your own cache for the way you determine the result.

Matthias Noback

• # September 3, 2012 at 14:43

Thanks for sharing your findings. I could successfully implement this for my project. However I have one open question: To build the rules I need to access the doctrine entity manager but since I don’t get the kernel or the container I have nothing to I could ask it.

Any ideas how to access the doctrine entity manager from withing the load() method?

Ernst

• # September 3, 2012 at 15:48

Hi Ernst,

use Doctrine\ORM\EntityManager

{
private $em; public function __construct(EntityManager$em)
{
$this->em =$em;
}
}


Then expand the service definition:

<service id="acme.routing_loader" class="Acme\RoutingBundle\Routing\ExtraLoader">
<argument type="service" id="doctrine.orm.entity_manager" />
</service>


Good luck!

Matthias

Matthias Noback

• # September 3, 2012 at 17:55

Thanks a lot that looks promising (and I think I just learned a lot about dependency injection). However I’m failing to translate this into YAML. Is there someone who can show me how this XML has to look like in YAML?

Ernst

• # September 4, 2012 at 08:11

After all I found it myself:

 services: people.routing_loader: class: path\to\bundle\MyBundle\Routing\EntityRouterLoader argument: [doctrine.orm.entity_manager] tags: - { name: routing.loader } 

Ernst

• # March 10, 2013 at 17:13

Catchable Fatal Error: Argument 1 passed to Acme\HelloBundle\Routing\ExtraLoader::__construct() must be an instance of Doctrine\ORM\EntityManager, none given, called in /var/www/domains/sf2.loc/Symfony/app/cache/dev/appDevDebugProjectContainer.php on line 1437 and defined in /var/www/domains/sf2.loc/Symfony/src/Acme/HelloBundle/Routing/ExtraLoader.php line 27

ivan

• # March 11, 2013 at 20:30

I don’t know Ivan, your service definition does not seem to be right – maybe it misses the doctrine.orm.entity_manager argument.

Matthias Noback

• # March 12, 2013 at 07:35

I can send you the code if you do not mind. Total 4 files. Please help me solve this problem.

ivan

• # March 22, 2013 at 20:17

Could you create a gist (https://gist.github.com/) or something?

Matthias Noback

• # September 17, 2012 at 07:37

Thanks for example!

Can you tell me will it cached or not?

John

• # September 17, 2012 at 07:44

Hi John, it will certainly be cached. For production, the routing is loaded only once and then dumped to the cache folder (refer to the generated UrlGenerator and UrlMatcher classes in /app/cache to see if the loading process was successful).

Matthias Noback

• # September 17, 2012 at 08:57

Thanks!

John

• # September 27, 2012 at 11:04

Dear Matthias,

I am trying to use your tutorial and I am getting the error: Cannot load resource “.”.

Do you have an idea on how to resolve this.

Thank you very much.

Milos

• # September 29, 2012 at 19:58

I have not tried to run this example recently – my guess would be that somehow the loader service is not well defined (missing tag or something) or the services.xml file is not loaded correctly. One other option: I have tested this with Symfony 2.0, it might not work in 2.1 anymore. Please let me know if you have found the solution.

Matthias Noback

• # October 11, 2012 at 21:09

Hello, great implementation. I am trying to add dynamic routers using your idea and I get an error:

Circular reference detected in “/../app/config/routing.yml” (“/../app/config/routing.yml” > “router_loader_extras” > “/../app/config/routing.yml”).

Max Martínez

• # October 14, 2012 at 10:58

It is possible that you have indeed made a circular reference somehow, which means that you load routing.yml, while you are already loading it. But most of the times, this exception means that your routing contains syntax errors, for example a missing or unavailable annotation. You should check out the previous exceptions, deeper in the hierarchy.

Matthias Noback

• # October 25, 2012 at 02:04

Hello Matthias Noback,

Thanks a lot for posting this tutorial.

I was confused how to simplify common CRUD routing.
But now I have workaround using resource as you can see in this class.

Before and after using CrudLoader can be seen here.
https://github.com/epsi/AlumniBook-SF2/blob/1004aea21a44e6ed8b664570e6d6cf4816fcd586/src/Iluni/BookBundle/Resources/config/routing/crud/acompetencies.yml

I don’t know if my approach is right or bad practice.
It works well so far.

Once again. Thank you.

~epsi — sorry for my english

epsi

• # October 28, 2012 at 10:24

Thanks for sharing, this looks very nice!

Matthias Noback

• # October 25, 2012 at 15:13

Under Symfony 2.1.2 i got this error:

Pixo\Modules\NewsBundle\Routing\ExtraLoader::setResolver() must be compatible with Symfony\Component\Config\Loader\LoaderInterface::setResolver(Symfony\Component\Config\Loader\LoaderResolverInterface $resolver) in /usr/share/nginx/www/Symfonya/sfvalet/src/Pixo/Modules/NewsBundle/Routing/ExtraLoader.php How can i fix this ? Shijima • # October 25, 2012 at 15:40 Solved ! thank’s. Shijima • # July 11, 2013 at 14:17 What is the solution for this problem ? PHP Fatal error: Declaration of MR\RegiomotoBundle\Routing\ExtraLoader::setResolver() must be compatible with that of Symfony\Component\Config\Loader\LoaderInterface::setResolver() in /home/bart/www/mobile.kody-etap8/src/MR/RegiomotoBundle/Routing/ExtraLoader.php on line 18 Bart • # July 16, 2013 at 08:48 The method signatures don’t match. For Symfony 2.0: public function setResolver(LoaderResolver$resolver);


public function setResolver(LoaderResolverInterface $resolver);  Matthias Noback • # October 26, 2012 at 08:19 I’m trying to use the service in a controller passing new route to the load function with no luck (router:debug shows only routes added in ExtraLoader) … what’s wrong ? Shijima • # October 28, 2012 at 10:27 Hi Shjima, I’m not sure if I understand tour question fully. But adding routes from within a controller is too late. It should be done when the routing is loaded, which means you should (like described above) add resources to the main routing.yml. Good luck! Matthias Noback • # November 24, 2012 at 13:54 Dear Matthias, Thank you for this wonderful tut. I have 1 question which I hope you can help me with: I’m in the situation where my bundle is loaded after the framework bundle is (because my bundle is relying on certain parts of the framework bundle). The problem is that in this case it seems like adding the extraloader via my bundle config doesnt seem to have any effect because the RoutingResolverPass has already been run before in the framework bundle. Any word of advice for me? Raine • # November 26, 2012 at 09:26 What is your specific dependency on the framework bundle? Maybe there is a workaround for that part. Matthias Noback • # January 19, 2013 at 21:41 Works great under Symfony 2.2.0 BETA1. Just change LoadResolver to LoadResolverInterface. Art Hundiak • # March 13, 2013 at 20:39 Nice article, I am interested though once I add those dynamically generated routes, how do i refer to them in twig or the router service like the path() and generateUrl methods? Thanks! Feras • # March 24, 2013 at 11:13 Matthias please add word-break: break-all; to the divs so to wrap long links Luis Cordova • # March 24, 2013 at 16:14 This is great, however how do you get around the caching issue? I’ve created my own CMS where pages can be added, however the dynamic routing works for the first page you land on e.g if you go to /about-us however no other pages are accessible because the routing file get’s cached so bypasses this script. David • # March 25, 2013 at 11:11 Hi David, You can not truly define your routes dynamically using this strategy. Your route loader will not be called for subsequent requests. If you can determine certain routes only at runtime, you could use a route pattern like /{dynamic} with this requirement for “dynamic”: .+. You can then forward to the right controller from within the controller this route points to. Matthias Noback • # March 26, 2013 at 22:09 When i return a RouteCollercion under my routeing news.yml esolving_pageB_news: resource: ‘@EsolvingPageBundle/Resources/config/routing/news_real.yml’ # resource: ‘/var/www/EsolvingSevenpharma/src/Esolving/PageBundle/Resources/config/routing/news_real.yml’ prefix: / Cannot import resource “@EsolvingPageBundle/Resources/config/routing/news_real.yml” from “routing/news.yml”. Make sure the “EsolvingPageBundle” bundle is correctly registered and loaded in the application kernel class. but in my kernel is registered EsolvingPageBundle… help me please. luis • # July 28, 2013 at 13:58 I got this working – thanks for this post. The one issue I had was the ExtraLoader::setResolver() must be compatible with …. To solve the issue make sure to change the use Symfony\Component\Config\Loader\LoaderResolver declaration at BeSimple\SoapBundle\ServiceDefinition\Loader\AnnotationClassLoader to use Symfony\Component\Config\Loader\LoaderResolverInterface Thanks Rob • # July 30, 2013 at 16:32 That’s right! Matthias Noback • # November 20, 2013 at 23:40 Hi Matthias; great article. I have a quick question. It seems like few thing changed in version 2.3. I applied this example in earlier versions with great success but with 2.3 I need to send a requirements parameter to Route constructor as well. Otherwise I get Route not found error. Do you have any idea why this is happening? http://symfony.com/doc/current/cookbook/routing/custom_route_loader.html In the code located in above page all I want to achieve is to build the router without$requirements parameter.

$pattern = ‘/extra/{parameter}’;$defaults = array(
‘_controller’ => ‘AcmeDemoBundle:Demo:extra’,
);
$requirements = array( ‘parameter’ => ‘\d+’, );$route = new Route($pattern,$defaults, $requirements); but without$requirements route cannot be found..

Can Berkol

• # November 21, 2013 at 09:52

Well, I don’t know exactly how the router works, but it seems to me that if you have a dynamic parameter in your route, you will always have to define what it can be (so that the router know which URIs match and which don’t). If the parameter can really be anything, your requirement would be .+ which means at least one thing of anything .

Matthias Noback

• # November 21, 2013 at 13:27

Matthias thanks for the quick response but I think I was wrong; the route is not being registered at all. I was debugging Symfony components but no luck so far. When I manually enter route definition to app/config things work fine. Anyways, I need to move on for now but as soon as I figure out what’s going on I’ll let everyone now.

Can Berkol