Старое ядро 1С-Битрикс - как читать и править легаси-код
В любом живом проекте на 1С-Битрикс встречается код старого ядра: классы с
префиксом C и глобальные объекты. Его нужно уметь читать и безопасно править -
а иногда и писать, потому что для части задач современного API просто нет.
Разберём, как это устроено и где проходит граница с D7.
Как это работает
Ядра два и они работают одновременно. Старое - это процедурные функции и
классы с префиксом C: CMain, CUser, CIBlockElement, CFile, CEvent.
Новое - пространства имён Bitrix\Main, автозагрузка, ORM, контроллеры.
Значительная часть модулей до сих пор предоставляет только классы старого ядра, а
публичные страницы строятся через пролог, эпилог и глобальный объект приложения.
Три глобальных объекта создаются в прологе. Объект $APPLICATION (класс
CMain) отвечает за страницу: заголовок и метатеги, подключение компонентов,
админ-панель, навигационную цепочку, свойства страницы. Объект $USER (класс
CUser) - это текущий посетитель: авторизация, идентификатор, группы. Объект
$DB (класс CDatabase) даёт прямое соединение с базой и транзакции.
В обработчиках событий и подключаемых файлах локальной переменной может не быть -
там пишут global $USER; или обращаются через суперглобальный массив. Тип нигде
не объявлен формально, поэтому и IDE не знает, что перед ней за объект - как
вернуть автодополнение для $APPLICATION, $USER и $DB докблоком, показано в
статье «Автодополнение и подсказки IDE».
Признаки легаси, которые видно сразу: классы с префиксом C; глобальные
объекты; выборка вида $rs = CXxx::GetList(...) с перебором while ($ar = $rs->GetNext()); подключение модуля через CModule::IncludeModule();
регистрация событий функциями AddEventHandler и RegisterModuleDependences;
ошибки в глобальной переменной. В новом коде тем же задачам соответствуют
Loader::includeModule(), ORM, EventManager и объект Result.
Принцип миграции. Новый код пишут на D7, но легаси не переписывают ради переписывания. Старое ядро остаётся штатным способом там, где современного API нет: административные списки, ресайз изображений, агенты, старые события без объекта события.
Примеры
1. Типовая физическая страница
<?phprequire($_SERVER['DOCUMENT_ROOT'] . '/bitrix/header.php');
$APPLICATION->SetTitle('Контакты');$APPLICATION->SetPageProperty('description', 'Как с нами связаться');?>
<p>Наш адрес...</p>
<?phprequire($_SERVER['DOCUMENT_ROOT'] . '/bitrix/footer.php');Этот шаблон вы увидите в тысячах проектов. Пролог подключает шапку сайта и создаёт глобальные объекты, эпилог - подвал и завершает буферизацию.
2. Подключение модуля и выборка
if (CModule::IncludeModule('iblock')) { $rs = CIBlockElement::GetList( ['SORT' => 'ASC'], ['IBLOCK_ID' => 5, 'ACTIVE' => 'Y'], false, ['nTopCount' => 20], ['ID', 'NAME', 'DETAIL_PAGE_URL'] );
while ($ar = $rs->GetNext()) { echo $ar['NAME']; // GetNext уже экранирует значения }}Два отличия от D7, о которых стоит помнить. Первое: GetNext() кодирует значения
и обрабатывает шаблоны ссылок, а Fetch() отдаёт данные как есть и работает
быстрее - при выводе после Fetch() экранируйте сами. Второе: значения приходят
строками, без приведения к типам полей.
Современный эквивалент подключения модуля - Loader::includeModule() или
Loader::requireModule(), а выборки - ORM.
3. Текущий пользователь
global $USER;
if ($USER->IsAuthorized()) { $userId = $USER->GetID(); $groups = $USER->GetUserGroupArray();}
if ($USER->IsAdmin()) { // административная логика}В контроллерах D7 то же самое делают через объект текущего пользователя, который приходит в действие автоварингом, - глобальный объект там не нужен.
4. Работа с файлами
// загрузка файла и сохранение в хранилище$fileId = CFile::SaveFile(CFile::MakeFileArray($path), 'iblock');
// массив с путями и размерами$file = CFile::GetFileArray($fileId);if ($file !== false) { echo $file['SRC'];}
// ресайз$resized = CFile::ResizeImageGet( $fileId, ['width' => 300, 'height' => 300], BX_RESIZE_IMAGE_PROPORTIONAL, true);echo $resized['src'];Обратите внимание на разницу методов: GetByID() возвращает объект выборки, у
которого нужно вызвать Fetch(), а GetFileArray() сразу отдаёт массив или
false. Путаница между ними - классическая ошибка.
Работа с изображениями - как раз тот случай, где старое ядро незаменимо: D7-API для ресайза нет.
5. Что чем заменяется
| Старое ядро | Современный аналог |
|---|---|
CModule::IncludeModule() | Loader::includeModule() / requireModule() |
$DB->Query() | Application::getConnection()->query() |
CDBResult::Fetch() в цикле | Result::fetch(), fetchAll(), fetchObject() |
CIBlockElement::GetList() | ORM инфоблока со скомпилированной сущностью |
AddEventHandler() | EventManager::addEventHandler() |
COption::GetOptionString() | Config\Option::get() |
$USER->GetID() в контроллере | объект текущего пользователя в действии |
$_REQUEST, $_GET, $_POST | HttpRequest из контекста |
| глобальная переменная с ошибкой | объект Result с коллекцией ошибок |
AddMessage2Log() | логгер PSR-3 |
А вот то, что заменять нечем: административные списки на CAdminList, ресайз
изображений через CFile, агенты CAgent, почтовые события CEvent, старые
события ядра без объекта события.
Частые ошибки
Фатальная ошибка об отсутствующем классе. Не подключён модуль перед обращением к его классам.
Обращение к результату CFile::GetByID() как к массиву. Метод возвращает
объект выборки - нужен вызов Fetch(). Для готового массива есть
GetFileArray(), и его результат надо проверять на false.
Данные не экранированы при выводе. Метод Fetch() отдаёт значения как есть.
Либо используйте GetNext(), либо экранируйте сами.
Групповое действие в админ-списке ведёт себя странно. Метод GroupAction()
объявлен как возвращающий логическое значение, но фактически отдаёт массив
идентификаторов - результат надо присваивать и проверять как массив.
Код после проверки режима списка не выполняется. В режимах AJAX и выгрузки в
Excel CheckListMode() прерывает выполнение - вызывайте его в правильной точке
подготовки списка.
Обработчик индексации меняет данные по ссылке. Массив полей приходит по значению, изменения нужно возвращать из обработчика.
Частые вопросы
Нужно ли переписывать весь легаси на D7?
Нет. Переписывание ради переписывания добавляет риск и не приносит пользы. Разумная стратегия такая: новый код пишем на D7, а старый трогаем тогда, когда всё равно правим этот участок или когда легаси мешает - например, мешает производительности или безопасности. И помните, что часть задач в принципе решается только старым ядром.
Где старое ядро остаётся единственным вариантом?
Административные списки и формы на классах CAdminList, ресайз и обработка изображений через CFile, агенты, почтовые события, а также подписка на старые события ядра, у которых нет объекта события. В этих местах использование C-классов - не легаси, а нормальная работа.
Чем Fetch отличается от GetNext?
Метод GetNext() кодирует значения и обрабатывает шаблоны ссылок, поэтому результат можно выводить сразу, но он медленнее. Метод Fetch() отдаёт сырые данные и работает быстрее - в этом случае экранирование при выводе полностью на вас. Для больших выборок обычно берут Fetch() и экранируют явно.
Можно ли смешивать старое ядро и D7 в одном файле?
Да, это нормальная ситуация: они работают в одном приложении и видят одни и те же данные. Осторожность нужна в двух местах. Первое - события: старые и новые механизмы независимы, и запись через одно API не поднимет обработчики другого. Второе - транзакции: начинайте и завершайте их через одно соединение, а не через $DB с одной стороны и D7-подключение с другой.
Связанные темы
- Архитектура платформы - жизненный цикл запроса
- Ядро D7 - современные аналоги
- D7 ORM - замена выборкам старого ядра
- События и агенты - две событийные модели
- Автодополнение и IDE - почему автодополнение обрывается на классах и глобальных объектах без объявленных типов
- Ошибка после обновления PHP - что ломается в старом коде на новой версии
- Раздел Основы