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
Thanks! This is the kind of stuff I like!
How long would it go from deprecated to fully removed? That seems like a lot of work and such a massive (although fairly trivial) change considering it worked like that since symfony 2.x
Dont get me wrong this is a great feature and I generally like explicitness vs implicitness, but I hope this has a longer deprecation time than 8.0
This is great! Can you provide an example with a compound key, or when a later query is dependent on an earlier one? City names aren't unique, my routes are
PHPUnit still throws deprecation warning if opt to use option 2:
But if we use
MapEntity
attribute the deprecation is resolved