Очень большой проблемой Bitrix24 является его монолитность. Несмотря на то, что от релиза к релизу разработчики решают проблемы сильной связанности, остаётся много легаси кода, поведение которого не всегда поддаётся модификации с помощью событий.
Видеоинструкция в Telegram канале.
Bitrix является, в каком-то смысле, модульной системой. При вызове классов какого-либо модуля происходит его загрузка через автоматическую загрузку классов. Регистрация автолоадера может происходить несколькими способами:
- С помощью стандартной функции php spl_autoload_register
- Используя функционал Bitrix CModule::AddAutoloadClasses
Нужна автозагрузка для подключения необходимого файла, в котором описан вызываемый класс.
Пример автозагрузки классов модуля CRM:
CModule::AddAutoloadClasses( 'crm', array( 'CAllCrmLead' => 'classes/general/crm_lead.php', 'CCrmLead' => 'classes/'.$DBType.'/crm_lead.php', 'CCrmLeadWS' => 'classes/general/ws_lead.php', 'CCRMLeadRest' => 'classes/general/rest_lead.php', 'CAllCrmDeal' => 'classes/general/crm_deal.php', 'CCrmDeal' => 'classes/'.$DBType.'/crm_deal.php', 'CAllCrmCompany' => 'classes/general/crm_company.php', 'CCrmCompany' => 'classes/'.$DBType.'/crm_company.php', 'CAllCrmContact' => 'classes/general/crm_contact.php', 'CCrmContact' => 'classes/'.$DBType.'/crm_contact.php', 'CCrmContactWS' => 'classes/general/ws_contact.php', 'CCrmPerms' => 'classes/general/crm_perms.php', 'CCrmRole' => 'classes/general/crm_role.php', ... ) );
Таким образом при обращении к классу CCrmRole будет подключен файл classes/general/crm_role.php из модуля crm.
Перегрузка и расширение функционала
Как происходить подключение нужных файлов мы разобрались. Но что делать, если потребуется изменить или расширить стандартный функционал? Напоминаю, что вносить правки в ядро системы категорически не рекомендуется!
Мы знаем, что есть 1 или несколько зарегистрированных автозагрузок классов, значит мы можем сделать еще один и указать, что он должен быть выполнен раньше всех (для этого у стандартной функции есть параметр prepend). Файл init.php идеально подойдёт для регистрации нашей функции автозагрузки.
Шаг 1 Регистрация автоматической загрузки классов
Добавим в директорию php_interface файл override.php и подключим его в init.php.
require_once(__DIR__.'/override.php');
Содержимое файла override.php
<?php /** * Перегрузка классов */ $classDirectoryPath = __DIR__ . '/classes'; /** * Конфигуратор переопределяемых классов */ $config = []; /** * Регистрация автозагрузчика */ spl_autoload_register(function ($baseClassName) use ($config, $classDirectoryPath) { if (!empty($config[$baseClassName])) { $classParts = explode('\\', $baseClassName); $className = array_pop($classParts); $namespace = implode('\\', array_filter($classParts)); $virtualClassName = "___Virtual{$className}"; if (file_exists($config[$baseClassName]['classPath'])) { $classContent = file_get_contents($config[$baseClassName]['classPath']); $classContent = preg_replace('#^<\?(?:php)?\s*#', '', $classContent); $classContent = str_replace("class {$className}", "class {$virtualClassName}", $classContent); if(!empty($config[$baseClassName]['replace'])) { foreach ($config[$baseClassName]['replace'] as $from => $to) { $classContent = str_replace($from, $to, $classContent); } } eval($classContent); } $classFilePath = $classDirectoryPath . '/' . str_replace('\\', '/', $config[$baseClassName]['overrideClass']) . '.php'; if (file_exists($classFilePath)) { $overrideClassContent = file_get_contents($classFilePath); $overrideClassContent = preg_replace('#^<\?(?:php)?\s*#', '', $overrideClassContent); $overrideClassContent = preg_replace('#extends ([^\s]+)#', "extends {$virtualClassName}", $overrideClassContent); $overrideClassContent = preg_replace('#namespace ([^\s]+);#', $namespace ? "namespace {$namespace};" : "", $overrideClassContent); eval($overrideClassContent); return; } } }, true, true);
Конфигуратор — массив, содержащий элементы следующего вида:
'Имя исходного класса' => [
'classPath' => 'абсолютный путь к файлу исходного класса',
'overrideClass' => 'имя класса, которым будем подменять исходный',
'replace' => массив значений для замены вида ['подстрока для замены в классе (например "private function foo")' => 'новое значение (например "protected function foo")']
]
Таким образом, для перегрузки классов CCrmDeal и Bitrix\Crm\DealTable массив $config будет такой:
$config = [ 'CCrmDeal' => [ 'classPath' => __DIR__ . '/../../bitrix/modules/crm/classes/mysql/crm_deal.php', 'overrideClass' => 'Aclips\Override\CCrmDeal' ], 'Bitrix\Crm\DealTable' => [ 'classPath' => __DIR__ . '/../../bitrix/modules/crm/lib/deal.php', 'overrideClass' => 'Aclips\Override\DealTable' ] ];
Шаг 2 Переопределение
Для примера попробуем запретить использовать метод Bitrix\Crm\DealTable::delete() и расширим Bitrix\Crm\DealTable::getRow(). В директории local/php_interface/classes/Aclips/Override (по умолчанию наши) создадим файл DealTable.php:
<?php namespace Aclips\Override; /** * Класс для переопределения поведения Bitrix\Crm\DealTable */ class DealTable extends \Bitrix\Crm\DealTable { public static function getRow($params) { print 'Метод переопределён'; return parent::getRow($params); } public static function delete($id) { throw new \Exception('Метод больше не используется'); } }
Шаг 3 Проверка
Вызов метода Bitrix\Crm\DealTable::getRow()
Bitrix\Main\Loader::includeModule('crm'); $row = \Bitrix\Crm\DealTable::getRow([ 'filter' => [ 'ID' => 26 ] ]); print("\nid = " . $row['ID']);

