Что такое Symfony

Symfony — одновременно full-stack PHP-фреймворк и набор переиспользуемых компонентов. Это важное отличие: можно взять весь framework skeleton и получить routing, controllers, service container, config, profiler, console и security из коробки, а можно подключить один пакет вроде symfony/console, symfony/routing или symfony/http-foundation в проект без Symfony-приложения. Поэтому Symfony стоит читать как мост между чистым PHP, Composer, Packagist и composer.json, Autoloading и PSR-4, HTTP-слоем и фреймворками поверх этих соглашений.

На практике Symfony часто выбирают там, где нужны явные границы, долгий срок жизни проекта, сильная инфраструктура компонентов и предсказуемая архитектура. Laravel делает ставку на цельный developer experience и «framework-way»; Symfony чаще ощущается как конструктор: больше конфигурации и понятий, зато легче брать только нужные части и строить систему вокруг них.

flowchart TD A[HTTP request] --> B[Front controller / Kernel] B --> C[HttpFoundation Request] C --> D[Routing] D --> E[Controller] E --> F[Service container] F --> G[Application services] E --> H[EventDispatcher] E --> I[Messenger bus] I --> J[Sync handler или queue transport] E --> K[HttpFoundation Response] K --> L[HTTP response]
flowchart TD
    A[HTTP request] --> B[Front controller / Kernel]
    B --> C[HttpFoundation Request]
    C --> D[Routing]
    D --> E[Controller]
    E --> F[Service container]
    F --> G[Application services]
    E --> H[EventDispatcher]
    E --> I[Messenger bus]
    I --> J[Sync handler или queue transport]
    E --> K[HttpFoundation Response]
    K --> L[HTTP response]
Типичный путь запроса в Symfony: HttpFoundation моделирует request/response, Routing выбирает controller, container подставляет зависимости, а события и Messenger подключают побочные процессы.
Quick recall
Почему Symfony можно использовать даже в проекте, который не является Symfony-приложением?

HttpFoundation, Routing и контроллеры

Базовая web-модель Symfony строится вокруг Request и Response из компонента HttpFoundation. Request::createFromGlobals() собирает данные из суперглобалов, query string, body, cookies, uploaded files и server parameters в объект. Response хранит body, status code и headers. Это та же предметная область, что в SAPI и суперглобалы, GET, POST и фильтрация ввода и HTTP-заголовки, ответы и редиректы, только с объектной оболочкой.

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

$request = Request::createFromGlobals();
$name = $request->query->get('name', 'мир');

$response = new Response("Привет, {$name}", Response::HTTP_OK);
$response->headers->set('Content-Type', 'text/plain; charset=UTF-8');
$response->send();

Routing связывает URL с controller action. В современных Symfony-приложениях часто используют PHP attributes, хотя YAML, XML и PHP-конфиги тоже поддерживаются. Controller обычно возвращает Response или значение, которое framework умеет преобразовать в response.

namespace App\Controller;

use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;

final class HealthController
{
    #[Route('/health', name: 'health', methods: ['GET'])]
    public function __invoke(): JsonResponse
    {
        return new JsonResponse(['ok' => true]);
    }
}

Symfony исторически использует HttpFoundation, а не PSR-7 как основной request/response API. Это не конфликт: для совместимости есть bridge-пакеты, а сама идея стандартизированных HTTP-сообщений подробно относится к PSR-7, middleware и HTTP-клиенты. Если библиотека требует Psr\Http\Message\ServerRequestInterface, обычно нужен адаптер, а не переписывание всего приложения.

Quick recall
Что выведет этот минимальный HttpFoundation-код при запросе `/hello?name=Анна`? ```php $request = Request::createFromGlobals(); $name = $request->query->get('name', 'мир'); $response = new Response("Привет, {$name}", Response::HTTP_OK); $response->send(); ```

Service container и dependency injection

Service container — центральная часть Symfony. Он знает, какие классы являются сервисами, как создавать их зависимости и какие настройки подставлять. В типовом config/services.yaml включают autowiring и autoconfiguration: Symfony сам читает type declarations конструктора и регистрирует классы из src/ как сервисы.

# config/services.yaml
services:
    _defaults:
        autowire: true
        autoconfigure: true

    App\:
        resource: '../src/'

После этого controller, command или handler может требовать зависимость явно:

namespace App\Controller;

use App\Service\ReportRenderer;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

final class ReportController
{
    public function __construct(
        private ReportRenderer $renderer,
    ) {}

