Пользователь
0,0
рейтинг
13 апреля 2011 в 12:42

Разработка → Приложение Symfony2 в разрезе

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


Тоже самое происходит и при работе с фреймворками, которые все делают за нас, но хочется знать, как они это делают, и в случае необходимости иметь возможность изменить поведение. К сожалению, документация, какой бы хорошей она не была (а у 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. Запускаются бандлы.


FrontController


В качестве фронт-контроллера выступают обычные скрипты. Примерами могут служить скрипты из стандартной поставки Symfony2.
  • app/console — доступ к приложению через CLI.
  • web/app.php — доступ к приложению через web.
  • web/app_dev.php — доступ к приложению через web в режиме разработки.

Все эти фронт-контроллеры строятся по одному принципу:
  • настраивается autoload путем подключения скрипта с регистрацией загрузчика классов;
  • иногда используется кэш классов (часто используемые классы, собранные в 1 файл);
  • запускается Ядро;


Ядро (Kernel)


Ядро — это класс, реализующий интерфейс 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();
   }

  • (4.1) initializeBundles — собирает информацию о бандлах (тех самых, что регистрирует разработчик для своего приложения через KernelInterface::registerBundles, см. пример из стандартной поставки);
  • (4.2) initializeContainer — создает DI контейнер и заполняет его данными;
  • (4.3) запускается каждый из бандлов.

Инициализация контейнера


Результатом данного этапа является полностью готовый к работе контейнер, переведенный в режим read-only. Делится этот процесс на 4 подэтапа:
  • Создается контейнер (ContainerBuilder) и туда помещаются стандартные параметры ядра: пути до основных директорий, параметры окружения (environment, debug) и другие.
  • Для каждого бандла запускается build, на этом этапе бандлам предоставляется возможность модификации контейнера
  • Добавляется конфигурация, определенная разработчиком (через registerContainerConfiguration ядра). В стандартной поставке этот метод выглядит очень просто:
        public function registerContainerConfiguration(LoaderInterface $loader)
        {
            $loader->load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml');
        }
    
  • И наконец, контейнер компилируется (compile): подставляются реальные значения параметров; выполняются «проходы компилятора»* (CompilerPass), именно они выполняют модификации, добавленные бандлами (подробнее о них далее); контейнер «замораживается», т.е. переходит в режим только на чтение, после чего любая попытка его изменения вызовет исключение.
*В теории компиляторов есть понятие «прохода», здесь подразумевается примерно то же самое, поэтому CompilerPass перевел именно так.

Модификация контейнера бандлами


Как было отмечено выше, в процессе компиляции у каждого бандла есть возможность внести модификации в общий контейнер. Основным способом модификации является добавление проходов компилятора

Compiler Pass


На этапе компиляции контейнер выполняет серию проходов для приведения своего содержимого в конечное состояние. Проходы являются реализацией интерфейса CompilerPassInterface и бывают 6 видов (в порядке исполнения): merge, beforeOptimization, optimization, beforeRemoving, removing, afterRemoving — по умолчанию конфигурация компилятора уже содержит набор проходорв.
  • before*/after* — лишь «хуки» для пользовательских «проходов» и не используются стандартными (добавленными по умолчанию);
  • merge — обрабатывает расширения (extensions) контейнера;
  • optimization — оптимизируют контейнер, примерами могут служить: ResolveInterfaceInjectorsPass (преобразует интерфейсные инъекции), CheckCircularReferencesPass (проверяет циклические ссылки в контейнере)
  • removing — удаляют неиспользуемые сервисы, например, RemoveUnusedDefinitionsPass (удаляет неиспользуемые приватные сервисы)

В бандлах 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 уменьшает рутинные действия и добавляет удобные методы. Работают обычно расширения следующим образом:
  • загружаются определения сервисов и параметров по умолчанию из конфига расширения (чаще всего xml файл);
  • обрабатывается конфиг расширения, определенный для приложения, переписывая значения по умолчанию;
  • регистрируются классы для сборки в один файл (тот самый кэш классов в одном файле, упомянутый во фронт-контроллере)