Вызов метода Bitrix\Crm\DealTable::delete()
try { \Bitrix\Crm\DealTable::delete(1); } catch (\Throwable $e) { print $e->getMessage(); }

Итог
Теперь везде, где использовались переопределяемые методы будет отрабатывать новая логика, таким образом можно изменить поведение любых функций системы. Если появится необходимость получить доступ к защищённым свойствам и методам исходного (родительского) класса, то можно расширить автозагрузчик и модифицировать класс с помощью Reflection API.
Описанный способ подойдёт для перегрузки любых классов, подгружаемых через автоматическую загрузку классов, например библиотеки установленные через composer (практичность сомнительна, но возможность есть).
6 комментариев
Подскажи, таким способом можно заменить любой класс или только из битриксового ядра?
Любой, который будет подгружен через автозагрузку.
пытаюсь по примеру заменить метод класса
CAllIMContactList
находится по пути
/bitrix/modules/im/classes/general/im_contact_list.php
‘Bitrix\Im\CAllIMContactList’ => [
‘classPath’ => __DIR__ . ‘/../../bitrix/modules/im/classes/general/im_contact_list.php’,
‘overrideClass’ => ‘Bazoo\Override\ImOverrideClass’
],
в файле класса
namespace Bazoo\Override;
use \Bitrix\Im\CAllIMContactList;
class ImOverrideClass extends CAllIMContactList
и метод GetList в котором просто exit()
ваш пример работает, мой метод не переписывается
сможете подсказать в чем дело ?
Благодарю.
Решил проблему интуитивно
понял, что файл должен находиться именно в lib
но сути не понял, если объясните — буду рад
Указание такого неймспейса (Bitrix\Im\CAllIMContactList) говорит о том, что файл класса будет взять из директории модуля lib (bitrix/modules/im/lib). Попробуй указать просто CAllIMContactList. Класс в overrideClass лучше делать одноимённым с переопределяемым.
спасибо, прояснили.