Ночной cron написал в лог "удалено 800", а утром в каталоге пусто - или наоборот: на каждом ID только false, а LAST_ERROR молчит. На практике типичная ошибка: скопировали пример из REST API облачной CRM, не подключили модуль iblock, в init.php висит OnBeforeIBlockElementDelete с return false без ThrowException, а для товаров с SKU удалили только родителя. Ниже - минимальный CIBlockElement::Delete, проверка GetException, decision-tree Delete vs ACTIVE=N, порядок удаления offers и безопасный массовый cron.
CIBlockElement::Delete - статический метод модуля iblock в 1С-Битрикс: Управление сайтом. Удаляет элемент инфоблока по ID вместе со свойствами, разделами и привязками. Перед удалением - OnBeforeIBlockElementDelete (отмена через ThrowException и return false), после - OnAfterIBlockElementDelete. При ошибке - false; для несуществующего ID официально возвращает true. Сегодня: подключите iblock, вызовите Delete((int)$ID), при false читайте GetException, для каталога сначала удалите offers через CCatalogSKU::getOffersList.
Речь про 1С-Битрикс: Управление сайтом на вашем хостинге, не про Bitrix24 CRM. Элемент инфоблока - товар, новость, услуга в CMS. Создание карточки - CIBlockElement::Add, правка полей - CIBlockElement::Update. Здесь - безвозвратное удаление записи из базы.
Разберите, когда нужен Delete, а когда достаточно ACTIVE=N
CIBlockElement::Delete - PHP-метод для полного удаления элемента: ночная очистка неактивных товаров, миграция между инфоблоками, API-скрипт после выгрузки. В админке то же действие - кнопка "Удалить", но в cron и обменах без кода не обойтись.
| Ситуация | ACTIVE=N (деактивация) | CIBlockElement::Delete |
|---|---|---|
| Временно скрыть товар | Да, восстановить через Update | Избыточно и рискованно |
| Убрать дубликаты импорта навсегда | Мусор останется в БД | Правильный выбор |
| SEO: страница еще в индексе | Элемент скрыт, URL может жить | Запись исчезает, нужен редирект отдельно |
| Массовая очистка 5000+ ID | Быстрее, но база растет | Нужен бэкап и батчи |
Делайте: деактивируйте, если карточка может понадобиться снова. Не делайте: Delete на проде без бэкапа сайта перед массовой чисткой.
Что такое CIBlockElement::Delete и когда он нужен
CIBlockElement::Delete принимает числовой ID и убирает запись: поля, свойства, разделы, поисковый индекс. Метод статический - без new CIBlockElement, в отличие от Add. Нужен, когда запись должна исчезнуть навсегда. Не нужен, если хватит ACTIVE=N через Update.
Выполните минимальный Delete: модуль, ID и проверка результата
- Модуль и проверка:
Loader::includeModule('iblock'), затемCIBlockElement::GetByID($id)- убедитесь, что элемент есть и IBLOCK_ID верный. - Вызов:
$ok = CIBlockElement::Delete((int)$id);- ID всегда приводите к int. - Результат: при
$ok === trueGetByID пуст; при false читайте$APPLICATION->GetException()илиCIBlockElement::GetLastError(). - Ловушка missing ID: по официальной справке Delete для несуществующего ID тоже возвращает true. Сверяйте GetByID до и после.
<?php
use Bitrix\Main\Loader;
Loader::includeModule('iblock');
$id = 1542;
if (!CIBlockElement::GetByID($id)->Fetch()) {
echo "Элемента $id уже нет";
return;
}
if (!CIBlockElement::Delete($id)) {
global $APPLICATION;
$ex = $APPLICATION->GetException();
echo $ex ? $ex->GetString() : CIBlockElement::GetLastError();
}
Делайте: логируйте ID и текст ошибки в cron. Не делайте: считать каждый true за реально удаленную карточку без проверки существования.
Настройте OnBeforeIBlockElementDelete и OnAfterIBlockElementDelete
OnBeforeIBlockElementDelete срабатывает до удаления; в handler только $ID - для фильтра по IBLOCK_ID сделайте GetByID. Отмена: ThrowException + return false. Без ThrowException админка пишет generic "Ошибка удаления". OnAfterIBlockElementDelete - после успеха, $arFields для пост-обработки. Симметрия Update - OnBeforeIBlockElementUpdate, регистрация - гайд по событиям.
// OnBefore: запрет удаления из каталога IBLOCK_ID=7
function myOnBeforeDelete($ID) {
$el = CIBlockElement::GetByID($ID)->Fetch();
if ($el && (int)$el['IBLOCK_ID'] === 7) {
global $APPLICATION;
$APPLICATION->ThrowException('Удаление из каталога запрещено политикой');
return false;
}
return true;
}
В реальном проекте перед отменой в OnBefore после SetPropertyValuesEx вызовите $DB->Commit(). Делайте: grep OnBeforeIBlockElementDelete при false. Не делайте: return false без ThrowException.
Удалите связанные данные: свойства, файлы и торговые предложения
Delete убирает свойства, разделы и поисковый индекс. Файлы в b_file иногда остаются сиротами - при чистке диска проверьте CFile::Delete до Delete.
Торговые предложения (SKU, offers) с родителем не удаляются каскадом. Для товара каталога порядок такой:
Схема для товара с SKU:
CCatalogSKU::getOffersList(productIds) → CIBlockElement::Delete(каждый offer ID) → CIBlockElement::Delete(product ID)
Делайте: сначала offers, потом родитель. Не делайте: Delete только product - SKU останутся сиротами.
Организуйте массовое удаление через GetList и cron без timeout
- Выборка: CIBlockElement::GetList с фильтром ACTIVE=N и DATE_CREATE старше N дней. Батчи по 50-100 через nTopCount или limit+offset.
- Цикл: foreach по Fetch - Delete((int)$ar['ID']), счетчик успехов и ошибок.
- Транзакции и CLI: каждые ~100 элементов Commit; prolog_before в отдельном php-файле, запуск из cron, не из браузера.
- Лог: файл или таблица с ID, результатом и текстом GetException - иначе "молчаливый" false повторится.
$rs = CIBlockElement::GetList(
['ID' => 'ASC'],
['IBLOCK_ID' => 7, 'ACTIVE' => 'N'],
false,
['nTopCount' => 100],
['ID']
);
while ($row = $rs->Fetch()) {
if (!CIBlockElement::Delete((int)$row['ID'])) {
// лог ID + GetException
}
}
В D7 ElementTable::delete() нет - канон CIBlockElement::Delete. Делайте: бэкап и тест на 10 ID. Не делайте: прогон на весь каталог без лимита.
Проверьте чек-лист, если Delete не работает или элемент остался
- false без текста: includeModule('iblock'), GetException после false, grep OnBeforeIBlockElementDelete.
- Обработчик блокирует: ThrowException в handler или временный лог в начале функции с $ID.
- true, но элемент на месте: перепутали с ACTIVE=N, другой ID или несуществующий ID - GetByID до и после.
- Каталог с SKU и медленный cron: offers через getOffersList; уменьшите батч, отключите лишние OnAfter на dev.
Аудит скрипта очистки или обмена - обсудите проект, примеры внедрений - портфолио. Делайте: dry-run GetList без Delete на первом шаге. Не делайте: путать CMS Delete с lists.element.delete из облачного REST API.
Автор: Максим Мольков, разработчик 1С-Битрикс.
Источники: CIBlockElement::Delete, OnBeforeIBlockElementDelete, товары и SKU, практика внедрений CMS.
Частые вопросы
Как удалить элемент инфоблока программно в Битрикс?
Loader::includeModule('iblock'), затем CIBlockElement::Delete((int)$ID). Успех - true и элемент не находится через GetByID. При false читайте $APPLICATION->GetException() или CIBlockElement::GetLastError(). Для каталога сначала удалите offers через CCatalogSKU::getOffersList.
Чем Delete отличается от ACTIVE=N?
Delete безвозвратно убирает запись из базы вместе со свойствами и привязками. ACTIVE=N через Update только скрывает элемент: восстановить можно обратным Update. Для временной паузы в продажах выбирайте деактивацию, для мусора после импорта - Delete с бэкапом.
Можно ли отменить удаление через OnBeforeIBlockElementDelete?
Да. В обработчике OnBeforeIBlockElementDelete вызовите $APPLICATION->ThrowException('текст'); и return false. Delete прервется, элемент останется. После успешного Delete откат средствами API нет - только из бэкапа.
Удаляются ли файлы свойств при CIBlockElement::Delete?
Значения свойств удаляются, но файлы в b_file не всегда чистятся автоматически. Для свойств типа "Файл" и картинок PREVIEW/DETAIL при критичной очистке диска проверьте CFile::Delete или del в SetPropertyValuesEx до Delete.
Как массово удалить неактивные элементы инфоблока?
CIBlockElement::GetList с фильтром ACTIVE=N и батчем nTopCount, цикл Delete по ID, лог ошибок, cron через CLI. Перед первым прогоном - бэкап и тест на staging. Транзакции каждые ~100 записей снижают риск обрыва по timeout.
Почему CIBlockElement::Delete возвращает false?
Частые причины: не подключен модуль iblock, OnBeforeIBlockElementDelete вернул false, недостаточно прав, не удалены связанные offers. Текст - в GetException; если пусто - ищите обработчик без ThrowException в init.php.
Нужно ли удалять торговые предложения SKU отдельно?
Да. Offers не удаляются каскадом вместе с родительским товаром. Порядок: CCatalogSKU::getOffersList → CIBlockElement::Delete для каждого offer → CIBlockElement::Delete для product. Парный гайд по созданию - CIBlockElement::Add в статье про Add.