Настройка маршрутов в Symfony от атрибутов до кастомных лоадеров

Раздел: Фреймворк Symfony -> Роутинг в PHP / Symfony

Маршруты (routes) в Symfony определяют соответствие между URL и контроллером. Начиная с Symfony 5.3, основным способом является использование PHP-атрибутов (attributes) прямо в классах контроллеров. Этот подход считается наиболее эффективным благодаря лаконичности и интеграции с IDE. Для проектов, где требуется централизованное управление или поддержка старых версий, доступны альтернативы: YAML, PHP-массивы, аннотации (устарели).

Основные способы определения маршрутов

Какой способ самый современный и рекомендуемый для Symfony 6+?

PHP-атрибуты (Attributes)


// src/Controller/ProductController.php
use Symfony\Component\Routing\Annotation\Route;

class ProductController extends AbstractController
{
    #[Route('/products', name: 'product_list')]
    public function list(): Response
    {
        // ...
    }

    #[Route('/products/{id}', name: 'product_show', requirements: ['id' => '\d+'])]
    public function show(int $id): Response
    {
        // ...
    }
}
  

Php symfony routes (маршруты symfony)

Атрибут #[Route] принимает путь и опции. Параметры в фигурных скобках (например {id}) автоматически передаются в метод контроллера. Обязательно наличие PHP 8.0+.

Типичная ошибка: роут не найден из-за отсутствия импорта use Symfony\Component\Routing\Annotation\Route;. Решение: добавить use-выражение. Также возможно конфликт с аннотациями Doctrine: следует использовать именно Routing\Annotation\Route.

Как определить маршруты в отдельном файле YAML?


# config/routes/product.yaml
product_list:
    path: /products
    controller: App\Controller\ProductController::list
    methods: GET

product_show:
    path: /products/{id}
    controller: App\Controller\ProductController::show
    requirements:
        id: '\d+'
  

В основном файле config/routes.yaml подключаются другие ресурсы. YAML удобен для централизованного управления, но не позволяет IDE подсказывать параметры контроллера.

Ошибка: название маршрута (например product_list) должно быть уникальным. При дублировании Symfony выдаст исключение RouteNotFoundException.

Как использовать PHP-массивы для описания маршрутов?


// config/routes.php
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;

return function (RoutingConfigurator $routes) {
    $routes->add('product_list', '/products')
        ->controller([ProductController::class, 'list'])
        ->methods(['GET']);

    $routes->add('product_show', '/products/{id}')
        ->controller([ProductController::class, 'show'])
        ->requirements(['id' => '\d+']);
};
  

PHP-формат даёт максимум гибкости: можно динамически создавать маршруты, использовать условия и циклы.

Проблема: при ошибке синтаксиса (например лишняя запятая) файл не выполнится, и роутинг перестанет работать. Рекомендуется использовать статический анализ.

Есть ли способ с аннотациями (устаревший)?


/**
 * @Route("/products", name="product_list")
 */
public function list() {}
  

Аннотации (DocBlock) работали до Symfony 6.0, но теперь заменены атрибутами. Их использование не рекомендуется в новых проектах.

Ошибка: при обновлении Symfony с версии 5.4 на 6.0 аннотации перестают обрабатываться, если не установлен пакет doctrine/annotations. Лучше сразу перейти на атрибуты.

Параметры, требования и значения по умолчанию

Для маршрута можно задать параметры с типами, требованиями (regex) и значениями по умолчанию. Атрибут позволяет всё в одном месте:


#[Route('/blog/{page}', name: 'blog_index', defaults: ['page' => 1], requirements: ['page' => '\d+'])]
public function index(int $page): Response { ... }
  

Параметр {page} опционален: если не указан в URL, используется значение 1. Требование \d+ гарантирует, что только цифры.

Ошибка: если параметр объявлен без типа int, он приходит строкой. Это может сломать логику. Используйте type hinting и конвертацию.

Именованные маршруты и генерация URL

Каждый маршрут имеет имя (параметр name). Имена используются для создания ссылок в шаблонах Twig или контроллерах.


// Twig: <a href="{{ path('product_show', {id: product.id}) }}">
// PHP: $this->generateUrl('product_show', ['id' => $product->getId()])
  

Сгенерированный URL учитывает префиксы и хосты.

Проблема: если имя маршрута изменено, а ссылки остались старыми, страница упадёт с 404. Используйте константы или генерацию через сервис router для тестирования.

Группировка маршрутов и префикс

Для модулей удобно группировать роуты с общим префиксом. В атрибутах можно использовать #[Route(path: '/admin', name: 'admin_')] на уровне класса:


#[Route('/admin', name: 'admin_')]
class AdminController extends AbstractController
{
    #[Route('/dashboard', name: 'dashboard')]
    public function dashboard(): Response {}
    // итоговый путь: /admin/dashboard, имя: admin_dashboard
}
  

В YAML и PHP группа задаётся через префикс в родительском файле.

Типичная ошибка: дублирование префикса вложенных маршрутов (например /admin/admin/dashboard). Проверьте, чтобы пути методов не начинались с /, иначе префикс игнорируется.

