Routing
A route connects an address with a controller method. In Symfony it is written as an attribute right above the method — the address and the code handling it always sit together.
A simple route
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class BlogController
{
#[Route('/blog', name: 'blog_index')]
public function index(): Response
{
return new Response('List of posts');
}
}name is the route's name. Links are generated by it, so always set one.
Parameters
A part of the address in curly braces goes into the method argument of the same name:
#[Route('/blog/{slug}', name: 'blog_show')]
public function show(string $slug): Response
{
return new Response("Post: $slug");
}The address /blog/first-post passes the string first-post to the method.
Requirements and defaults
A regular expression in angle brackets restricts a parameter, and a default value on the argument makes it optional:
#[Route('/blog/page/{page<\d+>}', name: 'blog_page')]
public function page(int $page = 1): Response
{
return new Response("Page $page");
}The address /blog/page/abc does not match this route — Symfony returns a 404 error.
HTTP methods
#[Route('/blog/{id}', name: 'blog_update', methods: ['PUT', 'PATCH'])]
public function update(int $id): Response
{
// …
}Without methods a route answers any method.
A shared prefix
An attribute above the class adds the start of the address and of the name to every route inside:
#[Route('/admin', name: 'admin_')]
class AdminController
{
#[Route('/users', name: 'users')] // /admin/users, named admin_users
public function users(): Response
{
// …
}
}Generating links
Addresses are not written by hand — they are built from the route name. In a controller:
$url = $this->generateUrl('blog_show', ['slug' => 'first-post']);
// /blog/first-postIn a template:
<a href="{{ path('blog_show', {slug: post.slug}) }}">{{ post.title }}</a>If the route's address changes, every link updates by itself.
Listing routes
php bin/console debug:router
php bin/console router:match /blog/first-postThe second command shows which route handles an address.
Common mistake
Routes are checked in order, and the first match wins. If /blog/{slug} is declared before /blog/new, the address /blog/new lands in the first one — with slug equal to new. Declare exact addresses higher up, or set the order explicitly: #[Route('/blog/new', priority: 2)].