Prefixes, Groups and Debugging

Put #[Route] on the class to prefix a group of routes, and use debug:router to see what the router really has.

Syntax#[Route('/articles', name: 'app_article_')] on the class; php bin/console debug:router

Routes in one controller usually share a path and a naming scheme. A #[Route] attribute on the class supplies both. Its path is prefixed to every method's path, and its name is prefixed to every method's route name. A class-level requirements array applies to all of them too.

The concatenation is literal. Class name: 'app_article_' plus method name: 'show' gives app_article_show; drop the trailing underscore and you get app_articleshow. A method whose own #[Route] has an empty path matches the prefix itself.

Groups in YAML

The same grouping exists one level up. config/routes.yaml imports your controllers, and that import can carry prefix, name_prefix and requirements that apply to everything imported. This is how a whole application moves under /api without editing a single attribute.

Locale prefixes

A route's path can be an array keyed by locale, giving a different URL per language for one controller. Generating that URL uses the one route name and the current locale, or an explicit _locale parameter.

Debugging

Prefixes make it easy to be wrong about the final path. php bin/console debug:router lists every route with its methods and path; adding a route name shows that route alone. php bin/console router:match /articles/7 works the other way, reporting which route a URL hits. Check these before changing code.

Example

Example · php
<?php
// src/Controller/ArticleController.php

declare(strict_types=1);

namespace App\Controller;

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

// Class-level attribute: path prefix and name prefix for every
// method below. The trailing underscore in the name is required.
#[Route('/articles', name: 'app_article_')]
final class ArticleController extends AbstractController
{
    // Empty path: matches the prefix itself, /articles.
    // Full route name: app_article_index
    #[Route('', name: 'index', methods: ['GET'])]
    public function index(): Response
    {
        return new Response('<h1>Articles</h1>');
    }

    // Path /articles/new, name app_article_new
    #[Route('/new', name: 'new', priority: 1, methods: ['GET'])]
    public function new(): Response
    {
        return new Response('<h1>New article</h1>');
    }

    // Path /articles/{id}, name app_article_show
    #[Route('/{id<\d+>}', name: 'show', methods: ['GET'])]
    public function show(int $id): Response
    {
        return new Response(sprintf('Article %d', $id));
    }
}

When to use it

  • Give every route in a controller the same /articles prefix and app_article_ name prefix.
  • Mount an existing set of controllers under /api without editing their attributes.
  • Find out which route and controller a failing URL actually matches.

More examples

Seeing the result of a prefix

`debug:router` prints the routes as the router holds them, after every prefix has been applied, so it settles arguments about what a path really is. `--show-controllers` adds the class and method that each route runs, which is how you find the code behind an unfamiliar URL. `router:match` reports the matching route for a path, or tells you none matched.

Example · bash
php bin/console debug:router --show-controllers

# -------------------- -------- -------- ------ -------------------
#  Name                 Method   Scheme   Host   Path
# -------------------- -------- -------- ------ -------------------
#  app_article_index    GET      ANY      ANY    /articles
#  app_article_new      GET      ANY      ANY    /articles/new
#  app_article_show     GET      ANY      ANY    /articles/{id}
# -------------------- -------- -------- ------ -------------------

# One route in detail, including its requirements and defaults:
php bin/console debug:router app_article_show

# Which route answers a given URL:
php bin/console router:match /articles/7

Prefixing an import in YAML

`prefix` and `name_prefix` on an import are the file-level equivalent of a class-level `#[Route]`. This is how a versioned or namespaced API gets its path segment in one place, and how a bundle's routes are mounted under a path you choose rather than the one its author chose.

Example · yaml
# config/routes.yaml
# The default import that reads #[Route] attributes from src/Controller/.
controllers:
    resource: routing.controllers

# A second import of the same kind can carry a prefix. Everything
# it loads gains /api on the path and api_ on the route name,
# without any attribute changing.
api_controllers:
    resource: routing.controllers
    prefix: /api
    name_prefix: api_

One controller, a different URL per locale

Both `GET /about-us` and `GET /over-ons` return 200 from this one method, and generating `app_about` produces the URL for the current locale unless you pass `_locale` explicitly. Reach for this when the URLs themselves should be translated rather than carrying a `/en/` segment.

Example · php
<?php
// src/Controller/AboutController.php

declare(strict_types=1);

namespace App\Controller;

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

final class AboutController extends AbstractController
{
    // path as an array: one URL per locale, one route name.
    // The entry without a key is the fallback for other locales.
    #[Route(path: [
        'en' => '/about-us',
        'nl' => '/over-ons',
        '/about-us',
    ], name: 'app_about', methods: ['GET'])]
    public function about(): Response
    {
        // When a localized route matches, that locale is used for
        // the rest of the request.
        return new Response('<h1>About</h1>');
    }
}

Discussion

  • Be the first to comment on this lesson.