Расширенные возможности: хост, методы, схема, условия

Маршрут может быть ограничен хостом (например api.example.com), HTTP-методом, схемой (https) или произвольным условием (условное выражение Symfony ExpressionLanguage).


#[Route('/api/users', name: 'api_users', host: 'api.example.com', methods: ['GET'], schemes: ['https'])]
public function listUsers(): Response { ... }
  

Ошибка: если условие condition синтаксически неверно, роутинг сломается. Используйте context() для проверки.

Локализация и перевод маршрутов

Для мультиязычных сайтов используют параметр {_locale}. Он автоматически подставляет код языка и определяет локаль приложения.


#[Route('/{_locale}/blog/{slug}', name: 'blog_show', requirements: ['_locale' => 'en|fr|de'])]
public function show(string $slug): Response {}
  

Если нужно переводить URL (например /fr/actualites), применяется пакет jms/translation-bundle или явное описание нескольких маршрутов.

Ошибка: забыть добавить требование для _locale - тогда любая строка будет восприниматься как локаль, и может возникнуть конфликт с другими параметрами.

Продвинутые примеры настройки маршрутов Symfony

1. Условная маршрутизация с ExpressionLanguage

Пример

// src/Controller/ConditionalController.php
use Symfony\Component\Routing\Annotation\Route;

class ConditionalController extends AbstractController
{
    #[Route('/special-offer', name: 'special_offer', condition: "request.headers.get('X-Test') == 'true'")]
    public function special(): Response
    {
        return new Response('Condition met');
    }
}

Маршрут доступен только если в запросе присутствует заголовок X-Test: true. Условие может использовать объекты request, params, context. Ошибка: неправильный синтаксис выражения приводит к исключению.

// curl -H "X-Test: true" https://example.com/special-offer
// > Condition met
// curl https://example.com/special-offer
// > 404 Not Found

2. Использование нескольких методов и схем

Пример

#[Route('/contact', name: 'contact', methods: ['GET', 'POST'], schemes: ['https'])]
public function contact(): Response
{
    // ...
}

Метод может обрабатывать и GET (отображение формы) и POST (отправка). Схема https обязательна. Если не указать schemes, маршрут будет работать для любого протокола.

// http://example.com/contact -> 404, т.к. не https
// https://example.com/contact -> OK

3. Редирект с помощью маршрута

Пример

// config/routes.yaml
old_products:
    path: /old-products
    controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::redirectAction
    defaults:
        route: product_list
        permanent: true

Вместо написания контроллера используется встроенный RedirectController. Редирект 301 (permanent) на маршрут product_list. Можно указать permanent: false для 302.

// GET /old-products -> 301 Location: /products

4. Сложные требования с регулярными выражениями

Пример

#[Route('/article/{slug}', name: 'article_show', requirements: ['slug' => '[a-z0-9\-]+'])]
public function show(string $slug): Response {}

Допускаются только строчные буквы, цифры и дефис. Ошибка: если slug содержит заглавные или спецсимволы, маршрут не срабатывает - возвращается 404.

// /article/my-article -> OK
// /article/My_Article! -> 404

5. Группировка и префикс с общим контроллером

Пример

// src/Controller/Api/V1/UserController.php
#[Route('/api/v1/users', name: 'api_users_')]
class UserController extends AbstractController
{
    #[Route('', name: 'list', methods: ['GET'])]
    public function list(): Response {}

    #[Route('/{id}', name: 'show', methods: ['GET'], requirements: ['id' => '\d+'])]
    public function show(int $id): Response {}
}

Все маршруты класса начинаются с /api/v1/users. Имена: api_users_list, api_users_show. Параметр name на классе задаёт префикс имени.

// GET /api/v1/users -> list()
// GET /api/v1/users/42 -> show(42)

6. Динамическая загрузка маршрутов через кастомный лоадер

Пример

// src/Routing/ExtraRouteLoader.php
class ExtraRouteLoader implements LoaderInterface
{
    public function load($resource, $type = null)
    {
        $routes = new RouteCollection();
        $route = new Route('/extra', ['_controller' => 'App\Controller\ExtraController::index']);
        $routes->add('extra_route', $route);
        return $routes;
    }

    public function supports($resource, $type = null)
    {
        return $type === 'extra';
    }
}

Регистрируется как сервис с тегом routing.loader. В config/routes.yaml подключается: extra: resource: . type: extra. Позволяет генерировать маршруты программно, например, из базы данных.

// GET /extra -> ExtraController::index()

7. Маршруты с локализацией и переводом пути

Пример

// config/routes/translated.yaml
product_list_en:
    path: /{_locale}/products
    controller: App\Controller\ProductController::list
    requirements:
        _locale: en|fr

product_list_fr:
    path: /{_locale}/produits
    controller: App\Controller\ProductController::list
    requirements:
        _locale: fr

Для разных языков разные пути, ведущие к одному и тому же контроллеру. Лучше использовать пакет lunetics/locale-bundle или JMSTranslationBundle для автоматической генерации.

// /en/products -> controller
// /fr/produits -> controller (тот же)

Маршруты Symfony - comments

En
Php symfony routes (php)