Шаг 30: Изучение внутренностей Symfony

5.0 version
Maintained

Изучение внутренностей 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_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_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.