Yandex Metrika
sanches.free 1 просмотр

Исключения, Active Record и конфигурация без тяжёлого DI в проектах на 1С‑Битрикс

Вводная: сложность «чистым» ядром

На задачах чуть сложнее каталога плюс корзины штатными средствами 1С‑Битрикс всё ещё можно обойтись, но цена очевидна: растёт количество веток, исключительных случаев и времени на поиск узнаваемого решения. Готовые каркасы из сообщества помогают с компонентом с контроллером или с адаптером над API инфоблоков — они не обязательны, просто экономят несколько первых недель прототипа. Ниже — практика, которая у меня работала именно поверх платформы, без привязки к конкретному стороннему модулю.

Исключения вместо бесконечных флагов

Удобнее выбросить узнаваемое исключение, чем тащить цепочку if (!$ok) через половину вызова. Особенно когда один и тот же сценарий должен понимать контроллер компонента, агент или консольный скрипт. В примере сервис пытается прочесть текст для страницы: сначала из параметров секции инфоблока, затем подставляет текст элемента, а при отсутствии нужного узла откатывается назад через код ошибки константы.

final class NarrativeCopyResolver {
    public const MISSING_NODE = 20404;
    private string $snippet = "";
    private string $body = "";

    public function build(array $sectionParams): array {
        if ($this->sectionHasAnnouncement($sectionParams)) {
            $this->hydrateFromSection($sectionParams);
            return $this->payload();
        }
        try {
            $this->hydrateFromRelatedElement();
        } catch (\Throwable $e) {
            if ((int)$e->getCode() === self::MISSING_NODE) {
                $this->hydrateFromSection($sectionParams);
            } else {
                throw $e;
            }
        }
        return $this->payload();
    }

    private function sectionHasAnnouncement(array $sectionParams): bool {
        return trim(strip_tags((string)($sectionParams["ANNOUNCE"] ?? ""))) !== "";
    }

    private function hydrateFromRelatedElement(): void {/* выбор CIBlockElement::GetList */}
    private function hydrateFromSection(array $sectionParams): void {/* чтение ANNOUNCE / DETAIL_TEXT */}
    private function payload(): array {
        return [
            "snippet" => $this->snippet,
            "body" => $this->body,
            "visible" => trim(strip_tags($this->snippet . $this->body)) !== "",
        ];
    }
}

Сообщение для пользователя удобно отдавать отдельным шаблоном компонента: сервис возвращает имя нужного файла или флаг ошибки — а $this->IncludeComponentTemplate() выбирается по этому ключу без дублирования HTML во всех исключительных кейсах.

Active Record и инфоблок

Стандартные выборки по инфоблоку возвращают массивы, которые хочется упаковать в объект. Если хранилище всё равно MySQL через ядро, Active Record здесь воспринимается как упрощающая прослойка, хотя теория называет его спорным. Важнее дисциплина: свойства синхронизируются перед Add и Update, суммы переводятся в нужные единицы именно при границе БД.

class LedgerBookingSlot {
    private object $merchant;
    private array $blob = [
        "NAME" => "Новая заявка",
        "ACTIVE" => "Y",
        "PROPERTY_VALUES" => [
            "AMOUNT_MINOR" => 0,
            "REFERENCE" => "",
            "STATUS" => 0,
        ],
    ];

    public function __construct(object $merchant) {
        $this->merchant = $merchant;
    }

    public function createDraft(): void {
        global $USER;
        $el = new CIBlockElement();
        $row = &$this->blob;
        $row["IBLOCK_ID"] = (int)$this->merchant->blockId;
        $row["CREATED_BY"] = $USER->GetID();
        $row["MODIFIED_BY"] = $USER->GetID();
        $minor = $row["PROPERTY_VALUES"]["AMOUNT_MINOR"];
        $row["PROPERTY_VALUES"]["AMOUNT_MINOR"] = intdiv((int)$minor, 100);
        $id = (int)$el->Add($row);
        if ($id <= 0) {
            throw new RuntimeException("CIBlockElement::Add вернул 0");
        }
        $this->blob["ID"] = $id;
    }

    public function persist(): void {
        $el = new CIBlockElement();
        $row = $this->blob;
        $row["PROPERTY_VALUES"]["AMOUNT_MINOR"] = intdiv((int)$row["PROPERTY_VALUES"]["AMOUNT_MINOR"], 100);
        if (!$el->Update((int)$row["ID"], $row)) {
            throw new RuntimeException("CIBlockElement::Update не сохранил");
        }
    }
}

В реальном проекте между вызовами я бы вынес дубль преобразования суммы в маленький приватный метод и добавил загрузку GetList(); блок выше задаёт именно паттерн, а не готовое решение платёжной ведомости.

Dependency Injection в чистом виде против «тонкого контекста»

Полномасштабный DI-контейнер в смеси с битриксовыми глобалями и массивами параметров может ухудшить читаемость: одни службы живут через провайдер, другие напрямую тянут $USER, третья половина — это $arParams. Я чаще останавливался на прозрачной передаче конфигурационного файла и явном конструкторе у зависимого класса — без автоматического графа.

$APPLICATION->IncludeComponent(
    "partner:payments.bridge",
    "",
    [
        "LEDGER_IBLOCK_ID" => 14,
        "SETTINGS_PATH" => "/upload/payments/live.json",
    ],
    false,
);

// в методе класса компонента
public function hydrateSettings(array $params): void {
    $diskPath = $_SERVER["DOCUMENT_ROOT"] . '/' . ltrim((string)$params["SETTINGS_PATH"], "/");
    if (!is_readable($diskPath)) {
        throw new InvalidArgumentException("Нет конфигурации {$diskPath}");
    }
    $decoded = json_decode((string)file_get_contents($diskPath), false, 512, JSON_THROW_ON_ERROR);
    $decoded->runtime = [
        "LEDGER_IBLOCK_ID" => (int)$params["LEDGER_IBLOCK_ID"],
    ];
    $this->settings = $decoded;
}

Сторона, которая реально платит по заказам, получает объект настроек в конструкторе и не знает про пути на диске — это сохраняет границу между инфраструктурой компонента и доменным действием без лишнего контейнерного DSL.

Сжатый вывод

  • Исключения с осмысленными кодами и отдельные шаблоны компонента уменьшают вложенность и дубль разметки.
  • Active Record допустим для инфоблоковой записи при жёстко описанном преобразовании свойств между доменной моделью и полями $fields API.
  • Пара JSON в параметрах плюс строго проверенный файл часто понятнее, чем второй параллельный механизм внедрения зависимостей только ради паттерна.

Не хотите копаться сами?

Починю за 1-3 дня. Без предоплаты — оплата по результату.

15+ лет опыта с 1С-Битрикс · Без предоплаты · 7 дней гарантии