Очень большой проблемой 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 (практичность сомнительна, но возможность есть).
1 комментарий
Обратите внимание на runkit7_method_redefine()
см https://www.php.net/manual/ru/function.runkit7-method-redefine.php
на php8.2 и bitrixVM модуль устанавливается без проблем