Классическая задача: у одной Статьи может быть много Тэгов, и один Тэг может быть привязан к многим Статьям. В реляционных базах данных это решается через промежуточную таблицу.
Структура таблиц
- b_article (ID, NAME) — Статьи.
- b_tag (ID, NAME) — Тэги.
- b_article_tag (ARTICLE_ID, TAG_ID) — Промежуточная таблица.
Шаг 1: Создание сущностей
Сущности для Статей и Тэгов стандартные. Интерес представляет промежуточная сущность ArticleTagTable.
namespace My\Module;
use Bitrix\Main\Entity;
class ArticleTagTable extends Entity\DataManager
{
public static function getTableName()
{
return 'b_article_tag';
}
public static function getMap()
{
return [
// Поле ID статьи
new Entity\IntegerField('ARTICLE_ID', [
'primary' => true
]),
// Связь со статьей
new Entity\ReferenceField(
'ARTICLE',
'\My\Module\ArticleTable',
['=this.ARTICLE_ID' => 'ref.ID']
),
// Поле ID тега
new Entity\IntegerField('TAG_ID', [
'primary' => true
]),
// Связь с тегом
new Entity\ReferenceField(
'TAG',
'\My\Module\TagTable',
['=this.TAG_ID' => 'ref.ID']
),
];
}
}Шаг 2: Добавление связей в основные сущности
Чтобы удобно выбирать тэги из статьи, добавим связь «Один-ко-Многим» в ArticleTable.
// В ArticleTable::getMap()
new Entity\ReferenceField(
'TAG_RELATION', // Виртуальное поле связи
'\My\Module\ArticleTagTable', // Промежуточная таблица
['=this.ID' => 'ref.ARTICLE_ID']
),Шаг 3: Выборка (Получить статьи с их тегами)
Это самое интересное. Чтобы получить названия тегов, нам нужно пройти через две связи: Article -> ArticleTag -> Tag.
$articles = \My\Module\ArticleTable::getList([
'select' => [
'ID',
'NAME',
'TAG_NAME' => 'TAG_RELATION.TAG.NAME' // Двойной переход!
]
])->fetchCollection();
foreach ($articles as $article) {
// Внимание: из-за JOIN это вернет декартово произведение.
// Если у статьи 3 тега, будет 3 строки в выборке с одинаковым ID статьи.
// D7 Collection умеет это "склеивать" (см. статью про Коллекции).
}Оптимизированный подход (раздельные запросы):
Лучше выбрать статьи, собрать их ID, а потом отдельным запросом выбрать все теги для этих статей через ArticleTagTable.
// 1. Получаем статьи
$articles = ArticleTable::getList(...)->fetchAll();
$articleIds = array_column($articles, 'ID');
// 2. Получаем теги
$tags = ArticleTagTable::getList([
'select' => ['ARTICLE_ID', 'TAG_NAME' => 'TAG.NAME'],
'filter' => ['@ARTICLE_ID' => $articleIds]
])->fetchAll();
// 3. Собираем в PHPВывод:
Связь N:M в D7 реализуется явно через сущность промежуточной таблицы. Хотя D7 позволяет строить цепочки TAG_RELATION.TAG.NAME в одном запросе, для производительности часто лучше разбивать это на два простых запроса или использовать метод QueryHelper::decompose .
Многие ко многим D7, ManyToMany Bitrix, промежуточная таблица ORM, связи ORM, ReferenceField, getList N:M.