Как удаляются связи OneToMany и ManyToMany в D7 ORM
Проблема/контекст
В D7-связях удаление часто понимают слишком упрощенно: если сущности связаны, значит ядро само "разрулит" каскад. Но в исходниках ORM поведение у OneToMany и ManyToMany принципиально разное, и ошибка в ожиданиях быстро приводит либо к лишнему удалению, либо к зависшим данным.
Ключевая точка здесь Bitrix\Main\ORM\Objectify\EntityObject::delete(). Для OneToMany метод смотрит на getCascadeDeletePolicy() и либо удаляет дочерние объекты, либо снимает ссылку через sysRemoveAllFromCollection(). Для ManyToMany логика иная: при удалении объекта ядро всегда очищает таблицу-посредник, а сами связанные сущности не удаляет. Это напрямую следует из связки EntityObject::delete(), sysRemoveAllFromCollection() и sysSaveRelations().
Решение с кодом
Если дочерняя запись принадлежит только одному владельцу, используйте OneToMany и явно задавайте политику удаления:
<?php
use Bitrix\Main\ORM\Fields\Relations\CascadePolicy;
use Bitrix\Main\ORM\Fields\Relations\OneToMany;
final class OrderTable extends \Bitrix\Main\ORM\Data\DataManager
{
public static function getMap(): array
{
return [
// При удалении заказа удалятся и его позиции
(new OneToMany('ITEMS', OrderItemTable::class, 'ORDER'))
->configureCascadeDeletePolicy(CascadePolicy::FOLLOW),
];
}
}
Если FOLLOW не указан, у OneToMany по умолчанию работает SET_NULL: ядро не удаляет дочерние строки, а отвязывает их от родителя. Это удобно для журналов, уведомлений и других записей, которые могут жить дольше основной сущности.
Для общих справочников и тегов используйте ManyToMany, но помните реальное поведение удаления:
<?php
use Bitrix\Main\ORM\Fields\Relations\ManyToMany;
final class ArticleTable extends \Bitrix\Main\ORM\Data\DataManager
{
public static function getMap(): array
{
return [
// При удалении статьи ядро очистит только b_article_tag
(new ManyToMany('TAGS', TagTable::class))
->configureTableName('b_article_tag'),
];
}
}
После $article->delete() будут удалены строки из b_article_tag, потому что sysSaveRelations() удаляет записи посредника через data class медиаторной сущности. Сами теги останутся. Поэтому ManyToMany подходит там, где связь должна исчезнуть, а партнерская сущность продолжает использоваться в системе. Если же нужно удалять и "осиротевшие" записи, не полагайтесь на ManyToMany: описывайте таблицу связи явно или запускайте отдельный сервис очистки после удаления основной сущности.
Итог
Для OneToMany в D7 важно выбирать политику FOLLOW или SET_NULL осознанно. Для ManyToMany безопасно рассчитывать только на очистку таблицы связей, а не на удаление связанных объектов.