Пример конфигурации расширения (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 заинтересует читателей, то возможно превратить все это в цикл статей с разбором внутренностей фреймворка, отдельных компонентов и популярных бандлов. Кроме того, это моя первая статья, несмотря на долгое прибывание на Хабре. Я больше люблю говорить чем писать, но пытаюсь воспитывать в себе и такой способ изложения своих мыслей. Поэтому буду очень признателен за отзывы непосредственно о подаче материала: насколько сложно воспринимается; на что лучше делать больший акцент, на что меньший; возможно нужно больше иллюстраций и примеров кода; рассчитывать больше на тех, кто знаком с предметной областью, или стараться описать, чтобы было понятно всем и т.д.
Kirill chEbba Chebunin @chEbba
карма
44,7
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (21)

  • +77
    Благодаря вам я наконец то узнал как делается этот фокус с разрезанием человека :)
    • +14
      ага, про симфони то мы итак давно знали, а вот это…: )
    • +3
      Аналогично. Всю жизнь не понимал: как? Теперь знаю. Остальные фокусы были более менее понятными… )
  • +11
    а я думал там пластмассовые ноги :[
  • 0
    Прочитал. Подумал, если не смог понять всего этого с первого раза (а не понял) — зачем мне такой фрэйм?
    • 0
      не знаю как вы, а я степень применимости/удобности фреймворка всегда определял после изучения его исходников, а не после прочтения отдельно взятого мануала
    • +1
      На самом деле там логика простая: есть бандлы (аналог модулей), при инициализации приложения они расширяют DI контейнер. Бандлы связываются только через DI — в основном полностью независимы — свои конфиги, MVC классы. Непонятно потому, что только альфа версия. 2 месяца назад обещали бету… решили не спешить в ущерб архитектуре. Ну и сам фреймворк сложнее.

      Мне сейчас нужно будет сделать небольшой магазин — решил делать на Kohana 3.1, а реальные проекты на sf2 буду начинать после выпуска беты, а сейчас как раз время изучать исходники.
    • +3
      Вам не нужно все это понимать, чтобы успешно им пользоваться.
  • –1
    symfony 1.4 — отличный фреймворк. С удовольствием изучу статьи по его второй версии.
  • +5
    Имхо в данном случае цикл статей нужно начинать с краткого рассказа о конкретных компонентах / используемых технологиях, прежде чем переходить к общей архитектуре.

    Многие не имеют малейшего понятия, что такое сервис, сервисный контейнер, Dependency Injection, никогда не использовали билд/компиляцию в пхп, не поймут что такое экстеншны и зачем нужны бандлы, спутают фронт-контроллер с mvc-контроллером, итд. Да что там далеко ходить, многие могли успешно писать на первой симфони, не имея понятия что такое неймспейсы или интерфейсы.
    Из своего опыта объяснения симфони2 — рассказывать надо в последовательности Сервисы — Бандлы — Конфигурация — MVC — и только потом, как оно все умудряется работать вместе.

    А продвинутые кодеры и сами разберутся с архитектурой, им объяснять надо преимущества на реальных задачах.
    • +4
      Спасибо за отзыв.
      Материал, описанный в мануалах и других источниках, не хотел рассказывать осознанно. Поэтому статья скорее рассчитана на тех кто уже щупал или имеет желание разбираться (поэтому в статье довольно много ссылок). Да и вообще, хочется рассказывать о том, о чем не прочитаешь в других источниках.
      Но все равно вам +.
  • +2
    Хоть и на офф.сайте документации предостаточно, тем не менее:
    — Автор, продолжайте в том же духе.
    — Хотелось бы увидеть статьи с примерами создания приложений(бандлов), примеры настройки кэша и тесты поедания памяти php и opcode cache'м
    • +1
      написать статью с примером создания приложения довольно внушительный труд,
      вот тут версия jobeet(пока только 4 дня) для sf2.0

      автору на статью респект!
  • +1
    Первая картинка меня немного шокировала) я сначала думал это приложения где рассмотрены позы… кхм… :[
  • 0
    Спасибо вам. После вашей статьи я наконец-таки поставлю себе вторую симфони и начну ее ковырять. И да, вы продолжайте, читать интересно.
  • –4
    код обрабатывается каждый раз при запросе? Бяка
    и вообще rails решение на все случаи
    • 0
      В заключении есть ответ на этот вопрос, если коротко, то «нет».
    • 0
      Симфони 1.4 многое взяла из рельс, а симфони2 кое в чём рельсы и обошла.

      И решение на рельсах не подойдёт, как минимум, там где нет руби.
  • 0
    Кирилл, я вижу ты тоже подсел на Symfony2 :)
    Есть проект живой или пока теория?
    • 0
      Сейчас в разработке пару проектов, поэтому много ковыряюсь во внутренностях sf2. Вот и решил поделиться с остальными =)
  • 0
    Использую symfony2 в одном из проектов.

    Для меня статья была полезна. Большое спасибо автору. Пиши ещё.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.