Search code examples
phpsymfonysymfony-routing

How can I add a route requirement which requires a service method?


I need to build a route which has a dynamic condition.

For the moment, I simply use requirements in order the match against a static list a words:

/**
 * @Route(
 *     "/{category}/{id}",
 *     requirements={
 *         "category"="^(foo|bar)$"
 *     }
 * )
 *
 * ...
 */

But now I need these words to be retrieved dynamically from a service method.

While searching a solution, I gave a hope to the condition expression language; but the only variables which are accessible here are the context and the request. However, to achieve my goal I need a full access to container services.

In other words, I would like the following pseudo-php to be executed in order to test the route:

if (in_array($category, MyService::getAllCategories())) {
    /* Inform Symfony that the route matches (then use this controller) */
} else {
    /* Inform Symfony that the route does not match and that the routing process
     * has to go on. */
}

Please note that the main reason of my problem is that the {category} parameter is placed early in the url, and then can offuscate other routes. Then I can't just test my condition inside the controller and return a 404 if the condition is not required. I surely could place this route at the end in the routing process order, but I don't think it is a good solution.


Solution

  • Custom route loader can be a solution ... http://symfony.com/doc/current/routing/custom_route_loader.html This example generates not dinamic routes, but works fine.

    Only as example, assuming CategoryProvider and Category are your classes ...

    <?php
    
    // src/Routing/CategoryLoader.php
    namespace App\Routing;
    
    use Symfony\Component\Config\Loader\Loader;
    use Symfony\Component\Routing\Route;
    use Symfony\Component\Routing\RouteCollection;
    use App\CategoryProvider;
    
    class CategoryLoader extends Loader
    {
        private $isLoaded = false;
    
        private $catProvider;
    
        public function __construct(CategoryProvider $catProvider)
        {
            $this->catProvider = $catProvider;
        }
    
        public function load($resource, $type = null)
        {
            if (true === $this->isLoaded) {
                throw new \RuntimeException('Do not add the "extra" loader twice');
            }
    
            $routes = new RouteCollection();
    
            foreach ($this->catProvider->getAll() as $cat) {
    
                // prepare a new route
                $path = sprintf('/%s/{id}', $cat->getSlug());
                $defaults = [
                    '_controller' => 'App\Controller\ExtraController::extra',
                ];
                $requirements = [
                    'parameter' => '\d+',
                ];
                $route = new Route($path, $defaults, $requirements);
    
                // add the new route to the route collection
                $routeName = 'categoryRoute' . $cat->getSlug();
                $routes->add($routeName, $route);
    
            }
    
            $this->isLoaded = true;
    
            return $routes;
        }
    
        public function supports($resource, $type = null)
        {
            return 'extra' === $type;
        }
    }