SymfonyJuniorTechnical
Как работает маршрутизация (routing) в Symfony и какими способами можно определять маршруты?
Маршруты в Symfony определяются через PHP-атрибуты #[Route] (рекомендуется), YAML или PHP-файл конфигурации; роутер сопоставляет URL с контроллером по пути, HTTP-методу и requirements-паттернам, а URL генерируется через generateUrl() или Twig-функцию path().
Маршрутизация в Symfony
Роутинг — процесс сопоставления входящего HTTP-запроса с конкретным контроллером и его методом. Symfony использует компонент Routing, который работает через RouterListener на событии kernel.request.
Способы определения маршрутов
1. PHP-атрибуты (рекомендуется с Symfony 6)
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
#[Route('/blog', name: 'blog_')]
class BlogController extends AbstractController
{
// GET /blog/
#[Route('/', name: 'index')]
public function index(): Response
{
return $this->render('blog/index.html.twig');
}
// GET /blog/42 или /blog/my-post — параметр с типом и дефолтом
#[Route('/{slug}', name: 'show', requirements: ['slug' => '[a-z0-9-]+'])]
public function show(string $slug): Response
{
return $this->render('blog/show.html.twig', ['slug' => $slug]);
}
// POST /blog/ — ограничение по методу
#[Route('/', name: 'create', methods: ['POST'])]
public function create(): Response
{
// ...
return $this->redirectToRoute('blog_index');
}
// /blog/{id} где id = целое число
#[Route('/{id}', name: 'delete', requirements: ['id' => '\d+'], methods: ['DELETE'])]
public function delete(int $id): Response
{
return new Response(null, Response::HTTP_NO_CONTENT);
}
}
2. YAML-конфигурация
# config/routes.yaml
blog_index:
path: /blog/
controller: App\Controller\BlogController::index
methods: [GET]
blog_show:
path: /blog/{slug}
controller: App\Controller\BlogController::show
requirements:
slug: '[a-z0-9-]+'
defaults:
slug: 'latest'
# Импорт маршрутов из контроллеров
blog:
resource: '../src/Controller/BlogController.php'
type: attribute
prefix: /blog
3. PHP-файл (routes.php)
<?php
// config/routes.php
use App\Controller\BlogController;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return function (RoutingConfigurator $routes): void {
$routes->add('blog_index', '/blog/')
->controller([BlogController::class, 'index'])
->methods(['GET']);
$routes->add('blog_show', '/blog/{slug}')
->controller([BlogController::class, 'show'])
->requirements(['slug' => '[a-z0-9-]+'])
->defaults(['slug' => 'latest']);
};
Генерация URL
<?php
// В контроллере
$url = $this->generateUrl('blog_show', ['slug' => 'hello-world']);
// Результат: /blog/hello-world
// Абсолютный URL
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
$absoluteUrl = $this->generateUrl('blog_show', ['slug' => 'hello'], UrlGeneratorInterface::ABSOLUTE_URL);
// Результат: https://example.com/blog/hello
// В Twig-шаблоне
<a href="{{ path('blog_show', {slug: 'hello-world'}) }}">Читать</a>
<a href="{{ url('blog_show', {slug: 'hello-world'}) }}">Абсолютная ссылка</a>
Отладка маршрутов
# Список всех маршрутов
php bin/console debug:router
# Детали конкретного маршрута
php bin/console debug:router blog_show
# Проверить, какой маршрут соответствует URL
php bin/console router:match /blog/hello-world
Подводные камни
- Порядок маршрутов имеет значение: первый подходящий маршрут побеждает. Маршрут
/blog/newдолжен быть выше/blog/{slug}, иначе «new» попадёт в параметр slug. - Требования (requirements) для параметров — это регулярные выражения; забытые границы \d+ совпадут с «123abc» частично — используйте \d+ только если Symfony сам добавляет ^ и $.
- При импорте маршрутов из аннотаций/атрибутов через
resourceс указанием директории кэш компилируется один раз — после добавления нового контроллера нуженcache:clearв production. - Метод generateUrl() бросает исключение если маршрут не найден по имени или не хватает обязательных параметров — всегда проверяйте имена маршрутов при переименовании.
- Параметры маршрута с одинаковым именем в родительском #[Route] и дочернем #[Route] вызывают конфликт — не дублируйте параметры в префиксах контроллера.
- Маршруты с методом DELETE/PUT недоступны из браузерных форм HTML; используйте скрытое поле
_methodи middleware HttpMethodOverride или делайте это через JavaScript fetch.
Common mistakes
- Сводить routing к названию метода без 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
- Объясняет routing через конкретную точку lifecycle в Symfony.
- Приводит корректный минимальный пример без вымышленных методов или callbacks.
- Называет edge cases: пустые значения, ошибки, транзакции, безопасность или concurrency.