diff --git a/cookbook/routing/custom_route_loader.rst b/cookbook/routing/custom_route_loader.rst index a6e682ffe70..046ea128409 100644 --- a/cookbook/routing/custom_route_loader.rst +++ b/cookbook/routing/custom_route_loader.rst @@ -4,23 +4,28 @@ How to Create a custom Route Loader =================================== -A custom route loader allows you to add routes to an application without -including them, for example, in a YAML file. This comes in handy when -you have a bundle but don't want to manually add the routes for the bundle -to ``app/config/routing.yml``. This may be especially important when you want -to make the bundle reusable, or when you have open-sourced it as this would -slow down the installation process and make it error-prone. - -Alternatively, you could also use a custom route loader when you want your -routes to be automatically generated or located based on some convention or -pattern. One example is the `FOSRestBundle`_ where routing is generated based -off the names of the action methods in a controller. +What is a Custom Route Loader +----------------------------- + +A custom route loader enables you to generate routes based on some +conventions or patterns. A great example for this use-case is the +`FOSRestBundle`_ where routes are generated based on the names of the +action methods in a controller. + +A custom route loader does not enable your bundle to inject routes +without the need to modify the routing configuration +(e.g. ``app/config/routing.yml``) manually. +If your bundle provides routes, whether via a configuration file, like +the `WebProfilerBundle` does, or via a custom route loader, like the +`FOSRestBundle`_ does, an entry in the routing configuration is always +necessary. .. note:: There are many bundles out there that use their own route loaders to accomplish cases like those described above, for instance - `FOSRestBundle`_, `JMSI18nRoutingBundle`_, `KnpRadBundle`_ and `SonataAdminBundle`_. + `FOSRestBundle`_, `JMSI18nRoutingBundle`_, `KnpRadBundle`_ and + `SonataAdminBundle`_. Loading Routes -------------- @@ -35,20 +40,18 @@ and therefore have two important methods: :method:`Symfony\\Component\\Config\\Loader\\LoaderInterface::supports` and :method:`Symfony\\Component\\Config\\Loader\\LoaderInterface::load`. -Take these lines from the ``routing.yml`` in the AcmeDemoBundle of the Standard -Edition: +Take these lines from the ``routing.yml`` in the Symfony Standard Edition: .. code-block:: yaml - # src/Acme/DemoBundle/Resources/config/routing.yml - _demo: - resource: "@AcmeDemoBundle/Controller/DemoController.php" + # app/config/routing.yml + app: + resource: @AppBundle/Controller/ type: annotation - prefix: /demo -When the main loader parses this, it tries all the delegate loaders and calls +When the main loader parses this, it tries all registered delegate loaders and calls their :method:`Symfony\\Component\\Config\\Loader\\LoaderInterface::supports` -method with the given resource (``@AcmeDemoBundle/Controller/DemoController.php``) +method with the given resource (``@AppBundle/Controller/``) and type (``annotation``) as arguments. When one of the loader returns ``true``, its :method:`Symfony\\Component\\Config\\Loader\\LoaderInterface::load` method will be called, which should return a :class:`Symfony\\Component\\Routing\\RouteCollection` @@ -59,20 +62,24 @@ Creating a custom Loader To load routes from some custom source (i.e. from something other than annotations, YAML or XML files), you need to create a custom route loader. This loader -should implement :class:`Symfony\\Component\\Config\\Loader\\LoaderInterface`. +has to implement :class:`Symfony\\Component\\Config\\Loader\\LoaderInterface`. + +In most cases it's better not to implement +:class:`Symfony\\Component\\Config\\Loader\\LoaderInterface` +yourself, but extend from :class:`Symfony\\Component\\Config\\Loader\\Loader`. The sample loader below supports loading routing resources with a type of ``extra``. The type ``extra`` isn't important - you can just invent any resource type you want. The resource name itself is not actually used in the example:: - namespace Acme\DemoBundle\Routing; + // src/AppBundle/Routing/ExtraLoader.php + namespace AppBundle\Routing; - use Symfony\Component\Config\Loader\LoaderInterface; - use Symfony\Component\Config\Loader\LoaderResolverInterface; + use Symfony\Component\Config\Loader\Loader; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; - class ExtraLoader implements LoaderInterface + class ExtraLoader extends Loader { private $loaded = false; @@ -87,14 +94,14 @@ type you want. The resource name itself is not actually used in the example:: // prepare a new route $path = '/extra/{parameter}'; $defaults = array( - '_controller' => 'AcmeDemoBundle:Demo:extra', + '_controller' => 'AppBundle:Extra:extra', ); $requirements = array( 'parameter' => '\d+', ); $route = new Route($path, $defaults, $requirements); - // add the new route to the route collection: + // add the new route to the route collection $routeName = 'extraRoute'; $routes->add($routeName, $route); @@ -107,32 +114,36 @@ type you want. The resource name itself is not actually used in the example:: { return 'extra' === $type; } + } - public function getResolver() - { - // needed, but can be blank, unless you want to load other resources - // and if you do, using the Loader base class is easier (see below) - } +Make sure the controller you specify really exists. In this case you +have to create an ``extraAction`` method in the ``ExtraController`` +of the ``AppBundle``:: + + // src/AppBundle/Controller/ExtraController.php + namespace AppBundle\Controller; - public function setResolver(LoaderResolverInterface $resolver) + use Symfony\Component\HttpFoundation\Response; + use Symfony\Bundle\FrameworkBundle\Controller\Controller; + + class ExtraController extends Controller + { + public function extraAction($parameter) { - // same as above + return new Response($parameter); } } -.. note:: - - Make sure the controller you specify really exists. - Now define a service for the ``ExtraLoader``: .. configuration-block:: .. code-block:: yaml + # app/config/services.yml services: - acme_demo.routing_loader: - class: Acme\DemoBundle\Routing\ExtraLoader + app.routing_loader: + class: AppBundle\Routing\ExtraLoader tags: - { name: routing.loader } @@ -144,7 +155,7 @@ Now define a service for the ``ExtraLoader``: xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - + @@ -156,14 +167,15 @@ Now define a service for the ``ExtraLoader``: $container ->setDefinition( - 'acme_demo.routing_loader', - new Definition('Acme\DemoBundle\Routing\ExtraLoader') + 'app.routing_loader', + new Definition('AppBundle\Routing\ExtraLoader') ) ->addTag('routing.loader') ; -Notice the tag ``routing.loader``. All services with this tag will be marked -as potential route loaders and added as specialized routers to the +Notice the tag ``routing.loader``. All services with this *tag* will be marked +as potential route loaders and added as specialized route loaders to the +``routing.loader`` *service*, which is an instance of :class:`Symfony\\Bundle\\FrameworkBundle\\Routing\\DelegatingLoader`. Using the custom Loader @@ -177,7 +189,7 @@ Instead, you only need to add a few extra lines to the routing configuration: .. code-block:: yaml # app/config/routing.yml - AcmeDemoBundle_Extra: + app_extra: resource: . type: extra @@ -201,8 +213,8 @@ Instead, you only need to add a few extra lines to the routing configuration: return $collection; -The important part here is the ``type`` key. Its value should be "extra". -This is the type which the ``ExtraLoader`` supports and this will make sure +The important part here is the ``type`` key. Its value should be "extra" as +this is the type which the ``ExtraLoader`` supports and this will make sure its ``load()`` method gets called. The ``resource`` key is insignificant for the ``ExtraLoader``, so it is set to ".". @@ -215,11 +227,11 @@ for the ``ExtraLoader``, so it is set to ".". More advanced Loaders --------------------- -In most cases it's better not to implement -:class:`Symfony\\Component\\Config\\Loader\\LoaderInterface` -yourself, but extend from :class:`Symfony\\Component\\Config\\Loader\\Loader`. -This class knows how to use a :class:`Symfony\\Component\\Config\\Loader\\LoaderResolver` -to load secondary routing resources. +If your custom route loader extends from +:class:`Symfony\\Component\\Config\\Loader\\Loader` as shown above, you +can also make use of the provided resolver, an instance of +:class:`Symfony\\Component\\Config\\Loader\\LoaderResolver`, to load secondary +routing resources. Of course you still need to implement :method:`Symfony\\Component\\Config\\Loader\\LoaderInterface::supports` @@ -228,7 +240,8 @@ Whenever you want to load another resource - for instance a YAML routing configuration file - you can call the :method:`Symfony\\Component\\Config\\Loader\\Loader::import` method:: - namespace Acme\DemoBundle\Routing; + // src/AppBundle/Routing/AdvancedLoader.php + namespace AppBundle\Routing; use Symfony\Component\Config\Loader\Loader; use Symfony\Component\Routing\RouteCollection; @@ -239,7 +252,7 @@ configuration file - you can call the { $collection = new RouteCollection(); - $resource = '@AcmeDemoBundle/Resources/config/import_routing.yml'; + $resource = '@AppBundle/Resources/config/import_routing.yml'; $type = 'yaml'; $importedRoutes = $this->import($resource, $type); @@ -251,7 +264,7 @@ configuration file - you can call the public function supports($resource, $type = null) { - return $type === 'advanced_extra'; + return 'advanced_extra' === $type; } }