В статье рассматривается альтернативный способ загрузки страниц т. н. «Единая точка входа» (иначе FrontController). Его сравнение с текущим способом загрузки ну и конечно реализация данного подхода.
INTRO
Все что описано ниже, это мое личное мнение и виденье, если оно вас не устраивает, если вы считаете меня не компетентным, или недостаточно опытным — это сугубо ваше личное мнение. Если говорить про объективную критику и советы — то милости прошу, это очень даже полезные штуки, тем более что именно для них и написана статья. Ну, а теперь поехали
AFTER_INTRO
Так уж случилось, что по каким-то причинам Битрикс не имеет единую точку входа. Не то, чтобы это было прям необходимо, но ДА - это прям необходимо! Почему же единая точка прям таки нужна (прям таки для Битрикс):
Вызов функции Cmain::ShowSpreadCookieHTML (данная функция выводит набор невидимых IFRAME'ов используемых в Технология переноса посетителей)
Событие OnEpilog
Завершение буферизации страницы
Отправка E-Mail писем
Событие OnAfterEpilog
Завершение соединения с базой данных
Ну а теперь небольшой разбор всего того что выше (если какие то пункты опущены, то значит по ним нет комментариев): 2-3. а зачем подключаться сразу? Если мы хотим вернуть статику (кеш) это действие будет лишним; 7. проверка, то есть запуск? Вообще немного странно, но про агенты будет далее; 8. опять лишнее действие если данные в сессии не хранятся (хотя по факту Битрикс ее юзает в любом случае, но по идее стартовать должен модуль ее использующий, и это не должно быть заложено априори); 16-21. самое непонятное что может только быть. То есть если нам нужно вывести при обработке страницы, что-либо в шаблоне (title, breadcrumbs, установка заголовков, ...) мы прекращаем текущую буферизацию, запоминаем данные, а затем начинаем буферизацию по новой и по сути загрузка страницы может выглядит примерно так:
// блок 1
ob_start();
$APPLICATION→AddBufferContent(…);
// записываем в переменную то, что вывели в блоке 1
ob_end_clean();
// блок 2
ob_start();
$APPLICATION→AddBufferContent(…);
// записываем в переменную то, что вывели в блоке 2
ob_end_clean();
// …
// записывает в переменную то, что вывели в блоке 100500
// находится компонент который возвращает JSON пакет (для AJAX запроса)
// собственно чистим буфер, т. к. то что вывелось выше нам не нужно
$APPLICATION→RestartBuffer();
22. почему отдельно, а не в пункте 23?
Собственно опущены очевидные вещи и все то что касается многосайтовости.
ЗАГРУЗКА СТРАНИЦЫ (by Juggernaut)
Для начала нужно составить порядок загрузки страницы:
Событие OnInit (конструктор)
Событие OnBeforeRequest
Обработка запроса
Событие OnAfterRequest
Отправка ответа (вместе с заголовками, куками и данными)
Теперь конкретно по пунктам:
OnInit – в данном событии определяются константы, обработчики событий и какой сайт запрашивается (собственно сначала нужно делать это, а потом уже все остальное).
OnBeforeRequest – сюда можно посадить например выполнение агентов.
Обработка запроса — определение нужно действия, обработка ошибок и прочие полезности.
OnAfterRequest – сюда можно посадить например логирование, выполнение агентов (хотя лучше не стоит, ниже подробнее).
Отправка ответа — собственно вот
Чем проще тем лучше господа!
Прежде чем я перейду к профитам данного порядка загрузки, представлю псевдокод (концепт) который собственно данный порядок формализует и добавляет некоторые возможности:
class Main
{
const EVENT_INIT = 'mainOnInit';
const EVENT_BEFORE_REQUEST = 'mainOnBeforeRequest';
const EVENT_AFTER_REQUEST = 'mainOnAfterRequest';
const EVENT_ERROR = 'mainOnError';
/**
* Функция обработки ошибки
* @var callable
*/
protected $errorHandler;
/**
* Инициализация предварительных данных
*/
public function __construct() {
$this->initAutoloader();
$this->initListeners();
$this->initErrorHandler();
//
Event::trigger(self::EVENT_INIT);
}
/**
* инициализация автозагрузчика,
* для возможности неявно подключать классы модулей, без ручного подключения,
* для возможности вызова класса компонентов напрямую (не через APPLICATION)
*/
public function initAutoloader() {}
/**
* загружает файлы инициализации всех активных модулей.
* вместо того чтобы вносить какие-либо изменения в файл 'php_interface/init.php'
* нужно в каждом модуле, если это требуется,
* создать файл 'init.php', который будет подгружаться в данном событии.
* в файле 'init.php' можно вешать слушателей на любые события,
* а также производить любые необходимые действия с учетом текущего этапа загрузки
*/
public function initListeners() {}
/**
* Точка входа
*/
public function run() {
try {
$this->onBeforeRequest();
$response = $this->handleRequest();
$this->onAfterRequest();
$response->send();
return $response->status;
}
catch (Exception $error) {
call_user_func($this->errorHandler, $error);
return $error->getCode();
}
}
/**
* событие ПЕРЕД обработкой запроса,
* сюда можно повешать выполнение агентов
*/
public function onBeforeRequest() {
$this->initSite();
$this->initUrlRewrite();
//
Event::trigger(self::EVENT_BEFORE_REQUEST);
}
/**
* инициализируются параметры запрошеного сайта:
* - id
* - локализация (по-умолчанию)
* - шаблон (по-умолчанию)
* - ...
*/
public function initSite() {}
/**
* инициализация URL менеджера
*/
public function initUrlRewrite() {}
/**
* непосредственная обработка запроса,
* определяет запрошеную страницу,
* формирует объект ответа,
* обработка запрошенной страницы,
* подключает запрошенную страницу на основе полученного маршрута,
* инициализирует контроллер страницы которые отвечает:
* - за вывод данных
* - за тип данных
* - за подключение шаблона
* - за атрибуты страницы
* - др.
*/
public function handleRequest() {
list($page, $params) = $this->parseRequest();
$pageController = new PageController($page);
$pageController->params = $params;
$pageController->run();
return $pageController->response;
}
/**
* событие ПОСЛЕ обработки запроса,
* сюда можно повешать различные логгирования
*/
public function onAfterRequest() {
Event::trigger(self::EVENT_AFTER_REQUEST);
}
/**
* вещает обработчика ошибок,
* если не был установлен никакой кастомный
*/
public function initErrorHandler() {
if ($this->errorHandler === null) {
/**
* подключает страницы с соответствующими страницами
*/
$this->errorHandler = function(Exception $error) {
if ($error instanceof NotFoundHttpException) {
include '404.php';
}
elseif ($error instanceof BadRequestHttpException) {
include '400.php';
}
else {
include '500.php';
}
};
}
}
/**
* выполняет разбор запроса, на основе URL менеджера и текущего сайта
*/
public function parseRequest() {}
}
Реализация классов Request, Response и др. опущена, потому что не имеет особо значения (на данный момент по крайней мере).
Какие плюсы вытекают из «нового» варианта загрузки:
это проще и прозрачнее
избавляет от глобальных переменных типа APPLICATION, USER, …
упрощает подключение файлов и модулей
все страницы, можно вынести из публичной части
избавляет от AddBufferContent и ее логики коллбэков
нет include (вообще)
…
На самом деле это все, что пришло в голову, но мне достаточно 1 пункта.
Вот такой небольшой вариант альтернативной загрузки, ваши вопросы, предложения, пожелания и гневные комменты жду ниже
P.S. небольшой опросик напоследок, который меня дико волнует
Группы на сайте создаются не только сотрудниками «1С-Битрикс», но и партнерами компании. Поэтому мнения участников групп могут не совпадать с позицией компании «1С-Битрикс».