Шаг 30: Изучение внутренностей Symfony
Изучение внутренностей Symfony¶
Мы уже достаточно долгое время используем Symfony и разработали внушительное приложение, при этом большая часть выполняемого кода является самим Symfony. Несколько сотен строк нашего кода и тысячи фреймворка.
Мне нравится узнавать, как всё работает изнутри. Я всегда был очарован инструментами, помогающими мне докопаться до сути. Первый раз, когда я использовал пошаговый отладчик или когда открыл для себя ptrace
— это волшебные воспоминания.
Хотите лучше понять, как работает Symfony? Тогда самое время разобраться, что происходит под капотом вашего приложения. Вместо объяснения довольно скучной обработки HTTP-запроса с теоретической точки зрения, мы будем использовать Blackfire, чтобы наглядно посмотреть как всё работает, а также познакомиться с продвинутыми темами.
Разбираемся во внутренностях Symfony и Blackfire¶
Вы уже знаете, что все HTTP-запросы обрабатываются в одной точке входа — в файле public/index.php
. Но что происходит потом? Как вызываются контроллеры?
Давайте спрофилируем английскую главную страницу на продакшене с помощью браузерного расширения Blackfire:
1 | $ symfony remote:open
|
Или непосредственно через командную строку:
1 | $ blackfire curl `symfony env:urls --first`en/
|
Откройте вкладку «Timeline» в профилировщике и вы увидите что-то похожее на это:

Наведите курсор на цветные полосы, расположенные на временной шкале, чтобы получить больше информации о каждом вызове; вы узнаете много нового о том, как работает Symfony:
- Основной точкой входа является файл
public/index.php
; - Метод
Kernel::handle()
обрабатывает запрос; - Он вызывает
HttpKernel
, который отправляет несколько событий; - Первым создаётся событие
RequestEvent
; - Вызывается метод
ControllerResolver::getController()
для определения, какой контроллер должен обрабатывать входящий URL-адрес; - Вызывается метод
ControllerResolver::getArguments()
для определения того, какие аргументы нужно передать контроллеру (для этого используется преобразователь параметров); - Вызывается метод
ConferenceController::index()
и большая часть нашего кода выполняется этим вызовом; - Метод
ConferenceRepository::findAll()
получает все конференции из базы данных (обратите внимание на соединение с базой данных с помощьюPDO::__construct()
); - Метод
Twig\Environment::render()
отображает шаблон; - Довольно быстро выполнились
ResponseEvent
иFinishRequestEvent
, но, видимо, из-за того, что нет обработчиков этих событий.
Временная шкала — отличный способ понять, как работает код; она крайне полезна в случае унаследованного проекта, разработанного кем-то другим.
Теперь профилируйте ту же страницу с локального компьютера в окружении разработки:
1 | $ blackfire curl `symfony var:export SYMFONY_PROJECT_DEFAULT_ROUTE_URL`en/
|
Откройте профилировщик. Вас перенаправит на граф вызовов, так как запрос очень быстро выполнился, то и временная шкала будет совершенно пустой:

Вы понимаете, что происходит сейчас? У нас включено HTTP-кеширование, поэтому мы профилируем кеширующий слой Symfony HTTP. А так как страница закеширована, вызов HttpCache\Store::restoreResponse()
получает HTTP-ответ из кеша, следовательно контроллер никогда не будет вызван.
Отключите кеширующий слой в public/index.php
, как мы делали это в предыдущем шаге, и попробуйте снова профилировать код. Вы сразу увидите, что результат будет совершенно другим:

Основные различия заключаются в следующем:
- Обработка события
TerminateEvent
, которая не была видна в продакшене, выполняется довольно много времени; При внимательном рассмотрении, вы увидите, что это событие отвечает за хранение данных профилировщика Symfony, собранных во время запроса; - Под вызовом
ConferenceController::index()
обратите внимание на методSubRequestHandler::handle()
, который отображает ESI (поэтому у нас два вызоваProfiler::saveProfile()
: один для основного запроса, второй — для ESI).
Изучите временную шкалу, чтобы узнать больше о выполненном коде. Затем переключитесь на граф вызовов для получения другого представления данных.
Как мы только что выяснили, код, выполняемый при разработке и в продакшене, совершенно разный. В окружении разработки код работает медленнее, так как профилировщик Symfony пытается собрать больше данных, чтобы облегчить отладку. Поэтому профилировать код нужно всегда в продакшен-окружении, даже локально.
Проведите несколько интересных экспериментов: профилируйте страницу с ошибкой, страницу с путем``/`` (которая является редиректом) или ресурс API. Каждое профилирование расскажет вам немного больше о том, как работает Symfony, какой класс или метод вызывается, что работает долго, а что — быстро.
Использование отладчика Blackfire Debug Addon¶
По умолчанию Blackfire удаляет вызовы незначительных методов, чтобы не создавать лишней нагрузки и больших графиков. При использовании Blackfire в качестве инструмента отладки лучше сохранять все вызовы. Для этого воспользуйтесь дополнением отладки.
В командной строке используйте флаг --debug
:
1 2 | $ blackfire --debug curl `symfony var:export SYMFONY_PROJECT_DEFAULT_ROUTE_URL`en/
$ blackfire --debug curl `symfony env:urls --first`en/
|
В продакшене вы увидите, например, загрузку файла .env.local.php
:

Откуда он взялся? SymfonyCloud выполняет оптимизацию при развёртывании приложения Symfony, например, оптимизирует автозагрузчик Composer (--optimize-autoloader --apcu-autoloader --classmap-authoritative
). Также, чтобы не парсить файл .env
с переменными окружения при каждом запросе, сгенерируем файл .env.local.php
:
1 | $ symfony run composer dump-env prod
|
Blackfire — очень производительный инструмент, который помогает понять, как выполняется PHP-код. Улучшение производительности — это только один из способов использования профилировщика.
- « Previous Шаг 29: Оптимизация производительности
- Next » Что дальше?
This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.