Чистый код в компонентах Битрикс: Магия class.php
Урок №2. Анатомия class.php. Собираем идеальный каркас и управляем жизненным циклом
На прошлом уроке мы разобрали, почему старый процедурный подход (component.php) — это путь в никуда, и как «лапша» из кода убивает поддержку проекта. Мы увидели, что даже переход на классы не спасает, если не менять само мышление.
Сегодня мы переходим от теории к практике. Мы возьмем наш реальный легаси-компонент и пройдем путь его трансформации в современный класс.
Наш пациент: Легаси-компонент (component.php)
Это типичный код списка новостей, который можно встретить в 90% проектов.
// lesson-01-legacy/component.php
if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED !== true) die();
global $USER, $APPLICATION;
$arParams["IBLOCK_ID"] = intval($arParams["IBLOCK_ID"]);
$arParams["NEWS_COUNT"] = $arParams["NEWS_COUNT"] ? intval($arParams["NEWS_COUNT"]) : 10;
$arParams["CACHE_TIME"] = $arParams["CACHE_TIME"] ? intval($arParams["CACHE_TIME"]) : 3600;
CModule::IncludeModule("iblock");
$obCache = new CPHPCache();
$cacheTime = $arParams["CACHE_TIME"];
$cacheId = serialize($arParams);
$cachePath = "/" . SITE_ID . "/news_list/";
if ($obCache->InitCache($cacheTime, $cacheId, $cachePath)) {
$vars = $obCache->GetVars();
$arResult = $vars["arResult"];
} else {
$obCache->StartDataCache();
$arResult = array();
$arResult["ITEMS"] = array();
$rsElements = CIBlockElement::GetList(
array("ACTIVE_FROM" => "DESC", "ID" => "DESC"),
array("IBLOCK_ID" => $arParams["IBLOCK_ID"], "ACTIVE" => "Y"),
false,
array("nTopCount" => $arParams["NEWS_COUNT"]),
array("ID", "NAME", "PREVIEW_TEXT", "PREVIEW_PICTURE", "DETAIL_PAGE_URL", "ACTIVE_FROM", "CREATED_BY")
);
while ($obElement = $rsElements->GetNextElement()) {
$arItem = $obElement->GetFields();
// ... форматирование, N+1 запросы авторов ...
$arResult["ITEMS"][] = $arItem;
}
$obCache->EndDataCache(array("arResult" => $arResult));
}
$APPLICATION->SetTitle("Новости компании");
$this->IncludeComponentTemplate();
Шаг 1: Миграция «в лоб» — переносим код в class.php
Первый логичный шаг при рефакторинге — это перенести старый процедурный код в метод executeComponent. Это промежуточная стадия, которая позволяет запустить компонент на «новом движке», но при этом сохранить старую логику.
Что нужно изменить минимально, чтобы это заработало:
- Контекст: Все переменные, которые раньше были «просто переменными» (
$arResult,$arParams), теперь становятся свойствами объекта через$this->($arResult->$this->arResult,$arParams->$this->arParams). - Глобальные объекты: Вместо
global $APPLICATIONвнутри класса лучше использовать\Bitrix\Main\Application(на этом этапе можно оставить иglobal $APPLICATION). - Вызов шаблона: Функция
IncludeComponentTemplate()превращается в метод$this->includeComponentTemplate(). - Кэширование: Вместо ручного создания объекта
new CPHPCache()и вызоваInitCache/StartDataCache, мы переходим на встроенный метод$this->startResultCache(). Это избавляет нас от 10+ строк лишнего кода и ручного управления путями кэша.
Главное изменение: Упрощение кэширования
В component.php мы вручную создавали объект CPHPCache, инициализировали его, проверяли InitCache и вручную сохраняли переменные через EndDataCache. В классе всё это делает один метод $this->startResultCache().
| Было (component.php) | Стало (class.php) |
|---|---|
$obCache = new CPHPCache(); |
(Не нужно, встроено в родительский класс) |
$cacheId = serialize($arParams); |
(Генерируется автоматически на основе $this->arParams) |
if ($obCache->InitCache($time, $id, $path)) |
if ($this->startResultCache()) |
$obCache->EndDataCache(["arResult" => $arResult]); |
(Происходит автоматически при вызове includeComponentTemplate) |
$this->startResultCache() автоматически использует время кэширования из $this->arParams["CACHE_TIME"]. Если метод возвращает true — кэша нет, нужно выполнять код. Если false — Битрикс сам достанет $this->arResult из кэша и сразу подключит шаблон.Почему мы не останавливаемся на этом этапе?
Хотя компонент заработал, мы получили «ООП-макароны». Весь код (100+ строк) по-прежнему лежит в одной куче внутри executeComponent. Это затрудняет дебаг и не дает нам использовать преимущества жизненного цикла Битрикса.
Шаг 2: Используем жизненный цикл (Lifecycle)
Битрикс предоставляет нам встроенные механизмы, которые позволяют разнести логику по этапам.
Метод onPrepareComponentParams: Первый рубеж обороны
Это самый важный метод. Он вызывается до кэширования.
- Его роль: Привести входящие параметры
$arParamsк нужному типу. - Архитектурный смысл: Гарантировать, что вся остальная логика класса будет работать с валидными данными.
Метод executeComponent: Дирижер, а не оркестр
Этот метод — точка входа. Его задача — управлять последовательностью действий, а не выполнять их все самому.
- Переносим валидацию параметров в onPrepareComponentParams.
- Выделяем проверку модулей в checkModules.
Результат: Чистый каркас (Boilerplate)
Вот как выглядит наш компонент после первого этапа правильного рефакторинга (код сокращен для наглядности).
// lesson-02-class-basics/step-2-lifecycle/class.php
if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED !== true) die();
use Bitrix\Main\Loader;
class NewsListComponent extends CBitrixComponent
{
/**
* Валидация параметров - теперь в отдельном методе!
*/
public function onPrepareComponentParams($params): array
{
$params["IBLOCK_ID"] = (int)($params["IBLOCK_ID"] ?? 0);
$params["NEWS_COUNT"] = (int)($params["NEWS_COUNT"] ?? 10);
$params["CACHE_TIME"] = (int)($params["CACHE_TIME"] ?? 3600);
return $params;
}
/**
* Проверка подключения модулей
*/
protected function checkModules(): void
{
if (!Loader::includeModule("iblock")) {
throw new \Bitrix\Main\SystemException("Модуль iblock не установлен");
}
}
public function executeComponent()
{
try {
$this->checkModules();
if ($this->startResultCache()) {
// ... логика выборки данных (CIBlockElement::GetList) ...
$this->includeComponentTemplate();
}
} catch (\Exception $e) {
$this->abortResultCache();
ShowError($e->getMessage());
}
// SEO и заголовки
$GLOBALS["APPLICATION"]->SetTitle("Новости компании");
}
}
Что мы получили:
- Читаемость:
executeComponentстал понятнее за счёт разбиения на отдельные методы. - Надежность: Параметры всегда валидны благодаря
onPrepareComponentParams. - Безопасность: Ошибки модулей обрабатываются через
try/catch.
Миграция в class.php
- Возьмите легаси-код из репозитория.
- Создайте новый компонент на базе классов.
- Перенесите логику последовательно: сначала параметры, потом проверку модулей, затем саму выборку.
- Убедитесь, что вы используете
$this->startResultCache()вместо старогоCPHPCache. - Сравните полученный результат с финальной версией
AI Домовой История
Привет! Я помогу с вопросами по 1С-Битрикс.
Спрашивай про D7, ORM, компоненты или события.