company_banner

PHP-фреймворк Badoo

    Код нашего сайта повидал уже не одну версию PHP. Он неоднократно дополнялся, переписывался, модифицировался, рефакторился — в общем, жил и развивался своей жизнью. В это время в мире появлялись и исчезали новые best practice, подходы, фреймворки и тому подобные явления, облегчающие жизнь разработчику и готовые решить все основные проблемы, возникающие в процессе создания веб-сайтов.
    В этой статье мы расскажем о нашем пути: как был организован код изначально, какие возникали проблемы и как появился текущий фреймворк.

    Что было


    Проект начали делать еще в 2005 году. Тогда никаких жестких правил по написанию кода и четко структурированного фреймворка не было. Код писали несколько разработчиков, они легко в нем ориентировались и его поддерживали, каждый привносил что-то свое. В то время известные сейчас фреймворки только создавались, поэтому примеров для подражания было мало. Так что можно сказать, что наш фреймворк образовался стихийно.

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

    Были пакеты — наборы классов, к которым обращались страницы, чтобы получить данные или обработать каким-то образом. Были представления, которые отвечали за шаблонизацию и вывод. В обычном случае каждая страница получала какие-то данные, передавала их в класс View, который расставлял их в структуру blitz-шаблона и выводил. Так сложилось, что для каждой страницы был свой шаблон (не было базового), и отличался он набором подключаемых скриптов, стилей и центральной частью.

    В принципе, это выглядело как обычная MVC-подобная схема. Но без четкой организации кода и с ростом количества разработчиков такой код стало все сложнее поддерживать.

    Что же, собственно, нас не устраивало и нужно было улучшить?

    1. Использование глобального контекста и статичных переменных.
    С одной стороны, это удобно, когда можно из любой точки кода получить глобальный объект. С другой — он становится зависимым, повышается связанность. С началом unit-тестирования мы поняли, что такой код ужасно тяжело тестировать: первый тест легко ломает следующий, за этим необходимо очень жестко следить. К тому же код, который использует глобальные объекты, а не имеет лишь вход и выход, требует много mock-объектов для тестирования.

    2. Большая связанность контроллеров с представлениями.
    Подготовка данных зачастую происходила под конкретный View, т. е. под конкретный шаблон. Целые иерархии контроллеров, наследуемых друг от друга, по частям собирают данные для blitz-шаблона. Поддержка такого кода крайне затруднительна. Создание версии с абсолютно другим шаблоном (например, мобильной версии) становилась порой почти неосуществимой задачей, поэтому было проще написать все с нуля.

    3. Использование публичных свойств как норма.
    Изначально PHP не поддерживал приватные свойства объектов. Поскольку наш код имеет достаточно большую историю, то в нем осталось много мест, где свойства объявляются через var, и много кода, который использует это. Вполне нормально встретить объект, который передается в другой объект, а тот что-то устанавливает или производит какие-то манипуляции со свойствами первого. Такой код очень сложен в понимании и отладке. В идеале нужно всегда делать геттеры и сеттеры для свойств классов — это сэкономит уйму времени и нервов вам и вашим коллегам!

    4. Ассоциативные массивы как контейнер для передачи параметров.
    Большой проблемой для нас стало то, что данные, полученные из одного источника, переносятся в какой-нибудь обработчик или контроллер, а по пути туда может быть дописано что угодно и в неограниченном количестве. В итоге все это постоянно обрастает новыми параметрами и в таком виде отправляется в класс View. Хотя лучше было бы использовать какую-нибудь типизацию или интерфейс, чтобы избежать хаоса.

    5. Отсутствие единой точки входа.
    Каждая страница — это отдельный php-файл, содержащий класс, наследуемый от базового. Если входные данные в такой схеме контролировать в одном месте возможно, то на выходе сделать что-то массово будет уже крайне сложно. Заведение маршрута, отличного от имени папки, файла или содержащего переменные, требует правки конфигов nginx. А это усложняет тестирование в стандартном рабочем процессе, требует предоставления дополнительного доступа и сложнее поддерживается при большом количестве разработчиков.

    Новые задачи для фреймворка


    Естественно, мы хотели решить большинство вышеперечисленных проблем. Мы хотели иметь возможность «из коробки» отображать одни и те же данные в разном представлении (JSON, мобильной или веб-версии). До этого задача решалась только набором IF-ов в каждом конкретном случае.

    Безусловно, новый фреймворк должен был быть достаточно легко совместим со старым кодом, а не вызывать у разработчиков сложностей при переходе. Именно поэтому мы не стали переходить на популярные фреймворки, а лишь воспользовались некоторыми идеями из них. Гораздо сложнее было бы переучить всех писать под какой-то конкретный популярный фреймворк. К тому же у нас много кода, заточенных под нас стандартных компонентов, которые есть в любом фреймворке, и пришлось бы проводить не самую простую интеграцию.

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

    Какова архитектура фреймворка?


    На основе глобальных переменных окружения создается объект Request, который передается приложению для получения ответа.

    $Request = new Request($_GET, $_POST, $_COOKIE, $_SERVER);
    $App = new Application();
    $Response = $App->handle($Request);
    $Response->send();
    

    В процессе работы приложение генерирует события «Получен запрос», «Найден контроллер», «Получены данные», «Поймано исключение», «Рендеринг данных», «Получен ответ».

    $Dispatcher = new EventDispatcher();
    $Dispatcher->setListeners($Request->getProject()->loadListeners());
    $RequestEvent = new Event_ApplicationRequest($Request); 
    $Dispatcher->dispatch($RequestEvent);
    

    На каждое событие есть набор подписчиков, которые реагируют специальным образом. Например, Listener_Router по клиенту (в основном по HTTP_USER_AGENT) и значению REQUEST_URI находит контроллер (например, Controller_Mobile_Index) и устанавливает его в объект события. После диспетчеризации этого события приложение либо вызывает найденный контроллер, либо кидает исключение Exception_HttpNotFound, которое будет выведено как ответ сервера 404. Пример списка подписчиков:

    $listeners = [
       \Framework\Event_ApplicationRequest::class => [
           [\Framework\Listener_Platform::class, 'onApplicationRequest'],
           [\Framework\Listener_Client::class, 'onApplicationRequest'],
           [\Framework\Listener_Router::class, 'onApplicationRequest'],
       ],
    ];
    

    Каждый контроллер представляет из себя отдельный класс с набором методов — action-ов. Фреймворк находит по карте маршрутов соответствующий класс и метод, создает объект Action (для удобства, вместо callable-массива). Пример карты маршрутов:

    $routes = [
       Routes::PAGE_INDEX => [
           'path' => '/',
           'action' => [Controller_Index::class, 'actionIndex'],
       ],
       Routes::PAGE_PROFILE => [
           'path' => '/profile/{user_login}',
           'action' => [Controller_Profile::class, 'actionProfile'],
       ],
    ];
    

    В массиве маршрутов указаны базовые классы. Если класс имеет наследника под текущего клиента, то будет использован именно он. Имена маршрутов в константах позволяют удобным образом генерировать URL-ы в любом месте проекта.

    Далее происходит диспетчеризация события «Найден контроллер». Поведением подписчиков этого события можно управлять из контроллера.

    $ActionEvent = new Event_ApplicationAction($Request, $RequestEvent->getAction()); 
    $Dispatcher->dispatch($ActionEvent);
    

    Например, для всех контроллеров, к которым обращается JavaScript, мы автоматом проверяем request-token для защиты от CSRF-уязвимостей. За это отвечает отдельный класс Listener_WebService. Но бывают сервисы, для которых нам это не требуется. В таком случае контроллер наследует интерфейс Listener_WebServiceTokenCheckInterface и реализует метод checkToken:

    public function checkToken($method_name)
    {
       return true;
    }
    

    Здесь $method_name — имя метода контроллера, который будет вызван.

    За работу с данными (например, загрузку из БД) у нас отвечают пакеты (packages) — наборы классов, объединенные одной областью применения. Контроллер получает данные из пакетов и, вместо использования массива для передачи данных во View, устанавливает их в объект ViewModel — по сути, в контейнер с набором сеттеров и геттеров. За счет этого внутри View всегда известно, какие данные переданы. Если набор данных меняется, все использования методов класса ViewModel можно запросто найти и поправить нужным образом. Будь это массив, пришлось бы искать по всему репозиторию, а потом и среди всех вхождений, особенно, если ключ массива назван простым и распространенным словом, например, “name”.

    Хотя такое обилие классов может казаться излишним, но в крупном проекте с большим количеством разработчиков это ощутимо помогает при поддержке кода. Сначала мы сделали возможность установки данных сразу во View, но быстро от нее отказались, поскольку у нас может быть не один проект, в котором требуется один и тот же контроллер, но данные отображаются по-разному. В любом случае придется создавать ViewModel и делать дополнительные View, поэтому лучше сразу написать чуть больше кода, что избавит в будущем от рефакторинга и дополнительного тестирования. Сейчас мы рассматриваем разные варианты оптимизации, поскольку многие разработчики считают, что кода стало очень много.

    На основе поступивших данных View готовит итоговый результат — это строка или специальный объект ParseResult. Это сделано, чтобы реализовать отложенный рендеринг: сначала идет подготовка всех данных, и лишь потом финальный рендеринг всего разом. Самый частый случай — создание страницы на основе blitz-шаблона и некоторых данных, подставляемых в него. В таком случае объект ParseResult будет содержать имя шаблона и массив с готовыми для шаблонизации данными, которые достаточно в нужный момент отправить в Blitz и получить итоговый HTML. Данные для шаблонизации могут содержать вложенные объекты ParseResult, поэтому финальный рендеринг производится рекурсивно. Тут хочется предостеречь вас от использования функции array_walk_recursive: она ходит не только по массивам, но и по публичным свойствам объектов. В связи с этим некоторые страницы у нас падали по памяти, пока мы не сделали собственную простую рекурсивную функцию:

    function arrayWalkRecursive($data, $function)
    {
       if (!is_array($data)) {
           return call_user_func($function, $data);
       }
       foreach ($data as $k => $item) {
           $data[$k] = arrayWalkRecursive($item, $function);
       }
       return $data;
    }
    

    Поскольку общение между PHP и JavaScript у нас очень тесное, то для него реализована соответствующая поддержка. Каждый объект View — это конкретный блок на сайте: header, sidebar, footer, центральная часть и т.п. Для каждого блока или компонента может быть свой собственный обработчик на JavaScript, который настраивается при помощи определенного набора данных — js_vars. Например, через js_vars передаются настройки для comet-соединения, по которому приходят разные обновления — счетчики, всплывающие уведомления и т.п. Все такие данные передаются через единую точку, определенную в blitz-шаблоне:

    <script type="text/javascript">
       $vars = {{JS_VARS}};
    </script>
    

    Помимо этого, у нас есть контроллеры, к которым обращается только JS и получает в качестве результата JSON. Мы называем их веб-сервисами. Относительно недавно мы начали описывать протокол общения PHP и JS с использованием Google Protocol Buffers (protobuf). На основе proto-файлов генерируются PHP-классы с набором сеттеров, автоматически валидирующие устанавливаемые в них данные, что позволяет оптимальным образом формализовать договоренность между front-end и back-end разработчиками. Ниже приведен пример proto-файла для описания оверлея и использование PHP-класса, сгенерированного на его основе:

    package base;
    
    message Ovl {
       optional string html = 1;
       optional string url = 2;
       optional string type = 3;
    }
    

    $Ovl = \GPBJS\base\Ovl::createInstance();
    $Ovl->setHtml($CloudView);
    $Ovl->setType('cloud');
    

    На выходе получаем JSON:

    {"$gpb":"base.Ovl","html":"here goes html","type":"cloud"}
    

    Среди всего прочего, в данных для JS может быть и HTML, получаемый из blitz-шаблонов. Каждый блок устанавливает js_vars от корня, и они рекурсивно сливаются в одну структуру при помощи функции array_replace_recursive. Пример структуры, готовой к рендерингу:

    ParseResult Object
    (
       [js_vars:protected] => Array
       (
           [Sidebar] => Array
           (
               [show_menu] => 1
           )
           [Popup] => Array
           (
               [html] => ParseResult Object
               (
                   [js_vars:protected] => Array ()
                   [template:protected] => popup.tpl
                   [tpl_data:protected] => Array
                   (
                       [name] => Alex
                   )
               )
           )
       )
       [template:protected] => index.tpl
       [tpl_data:protected] => Array
       (
           [title] => Main page
       )
    )
    

    Обычно контроллер готовит один блок на сайте — его центральную часть, а остальные блоки либо скрываются или показываются, либо определенным образом меняют свое поведение в зависимости от текущего контроллера. Для управления всем «каркасом» страницы используется объект Layout (грубо говоря, это базовый объект View), который устанавливает стандартные блоки и центральную часть в объект ParseResult для базового шаблона. Чтобы объявить, какой Layout будет использован, контроллер наследует специальный интерфейс HasLayoutInterface и реализует метод getLayout.

    Помимо сборки всей страницы, Layout несет дополнительную функцию: он формирует результат в виде JSON для «бесшовных» переходов между страницами. Уже достаточно давно наш сайт работает как веб-приложение: переходы между страницами осуществляются без перезагрузки всей страницы, меняются только определенные элементы (URL, заголовок, центральная часть, показываются или скрываются определенные блоки).

    Интеграция


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

    class TestPage extends CommonPage
    {
       public function run()
       {
           \Framework\Application::run(); // Запуск нового фреймворка
       }
    }
    
    $TestPage = new TestPage();
    $TestPage->init(); // Инициализация старого фреймворка
    $TestPage->run();
    

    Чтобы сделать независимый запуск нового фреймворка и оставить единую инициализацию, необходимо было вынести все, что происходит в init(), в отдельные классы и использовать и там, и там. Это было выполнено поэтапно, и в итоге получилось порядка 40 классов.

    После этого для пробы мы перевели несколько небольших проектов. Одним из первых было переведено наше расширение для браузера Chrome (Badoo Chrome Extension). А первым большим проектом стал сайт Hot Or Not, целиком написанный на новом фреймворке. В настоящее время мы постепенно переводим на него и наш основной сайт Badoo.

    Проекты, полностью реализованные на новом фреймворке, работают через фронт-контроллер, то есть единую точку входа — index.phtml. Для badoo.com мы имеем множество правил в nginx, последнее из которых отправляет на профиль. Т.е. badoo.com/something либо откроет профиль пользователя something, либо вернет 404. Именно поэтому, пока профиль полностью не переведен на новый фреймворк, у нас еще остается множество *.phtml файлов, которые содержат в себе лишь запуск фреймворка.

    <?php
    /*... includes ...*/
    \Framework\Application::run();
    

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

    Заключение


    В итоге мы решили достаточно большой пласт проблем: создали единую точку входа, убрали много уровней наследования, максимально уменьшили использование глобального контекста, код стал более объектным и типизированным. В основном изменения коснулись маршрутизации и всего процесса работы веб-скриптов, от запроса до ответа сервера. Также появилась кардинально новая система работы с представлением данных. Мы пробовали создать единую схему по доступу к данным, но пока не нашли решение, которое бы нас полностью устраивало. Но получившееся можно смело считать уверенным шагом в сторону удобной разработки и поддержки кода.

    Александр Treg Трегер, разработчик.
    Badoo
    400,00
    Big Dating
    Поделиться публикацией

    Комментарии 39

      +10
      Делитесь, че уж там)
        +1
        Такое желание есть, но пока фреймворк сыроват и не подготовлен к этому. Поживет еще какое-то время, причешем и, думаю, никаких преград открыть в open source не возникнет.
        +6
        Мне одному вот эта часть и всё, что дальше напомнило Symfony?
        $Request = new Request($_GET, $_POST, $_COOKIE, $_SERVER);
        $App = new Application();
        $Response = $App->handle($Request);
        $Response->send();
        
          +10
          Логично, что Application получает на вход Request. Логично, что Request не использует суперглобальные переменные, а получает их.
          Данные слова из английского языка передают точнее всего назначение этих классов.
          И такой код — самый очевидный способ это сделать. Естественно, что в большинстве фреймфорков он выглядел бы именно так.
            0
            И почему здесь нет $_FILES?
              0
              Пока что не требовалось, а делать максимально универсально задачи не стояло.
                0
                В таких крупных проектах обычно загрузка файлов производится не на вебы, а на специализированные аплоад-сервера. При этом код обработки аплоадов достаточно прост, и, возможно, вообще написан не на php, а, скажем, работает как модуль nginx.
                  0
                  и $_SESSION. А как с ними работа организована?
                    0
                    У нас собственное хранилище для сессий, работа с которым осуществляется через специальный класс, отвечающий за работу с демоном. SessionHandler-ы PHP и массив $_SESSION не используются.
                  0
                  В любом грамотно спроектированном фреймворке эта часть выглядит подобным образом. Тут нет ничего удивительного.
                  +8
                  А я еще в 2007-м году предлагал подобное. (Уходит, занудно бубня под нос).
                    0
                    Скажите, а внутри такого большого проекта как ваш потребовалось ли реализовывать механизм вложенного роутинга, т.е. когда контроллер создаёт новый Request, отправляет его в Application, получает ответ и обрабатывает его.

                    А ещё интересно было бы узнать связываете ли вы как-нибудь пути до action'ов контроллеров с путями внутри шаблонов, или это делается на уровне соглашений и документации?
                      +1
                      Ничто не мешает сделать вложенный роутинг, но таких задач не появлялось. Пока столкнулись лишь с ситуацией, когда в маршруте указан Action, результатом работы которого будет другой Action, но с тем же Request-ом. Например, возвращает специальный Action для неавторизованного пользователя.

                      Шаблоны с action-ами никак не связаны. Контроллер явно создает объект ViewModel, на основе имени которого будет найдет класс View, например ViewModel_Profile -> View_Web_Profile. А дальше в каждом конкретном случае View сама решает откуда брать шаблон, какие пути использовать и т.д.
                    • НЛО прилетело и опубликовало эту надпись здесь
                        +2
                        Я бы не сказал, что Application — это God-object, если я Вас правильно понял, и он не Singleton. Он выполняет четко заданную функцию — исполнить определенный алгоритм обработки HTTP-запроса, основываясь на «чек-поинтах» — событиях.

                        Исходники, как я уже ответил на первый комментарий, возможно мы откроем в будущем, когда код будет признан стабильным и приведен в «товарный вид».

                        90% страниц у нас работают как SPA — у нас своя достаточно интересная реализация этого в JS, заточенная под наши нужды, достойная отдельной статьи. Думаю, коллеги из отдела Frontend, в скором времени расскажут про нее.

                        Я знаю, что системные программисты у нас активно интересуются приложениями на Go, каких-то деталей не могу назвать. HHVM возможно кто-то пробовал, не используем в продакшене.
                          0
                          Просто хочу уточнить, у вас именно SPA или все же Pjax app? Просто если полноценный SPA, тогда причем тут генерация blitz-шаблонов да и вообще понятие «страница»?
                            0
                            Да, скорее Pjax. У нас нет шаблонизации на клиенте, все запрашивается исключительно с сервера. Если переход осуществлен по прямой ссылке, сервер отдаст целиком каркас с блоками без дополнительных запросов. При всех следующих переходах подгружаются лишь блоки (+дополнительные скрипты и стили), а базовый каркас остается.
                              0
                              Да, это базовый pjax сайт. Такие мы делали еще в далеком 2008, как только ajax стал хоть немного варимым. И это прям скажем не интересно, ибо настоящее SPA намного сложнее с точки зрения клиента и намного проще с точки зрения сервера и того как он работает с разнообразными видами клиентов (веб, мобилки, тв итп). Можно сказать что от сервера останутся только «веб-сервисы» в вашей терминологии. Кстати, а ТВ клиенты у вас есть, для Smart TV всяких?
                                0
                                Помимо веба у нас есть лишь мобильные приложения — iOS, Android, Windows, Blackberry, и 2 версии мобильного сайта — HTML5 и версия для более старых мобильных клиентов. Все они работают через единое API. Насколько я знаю, версий для ТВ-клиентов не планируется.
                                  0
                                  Довольно странно, ведь все крупные соц.сети имеют ТВ клиенты, Badoo всегда казалась мне достаточно крупной. Хотя раз не планируется, значит наверное есть причина…
                                    0
                                    Ну да, как приятно наверное вечером всей семьей сесть перед телевизором, зайти в баду, и посмотреть кого там муж наснимал.
                        +2
                        В процессе работы приложение генерирует события «Получен запрос», «Найден контроллер», «Получены данные», «Поймано исключение», «Рендеринг данных», «Получен ответ».


                        Вот эта часть один-в-один компонент Symfony HttpKernel. И я не понимаю зачем вы сделали свой велосипед.
                          0
                          Я могу предположить, что HttpKernel зависит от HttpFoundation, который в свою очередь супер кросплатформенный, что вызывает небольшой оверхед
                            +1
                            HttpFoundation просто предоставляет удобную ООП-обёртку над разными RFC. Не понимаю как именно он может вызвать оверхед.
                              0
                              Чтобы это понять — нужно влезть в код и посмотреть.
                              github.com/symfony/HttpFoundation/blob/master/Request.php#L1647 — вот, на пример.
                              И прочие тяжелые разборы, которые с легкостью можно отдать на откуп nginx или самому php.
                                0
                                Я это всё видел. Тот «оверхед», который вы привели — это просто песчинка в общем времени приложения. И экономия на этих песчинках точно не стоит чтобы велосипедить своё.
                                  0
                                  Почему не стоит? Если свой вилосипед будет экономить хоть сколько-то на каждом запросе — вполне норм писать свое. Учитывая, что свое можно всегда под себя изменить с легкостью.
                                  Авито недавно рассказывали что под себя драйвер постгреса допилили, ибо он какой-то оверхед делает.

                                  С другой стороны лицензия позволяет форкнуть и допилить, так что вы правы)
                                    0
                                    Когда у вас 1-2 сервера — песчинка. А когда половина датацентра одних вебов — уже нет ;)
                                      0
                                      Не согласен, это именно песчинка. Обычно слабое место — это поиск и сортировка данных, а отказываться от удобства и ясности в коде ради пары десятков милисекунд… ну тогда не php надо использовать.
                                        0
                                        Поиск и сортировка данных делаются в 1% случаев, в 99% случаев происходит чтение из мемкеша.
                                    0
                                    Мне бы первым делом пришла в голову идея отнаследоваться и оптимизировать такую тяжелую логику под вашу конфигурацию. Много вообще мест в HttpFoundation, которые вам кажутся чрезмерно тяжелыми?
                                      0
                                      ну, так-то да. Некоторые вещи от туда хорошо бы вынести в расширение на си. Например ParametersBag.
                                0
                                Событийная архитектура — да, тот же очень удобный принцип, но реализация другая. Главное отличие — у нас отрисовка данных (шаблонизация) осуществляется в последний момент. А также сборкой разных блоков занимается сам Application при помощи объектов Layout.
                                  0
                                  у нас отрисовка данных (шаблонизация) осуществляется в последний момент

                                  Хм, отрисовку данных в HttpKernel можно отложить на самый последний момент с помощью StreamedResponse класса.
                                  А также сборкой разных блоков занимается сам Application при помощи объектов Layout.

                                  Сборка разных блоков — это же шаблонизация. У вас шаблонизацией занимается Application?
                                  И ещё. Наверно «сборка разных блоков» подразумевает вывод в определенное место шаблона контент, который сгенерирован другим контроллером и представлением. В HttpKernel это опять же есть из коробки — Fragment называется.
                                    0
                                    StreamedResponse в данном случае будет выглядеть костылем, на мой взгляд. У нас есть отдельное событие — RenderEvent. Подписчики получают результат из контроллера и осуществляют шаблонизацию. В данный момент у нас есть два подписчика — первый в объект ParseResult доставляет js_vars, актуальные для большинства страниц, а второй занимается непосредственно шаблонизацией и преобразованием в JSON разных данных. Это достаточно объемная часть, заслуживающая быть выделенной отдельным компонентом.

                                    Layout — это объект, который, во-первых, знает, какие еще контроллеры, помимо главного, надо запустить, принимает результат их работы и знает как его использовать. Это происходит до шаблонизации. Обычно, Layout создает базовый ParseResult_Blitz (который при шаблонизации будет преобразован в HTML при помощи шаблонизатора Blitz). Устанавливает результат работы главного контроллера в блок CENTER, принимает на вход результат работы контроллера сайдбара и устанавливает в SIDEBAR. А сама шаблонизация произойдет позже. То есть под сборкой блоков понимается именно подготовка структуры.

                                    Конечно, можно было бы и на Symfony и на каком-то другом фреймворке сделать подобное, но вопрос в сроках, поддержке и уровне вхождения остается, на мой взгляд, открытым.
                                      0
                                      Ну всё что вы сейчас описали очень хорошо делается на компонентах симфони. И события у них есть, и результаты работы контроллеров в разные блоки вставлять можно. Видимо у вас просто не было человека с опытом работы в симфони ;)
                                      Я это к чему всё — у меня тоже есть (и продолжается) перевод старого (2007 год) проекта на рельсы симфони. И пока ещё ни разу не довелось жалеть о содеянном :)

                                      А что касается последнего абзаца:
                                      1. Сроки внедрения существующих симфони-компонент, КМК, вполне сопоставимы с разработкой аналогов
                                      2. Поддержка — код симфони-компонент покрыт тестами, + ими пользуются много разработчиков.
                                      3. Уровень вхождения — по симфони есть отличная готовая документация, по внутренним фреймворкам их часто нет :(
                                        +1
                                        Вы правы, специалистов с глубоким знанием Symfony на момент создания фреймворка у нас не было (или кто-то прятался). Изначально мы собрали рабочую группу, которая рассмотрела разные фреймворки, плюсы-минусы, но не могу сказать, что это был очень глубокий обзор.

                                        Рисковать, используя сторонний фреймворк, тоже не хотелось, т.к. нагрузки большие и очень важна скорость. Свое всегда можно достаточно быстро оптимизировать, со сторонними приложениями сложнее. Highload подразумевает зачастую эффективные, но некрасивые решения.
                                          +2
                                          Чужое тоже быстро оптимизируется — форкается и допиливается как надо. + можно отправить пулл реквест и помочь сообществу :)
                                            +1
                                            Оптимизации под высокие нагрузки — это зачастую такие грязные хаки, которые сообществу мало того, что не нужны — их и показывать даже не хочется :) Ну и заметная часть таких оптимизаций — это выпиливание abstraction layers и хардкод под конкретный случай, что сообществу вообще не нужно.

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

                              Самое читаемое