Symfony routes with conditions

coding010 on 2022-06-08

I was working on a recent Symfony project and had a requirement where the response between routes /foo and /foo?var=bar should be totally different. Of course this can be done in many ways in Symfony, but I struggled a bit with how to do this properly and clean. In this short blog post I’ll explain the solution I went for.

A Symfony Controller class

This solution requires us to install the Symfony ExpressionLanguage Component. I’ll show you why in the code examples down below.

Let’s install the ExpressionLanguage Component:

composer require symfony/expression-language

The ExpressionLanguage Component is pretty powerful and can do much more than this simple example.

Then create a Controller class for the /foo route:

// src/Controller/DefaultController
<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class DefaultController extends AbstractController
{
    #[Route('/foo', name: 'app.no_var')]
    public function __invoke(): Response
    {
        // Some specific logic goes here

        // ... and then return the response
        return $this->json([
            'name' => 'John Doe',
        ]);
    }
}

And create a second Controller class for /foo?var=bar:

// src/Controller/VarController
<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class VarController extends AbstractController
{
    #[Route(
        '/foo',
        name: 'app.with_var',
        condition: "'bar' === request.query.get('var')",
        priority: 1,
    )]
    public function __invoke(): Response
    {
        // Some totally different logic goes here
        $transport = '🚗';

        // ... and then return the response,
        return $this->render('var/index.html.twig', [
            'transport' => $transport,
        ]);
    }
}

I’m using attributes, but you can define your routes in XML, PHP or YAML as well. How to do that is out of scope for this post.

We can see our newly defined routes and their order when running the debug:router command.

symfony console debug:router

Which outputs the all routes in their matching order:

---------------- -------- -------- ------ -----
 Name            Method   Scheme   Host   Path
---------------- -------- -------- ------ -----
 app.with_var    ANY      ANY      ANY    /foo
 app.no_var      ANY      ANY      ANY    /foo
---------------- -------- -------- ------ -----

I tried other solutions like forward (heavy) and a kernel.controller event to change the controller to solve the problem. But it all seemed more complex and less obvious than this solution.

Please let me know your thoughts. There are many solutions and maybe you have a better way to do this.