Routing

Routing is how Symfony decides which controller method should handle an incoming URL. The modern way to define a route is a PHP 8 attribute placed directly above the controller method it belongs to — no separate routing file to keep in sync with your code.

Attribute-Based Routing

The #[Route(...)] attribute sits right on top of the method it maps to, so the URL and the code that handles it live in the same place:

<?php
namespace App\Controller;

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

class ArticleController extends AbstractController
{
    #[Route('/articles/{id<\d+>}', name: 'article_show')]
    public function show(int $id)
    {
        // $id arrives here already cast to an int
        return $this->render('article/show.html.twig', ['id' => $id]);
    }
}

This is the modern default and the approach favored throughout this track. Symfony also lets you define routes centrally in config/routes.yaml if you'd rather keep URL definitions out of your controllers, but attributes are the recommended starting point.

Route Parameters and Requirements

In the example above, {id<\d+>} is an inline requirement — it tells Symfony that {id} must match one or more digits, so a request like /articles/abc won't even match this route. The same requirement can also be written as a separate array on the attribute, which is useful when a pattern gets long or is reused:

#[Route('/articles/{id}', name: 'article_show', requirements: ['id' => '\d+'])]
public function show(int $id)
{
    // same route as the inline version above
}

Both forms produce the exact same route — pick whichever reads more clearly for the route you're writing. The inline <\d+> syntax is handy for quick, obvious constraints, while the requirements array keeps things readable once a route has several parameters.

Named Routes and Generating URLs

Giving a route a name — like article_show above — means you never have to hardcode the URL string elsewhere in your application. From inside a controller, call generateUrl() with the route name and any parameters it needs:

$url = $this->generateUrl('article_show', ['id' => 5]);
// $url is now "/articles/5"

If the route's path ever changes, every place that generates a URL by name keeps working automatically — only the #[Route] attribute itself needs updating.

Automatic Type-Casting

Notice the controller method signature used int $id rather than a plain $id. Every URL segment arrives as a string, but when the matching method argument has a scalar type-hint like int, float, or bool, Symfony converts the value for you before the method runs — one less manual cast to remember.

Up next: Head to Controllers to see the full picture of what a controller can do once a route has matched.