Что такое Symfony controller и что он делает?
Symfony-контроллер — callable, принимающий Request и возвращающий Response; фреймворк внедряет зависимости через DI, разрешает аргументы через ArgumentResolver и обрабатывает результат в kernel.view.
Что такое Symfony Controller
Контроллер в Symfony — это PHP-класс (или callable), который принимает объект Request и возвращает объект Response. Фреймворк не навязывает базовый класс: можно наследоваться от AbstractController для удобных хелперов, а можно писать plain PHP-класс — главное соответствие контракту callable(Request): Response.
Место в Request Lifecycle
HttpKernel обрабатывает запрос в несколько этапов:
- kernel.request — firewall, locale listener, router читает URL и сохраняет атрибуты в
Request::$attributes. - kernel.controller — ControllerResolver разрешает callable по атрибуту
_controller; ArgumentResolver инжектирует аргументы (сервисы из DI, route-параметры, тело запроса через #[MapRequestPayload]). - Контроллер выполняется и возвращает
Responseили любое значение. - kernel.view — если вернули не Response (например, массив или DTO), ViewListener сериализует его.
- kernel.response — финальные модификации (кеш-заголовки, CORS и т.д.).
Базовый пример
<?php
// src/Controller/UserController.php
namespace App\Controller;
use App\Entity\User;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
class UserController extends AbstractController
{
#[Route('/users/{id}', name: 'user_show', methods: ['GET'])]
public function show(User $user): JsonResponse
{
// EntityValueResolver автоматически загружает User по {id}
return $this->json([
'id' => $user->getId(),
'email' => $user->getEmail(),
]);
}
}
Атрибут #[Route] регистрирует маршрут прямо на методе — альтернатива YAML/XML-конфигурации. AbstractController::json() использует Symfony Serializer (или json_encode как fallback) и устанавливает Content-Type: application/json.
Инъекция зависимостей в контроллер
<?php
use App\Repository\UserRepository;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class UserController extends AbstractController
{
public function __construct(
private readonly UserRepository $users,
) {}
#[Route('/users', methods: ['GET'])]
public function index(Request $request): Response
{
$page = $request->query->getInt('page', 1);
$users = $this->users->findPaginated($page, limit: 20);
return $this->json($users);
}
}
Symfony DI автоматически внедряет UserRepository через constructor autowiring — никакого new или Service Locator не нужно.
Валидация и обработка ошибок
<?php
use App\Dto\CreateUserDto;
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
use Symfony\Component\HttpFoundation\JsonResponse;
#[Route('/users', methods: ['POST'])]
public function create(
#[MapRequestPayload] CreateUserDto $dto,
): JsonResponse {
// $dto уже провалидирован через Validator + Symfony Constraints
// При ошибке Symfony автоматически вернёт 422 Unprocessable Entity
$user = $this->users->createFromDto($dto);
return $this->json($user, status: 201);
}
Подводные камни
- Толстый контроллер — бизнес-логика в контроллере затрудняет тестирование; выносите её в сервисы или command handlers.
- EntityValueResolver и 404 — если сущность не найдена, Symfony бросает
NotFoundHttpException; убедитесь, что это нужное поведение, иначе используйте Repository вручную. - Порядок маршрутов — при YAML/XML-конфигурации маршрут
/users/meдолжен стоять перед/users/{id}, иначе «me» будет интерпретировано как UUID/id. - Сериализация циклических ссылок —
$this->json()упадёт с CircularReferenceException при двусторонних связях Doctrine без@Ignoreили группы сериализации. - Отсутствие Response-типа — возврат массива без kernel.view listener вызовет LogicException; убедитесь, что установлен TwigBundle или SensioFrameworkExtraBundle.
- Тестирование — используйте
WebTestCase::createClient()и$client->request('GET', '/users/1'); не мокайте сам контроллер, мокайте зависимости. - AbstractController vs plain class —
AbstractControllerпредоставляет$this->json(),$this->render(),$this->getUser(), но тянет зависимость от FrameworkBundle; для библиотечного кода лучше plain class.
Common mistakes
- Сводить controllers к названию метода без lifecycle и failure path.
- Игнорировать модель runtime: Symfony 7/8 строит request lifecycle вокруг HttpKernel, events, routing, controller resolver и response listeners.
- Не отделять validation, authorization, transaction boundary и business logic.
What the interviewer is testing
- Объясняет controllers через конкретную точку lifecycle в Symfony.
- Приводит корректный минимальный пример без вымышленных методов или callbacks.
- Называет edge cases: пустые значения, ошибки, транзакции, безопасность или concurrency.