Nicolas Grekas
Contributed by Nicolas Grekas in #54720 and #54455

Symfony maps route parameters to controller arguments automatically since day one. You only have to use the same name in both places and Symfony does the necessary data type conversion:

1
2
3
4
5
6
7
#[Route('/document/{id}')]
public function showDocument(int $id): Response
{
    // here, $id is an integer variable with the same value as
    // the string value included in the URL after '/document/'
    // ...
}

When using Doctrine entities, this feature is even more convenient because it can transform the route parameters into entities via database queries:

1
2
3
4
5
6
7
8
9
10
11
// ...
use App\Entity\Document;

#[Route('/document/{id}')]
public function showDocument(Document $document): Response
{
    // using the $id value of the route, Symfony makes a database query
    // to find an entity of type Document and whose id matches that value
    // (and throws a 404 error if the entity is not found)
    // ...
}

This feature works by using all the route parameters in the database query:

1
2
3
4
5
6
7
#[Route('/document/{slug}/{id}-{name}/')]
public function showDocument(Document $document): Response
{
    // the database query in this case would be:
    // $repository = $entityManager->getRepository(Document::class);
    // $document = $repository->findOneBy(['slug' => 'the slug', 'id' => 'the id', 'name' => 'the name']);
}

This becomes problematic when the controller arguments contain more than one entity. All route parameters will be used to find all entities, resulting in errors. You can solve this with the #[MapEntity] attribute but in Symfony 7.1 we're introducing Mapped Route Parameters as an alternative.

When adding route parameters, you can now define the mapping between the route parameter and the controller argument:

1
2
3
4
5
6
7
#[Route('/document/{slug:category}/{id:document}-{name:document}/')]
public function showDocument(Document $document, Category $category): Response
{
    // the database queries in this case would be:
    // $document = $documentRepository->findOneBy(['id' => 'the id', 'name' => 'the name']);
    // $category = $categoryRepository->findOneBy(['slug' => 'the slug']);
}

This explicit mapping configuration solves the issues when using multiple entities as controller arguments and is more concise than using the #[MapEntity] attribute. This mapping also comes in handy when you can't change the route parameter names but want to use different controller argument names:

1
2
#[Route('/voucher/{voucher:voucherCode}/{action:userAction}/')]
public function handleVoucher(string $voucherCode, string $userAction): Response

Given that this new feature provides a nice DX (developer experience) and is very concise, we've decided to deprecate the automatic mapping of route parameters into Doctrine entities starting from Symfony 7.1. This means that the following example is now deprecated:

1
2
#[Route('/document/{id}')]
public function showDocument(Document $document): Response

To fix this deprecation, you can use the #[MapEntity] attribute:

1
2
3
4
5
6
7
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
// ...

#[Route('/document/{id}')]
public function showDocument(
    #[MapEntity(id: 'id')] Document $document,
): Response

Or you can use the name of the argument as route parameter:

1
2
#[Route('/document/{document}')]
public function showDocument(Document $document): Response
Published in #Living on the edge