Автоматическое подключение модулей в битриксе

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

А в чем, собственно, проблема?

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

Например

Вот простой, наглядный пример, хоть и не очень жизненный:

function getCatalogSections() {
    CModule::IncludeModule("iblock");
    CIBlockSection::GetList(...);
    ...
}
function getNewsSections() {
    CModule::IncludeModule("iblock");
    CIBlockSection::GetList(...);
    ...
}

Здесь мы видим две функции, которые через api инфоблоков получают списки разделов. Нам заранее неизвестно в каком порядке будут вызваться эти функции и будут ли вообще вызываться на данном хите. Поэтому приходится использовать CModule::IncludeModule в каждой из них. Это приводит к загромождению кода ненужными конструкциями, ухудшает его читабельность. Да и постоянно приходится следить, чтобы все модули были подключены. Все это создает, хоть и небольшие, но все же неудобства при разработке, усложняет сопровождение кода. Особенно актуально для крупных проектов.

Легко забыть

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

Такое возможно, например, когда перед кодом, выполняется компонент, который и подключает необходимый модуль. В этом случае код будет прекрасно работать независимо от наличия в нем CModule::IncludeModule. Но что произойдет, если администратор пожелает отключить этот компонент? Правильно, fatal error.

Решение

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

// Карта классов. Задает соответствие класса модулю
$map = array(
    'iblock' => 'CIBlock CIBlockElement CIBlockSection',
    'catalog' => 'CCatalogProduct',
);

// Преобразуем карту в удобный для обработки вид
$preparedMap = array();
foreach($map as $module => $classes) {
    foreach(explode(' ', $classes) as $class) $preparedMap[$class] = $module;
}

spl_autoload_register(function($classname) use ($preparedMap) {
    // Определяем к какому модулю принадлежит класс
    if (isset($preparedMap[$classname]) && $preparedMap[$classname]) {
        // ... и подключаем этот модуль
        CModule::IncludeModule($preparedMap[$classname]);
        // ... а затем передаем управление автозагрузчику битрикса
        CModule::RequireAutoloadClass($classname);
    }
});

Пишем этот код, например, в init.php и о ручном подключении модулей можно забыть раз и навсегда.

Дальше можно не читать :)

Проблема обозначена, решение найдено. Дальше будет много букв про то, как это работает.

Как это работает?

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

Соответственно, если модуль не подключен, то автозагрузчик битрикса ничего не знает о его классах и не сможет подключить их, когда это будет необходимо. В итоге мы видим fatal error, если используем класс без подключения модуля.

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

  1. Определение к какому модулю относится требуемый класс
  2. Подключение этого модуля
  3. Передача управления автозагрузчику битрикса


Чей это класс?

Автозагрузчик модулей должен определить к какому модулю относится класс. В примере выше для этого используется карта классов, в которой прописано соответствие классов модулям. Это самое простое решение. Карта статична, она зашита в коде. Её нужно поддерживать в актуальном состоянии, вручную дописывать новые классы, что не очень удобно.

На самом деле эта карта классов, хоть и несколько в ином виде, все же присутствует внутри битриксового автозагрузчика. Возможно существует способ извлечь ее, например с помощью Reflection. В этом случае необходимость вручную поддерживать карту классов отпадает.

А как же функции?

А функции в пролете. В PHP отсутствуем механизм автозагрузки функций. Поэтому если вы хотите использовать, к примеру, функцию stemming, то будьте добры предварительно подключить модуль поиска search.

Поехали!

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

Допустим у нас все еще не подключен модуль инфоблоков и мы пытаемся выполнить следующий код:

$result = CIBlockElement::GetList();

Итак, поехали:

  1. Класс CIBlockElement еще не был объявлен и PHP возлагает почетную обязанность найти и подключить пропавший класс на первый обработчик из стека автозагрузки. То есть на автозагрузчик битрикса.
  2. Автозагрузчик битрикса, безуспешно пытаясь найти CIBlockElement, теряет доверие PHP.
  3. PHP, чтобы исправить положение, передает управление следующему обработчику, то есть нашему автозагрузчику модулей.
  4. Автозагрузчик модулей определяет, что CIBlockElement принадлежит модулю iblock и подключает этот модуль, после чего передает управление автозагрузчику битрикса.
  5. Автозагрузчик битрикса, не веря своему счастью, находит класс CIBlockElement в своей карте классов и, наконец-то, подключает его.
  6. Профит!

Подытожим

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