Контроллеры и маршруты в Symfony: короткое руководство
Разбираем, как связать URL с методом контроллера через атрибуты, получить данные из запроса и вернуть ответ. К концу у вас будет рабочий контроллер, отвечающий на GET и POST.

К концу этого руководства у вас будет контроллер, который отвечает на два маршрута — список и просмотр одной записи — и умеет читать параметры из URL и тела запроса. Займёт это минут двадцать, если проект уже создан. Всё написано для Symfony 7.x с PHP 8.2+; в 6.4 (LTS) атрибуты маршрутов работают так же.
Что нужно иметь до начала
- PHP 8.2 или новее (
php -vпокажет версию); - установленный Composer;
- созданный проект Symfony. Если проекта нет, соберите его через
composer create-project symfony/skeleton myapp, затем внутри папки поставьте web-профиль:composer require webapp; - пакет для генерации кода
composer require --dev symfony/maker-bundle— не обязателен, но экономит время; - атрибуты маршрутов из коробки требуют пакет
symfony/routing, который в webapp уже стоит.
Проверьте, что встроенный сервер запускается: symfony server:start или php -S 127.0.0.1:8000 -t public. В браузере по адресу http://127.0.0.1:8000 должна открыться стартовая страница Symfony.
Как маршрут находит контроллер
Symfony получает HTTP-запрос, компонент Routing сопоставляет путь и метод запроса с одним из маршрутов, а затем вызывает привязанный к нему метод контроллера. Маршрут можно объявить в YAML, но с версии 5.2 стандартный способ — атрибут #[Route] прямо над методом. Так объявление URL лежит рядом с кодом, который его обрабатывает.
Контроллер в Symfony — это просто callable, который принимаетRequestи возвращаетResponse. Всё остальное — сахар поверх этого правила.
Шаги
-
Создайте класс контроллера. Руками — файл
src/Controller/ArticleController.php, или командойphp bin/console make:controller ArticleController. Минимальный вид:<?php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Routing\Attribute\Route; class ArticleController extends AbstractController { // Маршрут отвечает только на GET по адресу /articles #[Route('/articles', name: 'article_list', methods: ['GET'])] public function list(): JsonResponse { return $this->json([ ['id' => 1, 'title' => 'Первая запись'], ['id' => 2, 'title' => 'Вторая запись'], ]); } }Проверка: откройте
http://127.0.0.1:8000/articles— в браузере появится JSON с массивом из двух объектов. -
Добавьте маршрут с параметром в пути. Фигурные скобки в шаблоне превращаются в аргумент метода с тем же именем:
// requirements ограничивает {id} только цифрами #[Route('/articles/{id}', name: 'article_show', methods: ['GET'], requirements: ['id' => '\d+'])] public function show(int $id): JsonResponse { return $this->json(['id' => $id, 'title' => 'Запись номер ' . $id]); }Проверка:
/articles/7вернёт объект сid: 7. Адрес/articles/abcиз-заrequirementsвернёт 404 — цифровое ограничение не пропустит буквы. -
Прочитайте данные из POST-запроса. Метод
Requestвнедряется как аргумент — Symfony подставит его автоматически:use Symfony\Component\HttpFoundation\Request; #[Route('/articles', name: 'article_create', methods: ['POST'])] public function create(Request $request): JsonResponse { // Для JSON-тела читаем через toArray(), доступно с Symfony 5.2 $data = $request->toArray(); $title = $data['title'] ?? null; if ($title === null) { return $this->json(['error' => 'Поле title обязательно'], 400); } return $this->json(['created' => true, 'title' => $title], 201); }Обратите внимание: путь
/articlesтеперь встречается дважды, но с разнымиmethods—GETдля списка иPOSTдля создания. Это нормально, Symfony различает их по HTTP-методу.Проверка: отправьте запрос через curl:
curl -X POST http://127.0.0.1:8000/articles \ -H "Content-Type: application/json" \ -d '{"title":"Черновик"}'Ответ придёт со статусом 201 и телом
{"created":true,"title":"Черновик"}. Пустое тело даст 400. -
Проверьте, что оба маршрута зарегистрировались. Команда покажет таблицу всех маршрутов проекта:
php bin/console debug:routerПроверка: в списке будут строки
article_list,article_showиarticle_createс колонками Method и Path. Если маршрута нет — контроллер не подхватился (см. раздел ниже).
Генерация URL по имени маршрута
Имя маршрута (name:) нужно, чтобы не хардкодить URL. Внутри контроллера ссылку на другой маршрут собирают так:
#[Route('/go', name: 'article_redirect', methods: ['GET'])]
public function go(): \Symfony\Component\HttpFoundation\RedirectResponse
{
// Второй аргумент — массив параметров маршрута
return $this->redirectToRoute('article_show', ['id' => 42]);
}
Метод generateUrl('article_show', ['id' => 42]) вернёт строку /articles/42 без редиректа — пригодится, когда URL нужен внутри JSON-ответа. Если поменяете путь в атрибуте #[Route], все ссылки по имени обновятся сами.
Если не получилось
- 404 «No route found for GET /articles» — контроллер не найден автоматически. Проверьте, что файл лежит в
src/Controller/и вconfig/routes/attributes.yaml(илиconfig/routes.yaml) есть загрузчик атрибутов с ресурсом../../src/Controller/. В стандартном webapp он уже настроен. - «Cannot autowire argument $request» — вы указали тип аргумента, который Symfony не умеет подставлять. Для тела запроса нужен именно
Symfony\Component\HttpFoundation\Request, а не что-то другое. Проверьтеuse. - «Attribute Route not found» или ошибка про класс атрибута — неверный
use. В Symfony 6.4+ класс лежит вSymfony\Component\Routing\Attribute\Route. В более старых версиях путь былSymfony\Component\Routing\Annotation\Route— если копируете чужой пример под 5.x, импорт может не совпасть. - «Syntax error» при
$request->toArray()— тело запроса не валидный JSON или заголовокContent-Type: application/jsonне отправлен. Метод бросает исключение на битом JSON, оборачивайте в try/catch, если данные приходят от внешнего клиента. - Изменения в маршрутах не видны — сбросьте кеш:
php bin/console cache:clear. В dev-окружении маршруты обычно перечитываются сами, но после смены окружения кеш стоит очистить руками.
Что делать дальше
Следующий логичный шаг — подключить Doctrine и заменить заглушки реальными записями из базы. Тогда вместо int $id в аргументе можно принимать сущность напрямую (EntityValueResolver подставит объект по id), а тело POST-запроса — разбирать через компонент Serializer с валидацией. Для форм и HTML-страниц вместо json() используйте render() с шаблоном Twig. Смотрите раздел Routing и Controller в официальной документации под вашу версию — API маршрутизации между 6.4 и 7.x совпадает, но мелкие атрибуты добавляются.
Репетитор 1—11 класс и ВУЗ: Математика и физика методом наведения на мысль, помощь с ДЗ.
Спросить за 8 ₽Источники: Symfony — Controller (официальная документация), Symfony — Routing (официальная документация)
Частые вопросы
У меня Symfony 5.4, импорт Route не работает — что делать?
Почему один и тот же путь /articles можно использовать в двух методах?
Что вернуть, если данные из toArray() приходят битым JSON?
Обязательно ли наследовать AbstractController?
Как ограничить параметр в URL только определёнными значениями?
Маршруты не подхватываются автоматически — где искать причину?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.