Наблюдая открыв рот за виртуозным фокусником и его красивыми помощницами, многие, однако, сосредоточенны совсем на другом: как он это делает? как там все устроенно внутри?

Тоже самое происходит и при работе с фреймворками, которые все делают за нас, но хочется знать, как они это делают, и в случае необходимости иметь возможность изменить поведение. К сожалению, документация, какой бы хорошей она не была (а у Symfony 2 она уже неплоха), рассказывает, как использовать всю эту «магию», но не раскрывает всей сути.
Эта статья — попытка разобраться, как происходит инициализация приложения и что же такое «Ядро Symfony2».
Для тех, кто не любит большие тексты, сразу прилагается краткая схема основных компонентов и небольшое описание происходящих действий.

1. Все запросы принимаются Фронт-контроллером (FrontController).
2. Настраивается автозагрузка классов (autoload).
3. Создается ядро в зависимости от окружения.
4. Запускается ядро.
1. Инициализируется список Бандлов (Bundles).
2. Создается Контейнер зависимостей (Dependency Injection Container).
1. Создается контейнер с основными параметрами.
2. Каждый бандл модифицирует (build) контейнер.
3. Загружается конфигурация приложения.
4. Контейнер компилируется.
1. Обрабатываются расширения.
2. Ссылки на параметры заменяются реальными значениями.
3. Контейнер переводится в режим только на чтение (frozen).
3. Запускаются бандлы.
В качестве фронт-контроллера выступают обычные скрипты. Примерами могут служить скрипты из стандартной поставки Symfony2.
Все эти фронт-контроллеры строятся по одному принципу:
Ядро — это класс, реализующий интерфейс KernelInterface, его задача — инициализация окружения. В основе ядра лежат два основных компонента: Dependency Injection контейнер (теорию можно почерпнуть, например, у Фаулера) и система бандлов. Бандл (bundle) — аналог плагина из Symfony 1.x. Подробнее о бандлах можно прочитать в официальной документации. Конечно, кроме непосредственно интерфейса ядра существует и стандартная абстрактная реализация Kernel, о которой в основном и пойдет речь дальше.
Код инициализации выглядит примерно следующим образом:
// init bundles $this->initializeBundles();
// init container $this->initializeContainer();
foreach ($this->getBundles() as $bundle) {
$bundle->setContainer($this->container);
$bundle->boot();
}Результатом данного этапа является полностью готовый к работе контейнер, переведенный в режим read-only. Делится этот процесс на 4 подэтапа:
public function registerContainerConfiguration(LoaderInterface $loader) {
$loader->load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml');
}*В теории компиляторов есть понятие «прохода», здесь подразумевается примерно то же самое, поэтому CompilerPass перевел именно так.
Как было отмечено выше, в процессе компиляции у каждого бандла есть возможность внести модификации в общий контейнер. Основным способом модификации является добавление проходов компилятора
На этапе компиляции контейнер выполняет серию проходов для приведения своего содержимого в конечное состояние. Проходы являются реализацией интерфейса CompilerPassInterface и бывают 6 видов (в порядке исполнения): merge, beforeOptimization, optimization, beforeRemoving, removing, afterRemoving — по умолчанию конфигурация компилятора уже содержит набор проходорв.
В бандлах CompilerPass чаще всего используется для обработки тегов в контейнере. Пример из стандартного TwigBundle:
$definition = $container->getDefinition('twig');
$calls = $definition->getMethodCalls();
$definition->setMethodCalls(array());
foreach ($container->findTaggedServiceIds('twig.extension') as $id => $attributes) {
$definition->addMethodCall('addExtension', array(new Reference($id)));
}
$definition->setMethodCalls(array_merge($definition->getMethodCalls(), $calls));Контейнер является центральным элементов системы, фактически, в нем сосредоточенна вся функциональность приложения, соответственно, чтобы добавить какую либо функциональность, нужно поместить в контейнер необходимые сервисы и параметры. Для удобства подобных модификаций существуют расширения контейнера (которые обрабатываются специальным проходом типа «merge», упомянутым выше).
Расширения — реализация интерфейса ExtensionInterface, а абстрактный класс Extension уменьшает рутинные действия и добавляет удобные методы. Работают обычно расширения следующим образом:
Пример конфигурации расширения (app/config/config.yml):
# Twig Configuration
twig:
debug: %kernel.debug%
strict_variables: %kernel.debug%
Эта конфигурация говорит, что нужно передать расширению (ExtensionInterface::load()) с именем twig (ExtensionInterface::getAlias()) параметры, определенные в соответствующей секции.
Подключить расширение бандла очень просто, нужно лишь создать класс с соответствующим именем (DependencyInjection\BundleNameExtension) и код метода Extension::build() сам подгрузит расширение (главное не забыть вызвать родительский метод и наследника ;) ).
И последний этап — запуск бандлов. На самом деле, большинство бандлов ничего не делают на данном этапе, он предназначен для выполнения действий, которые не могут быть выполнены контейнером. Например, основной бандл Symfony2 (FrameworkBundle) именно на этом этапе загружает кэш классов, уже не раз упомянутый в этой статье.
На этом процесс инициализации приложение Symfony2 можно считать завершенным: все подготовительные этапы выполнены, а контейнер содержит всю необходимую функциональность. Остается достать из контейнера нужный сервис и вызвать его методы для выполнения приложения.
Читатель, наверняка, обеспокоится довольно сложным и ресурсоемким процессом инициализации, но все не так страшно. Symfony2 наряду с гибкостью не забывает и о скорости, поэтому «из коробки» предоставляет множество решений по оптимизации. Так, например, весь процесс создания контейнера кэшируется путем создания класса, содержащего все настройки, и при следующем вызове (если это режим отладки) вместо загрузки множества конфигов различных параметров, работы компилятора контейнера и т.п. будет всего лишь загружен сгенерированный класс и сразу создан готовый объект контейнера.
На этом завершается первое погружение в дебри Symfony2. Если подобный тип статей о Symfony2 заинтересует читателей, то возможно превратить все это в цикл статей с разбором внутренностей фреймворка, отдельных компонентов и популярных бандлов. Кроме того, это моя первая статья, несмотря на долгое прибывание на Хабре. Я больше люблю говорить чем писать, но пытаюсь воспитывать в себе и такой способ изложения своих мыслей. Поэтому буду очень признателен за отзывы непосредственно о подаче материала: насколько сложно воспринимается; на что лучше делать больший акцент, на что меньший; возможно нужно больше иллюстраций и примеров кода; рассчитывать больше на тех, кто знаком с предметной областью, или стараться описать, чтобы было понятно всем и т.д.
Источник: 1
Комментарии