PHP & Symfony About PHP and Symfony2 development

Symfony2: dynamically add routes

Posted on by Matthias Noback

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 id="acme.routing_loader" class="Acme\RoutingBundle\Routing\ExtraLoader">
            <tag name="routing.loader"></tag>
        </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!

Categories: PHP Symfony2

Tags: dependency injection routing

Comments: Comments