    #[Route('/reports/{id}', methods: ['GET'])]
    public function show(int $id): Response
    {
        return new Response($this->renderer->renderHtml($id));
    }
}

Важная деталь: container — не глобальный ящик, из которого доменный код должен доставать всё подряд. Хороший Symfony-код обычно получает зависимости через constructor injection. Если нужен интерфейс, несколько реализаций или параметр из env/config, binding задают явно. Это хорошо ложится на темы Классы, объекты и видимость, Неймспейсы и use и PHPDoc, generics и статический анализ: зависимости видны в сигнатурах, а не спрятаны в static-вызовах.

Quick recall
Что позволяет Symfony container-у автоматически подставить `ReportRenderer` в constructor controller-а? ```yaml services: _defaults: autowire: true autoconfigure: true App\: resource: '../src/' ```

EventDispatcher, Console и Messenger

EventDispatcher даёт способ ослабить связность между частями приложения. Один код dispatch-ит событие, другой слушает его и реагирует. Внутри самого framework есть kernel events вокруг request lifecycle; в приложении события часто используют для side effects: логирования, уведомлений, аудита, обновления read model.

namespace App\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ResponseEvent;

final class SecurityHeadersSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [ResponseEvent::class => 'onResponse'];
    }

    public function onResponse(ResponseEvent $event): void
    {
        $event->getResponse()->headers->set('X-Frame-Options', 'DENY');
    }
}

Console — отдельный компонент для CLI-команд. Он используется в Symfony-приложениях, Composer-пакетах и самостоятельных инструментах. Команды удобно применять для migrations, импорта данных, обслуживания очередей, cron-задач и внутренних админских операций.

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Style\SymfonyStyle;

#[AsCommand(name: 'app:reindex')]
final class ReindexCommand
{
    public function __invoke(SymfonyStyle $io): int
    {
        $io->success('Индекс обновлён');
        return Command::SUCCESS;
    }
}

Messenger добавляет message bus: объект-сообщение отправляется в bus, а handler обрабатывает его синхронно или через transport — например, очередь. Это смежно с Очереди, фоновые задачи и воркеры: как только handler уезжает в worker, появляются retries, timeouts, failed messages, идемпотентность и мониторинг.

final readonly class SendWelcomeEmail
{
    public function __construct(public int $userId) {}
}

final class SendWelcomeEmailHandler
{
    public function __invoke(SendWelcomeEmail $message): void
    {
        // загрузить пользователя и отправить письмо
    }
}

Flex, bundles и экосистема

Symfony Flex — Composer plugin, который применяет recipes: добавляет конфиги, файлы, env-переменные и registration для пакетов. Поэтому composer require symfony/mailer или composer require symfony/messenger в Symfony-приложении обычно делает больше, чем просто кладёт пакет в vendor/: проект получает готовую минимальную интеграцию.

Bundles — способ подключать функциональность к Symfony-приложению. FrameworkBundle, SecurityBundle, TwigBundle, DoctrineBundle, MonologBundle и другие bundles связывают компоненты с application kernel и конфигурацией. На уровне идеи bundle — модуль интеграции, а component — переиспользуемая библиотека. Эту границу полезно держать в голове: symfony/console можно использовать где угодно, а bundle обычно рассчитан на Symfony-приложение.

Где Symfony особенно уместен

Symfony хорошо подходит для больших B2B-систем, API, админок, монолитов с долгим жизненным циклом, проектов с сильными требованиями к тестируемости и команд, которым важна явная архитектура. Он также удобен как источник отдельных компонентов: многие PHP-проекты используют Console, Finder, Process, HttpFoundation, EventDispatcher или DependencyInjection без полного Symfony framework.

Главный риск Symfony — переусложнить маленький проект. Если приложение — два endpoint-а и простой middleware pipeline, может быть достаточно Slim и Mezzio. Если команда хочет максимально цельный full-stack путь с большим количеством готовых продуктовых пакетов, стоит сравнить с Laravel. А если проект выбирается не по вкусу, а по ограничениям бизнеса, легаси, найма и поддержки, лучше смотреть общую статью Выбор PHP-фреймворка.

См. также

Sources

  1. Symfony Packages
  2. Symfony Docs: Service Container
  3. Symfony Docs: Routing
  4. Symfony Docs: HttpFoundation Component
  5. Symfony Docs: Messenger
  6. Symfony Docs: Flex
  7. PHP-FIG: PSR-7 HTTP Message Interfaces