Полный цикл в digital

Отношения между сущностями

В ORM отношения между сущностями описывают, как данные одной таблицы связаны с данными другой. Основные типы отношений:

  1. 1:1, «один к одному». Каждая запись в первой таблице соответствует одной записи во второй таблице, подробнее
  2. 1:N, «один ко многим». Одна запись в первой таблице может быть связана с несколькими записями во второй таблице, подробнее
  3. N:M, «многие ко многим». Записи в первой таблице могут быть связаны с несколькими записями во второй таблице и наоборот, подробнее
  4. Отношения со вспомогательными данными, подробнее

Пример схемы отношений

Рассмотрим пример с сущностями: книга Book, автор Author, издательство Publisher, обложка Cover и магазин Store. Схема отношений сущностей:

  • Книга принадлежит одному издательству
  • У книги может быть несколько авторов
  • Одна книга может иметь только одну обложку, и одна обложка принадлежит только одной книге
  • Книга продается в нескольких магазинах

Отношение 1: N, «один ко многим»

Одна запись в таблице может соответствовать множеству записей в другой. В Bitrix Framework для описания таких связей используются специальные конструкторы:

Параметры конструктора Reference

  • $name имя поля
  • $referenceEntity класс связываемой сущности
  • $referenceFilter условия джойна, то есть объединения таблиц. Используются префиксы this. и ref. для указания принадлежности к текущей и связываемой сущности. Доступные типы джойнов: INNER JOIN, LEFT JOIN, RIGHT JOIN. По умолчанию используется LEFT JOIN. Для изменения типа джойна используйте метод configureJoinType. Параметр необязательный и служит для добавления дополнительных условий фильтрации при соединении таблиц

Параметры конструктора OneToMany

  • $name имя поля
  • $referenceEntity класс связываемой сущности
  • $referenceFilter имя поля Reference в сущности-партнере

Пример связи многие к одному

Каждая книга издается в одном издательстве, что создает отношение «N книг — 1 издательство». Для реализации этой связи необходимо:

  • Добавить поле PUBLISHER_ID в таблицу книг
  • Создать связь между таблицами книг и издательств.
Определить поле PUBLISHER_ID

Добавим поле PUBLISHER_ID в класс BookTable. Это поле будет указывать на конкретное издательство.

PUBLISHER_ID это внешний ключ, связывающий таблицу книг с таблицей издательств.

namespace Bitrix\Main\Test\Typography;

use Bitrix\Main\ORM\Fields\IntegerField;

class BookTable extends \Bitrix\Main\ORM\Data\DataManager
{
    public static function getMap()
    {
        return [
            // Поле PUBLISHER_ID, целочисленное
            new IntegerField('PUBLISHER_ID'),
        ];
    }
}
Определить связь «многие к одному»

Для описания связи «книги — издательство» используем класс Reference из пространства имен Bitrix\Main\ORM\Fields\Relations. Этот класс позволяет связать две таблицы по определенному условию.

Условие соединения Join::on указывает, что поле PUBLISHER_ID в таблице книг должно соответствовать полю ID в таблице издательств.

namespace Bitrix\Main\Test\Typography;

use Bitrix\Main\ORM\Fields\IntegerField;
use Bitrix\Main\ORM\Fields\Relations\Reference;
use Bitrix\Main\ORM\Query\Join;

class BookTable extends \Bitrix\Main\ORM\Data\DataManager
{
    public static function getMap()
    {
        return [
            // Поле PUBLISHER_ID, целочисленное
            new IntegerField('PUBLISHER_ID'),

            // Определение связи с таблицей PublisherTable
            new Reference(
                // Имя связи
                'PUBLISHER',
                // Класс связанной таблицы
                PublisherTable::class,
                // Условие соединения: связывает PUBLISHER_ID с ID в PublisherTable
                Join::on('this.PUBLISHER_ID', 'ref.ID')
            // Установка типа соединения: INNER JOIN
            )->configureJoinType('inner'),
        ];
    }
}
Получить объект издательства через книгу

Чтобы получить объект сущности Publisher через объект Book, используем метод getPublisher(). Этот метод автоматически загружает связанное издательство.

$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1, [
    // Выбор всех полей книги и связанных данных издательства
    'select' => ['*', 'PUBLISHER'],
])->fetchObject();
// Вывод названия издательства, ожидаемый вывод: Publisher Title 253
echo $book->getPublisher()->getTitle();
Установить связь между книгой и издательством

Чтобы установить связь между книгой и издательством, передадим объект Publisher в сеттер setPublisher(). Значение поля PUBLISHER_ID заполнится автоматически.

// Инициализация объекта издательства
$publisher = \Bitrix\Main\Test\Typography\PublisherTable::wakeUpObject(253);
// Метод wakeUpObject создает объект с заданным первичным ключом без загрузки данных из базы

// Инициализация объекта книги
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)->fetchObject();
// Метод getByPrimary загружает объект книги с первичным ключом 1 из базы данных

// Установка значения объекта издательства для книги
$book->setPublisher($publisher);
// Метод setPublisher устанавливает связь между книгой и издательством

// Сохранение изменений в базе данных, метод save сохраняет изменения в объекте книги, включая обновленную связь с издательством
$book->save();
Результат для массивов

Если требуется получить данные в виде массива, результат будет содержать все поля книги и связанные данные издательства.

$result = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1, [
    // Выбор всех полей книги и связанных данных издательства
    'select' => ['*', 'PUBLISHER'],
]);

print_r($result->fetch());
/* Выведет:
Array (
    [ID] => 1
    [TITLE] => Title 1
    [PUBLISHER_ID] => 253
    [ISBN] => 978-3-16-148410-0
    [IS_ARCHIVED] => Y
    [MAIN_TEST_TYPOGRAPHY_BOOK_PUBLISHER_ID] => 253
    [MAIN_TEST_TYPOGRAPHY_BOOK_PUBLISHER_TITLE] => Publisher Title 253
)
*/
Как использовать алиасы для полей

По умолчанию поля связанной сущности получают уникальные имена, основанные на пространстве имен и имени класса. Чтобы сделать имена более короткими и удобными, можно использовать алиасы. Например, вместо MAIN_TEST_TYPOGRAPHY_BOOK_PUBLISHER_TITLE можно использовать PUB_TITLE.

$result = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1, [
    // Поля издательства будут представлены в виде алиасов с префиксом PUB_
    'select' => ['*', 'PUB_' => 'PUBLISHER'],
]);

print_r($result->fetch());
/* Выведет:
Array (
    [ID] => 1
    [TITLE] => Title 1
    [PUBLISHER_ID] => 253
    [ISBN] => 978-3-16-148410-0
    [IS_ARCHIVED] => Y
    [PUB_ID] => 253
    [PUB_TITLE] => Publisher Title 253
)
*/

Пример связи «один ко многим»

Для двунаправленной связи «один ко многим» рассмотрим пример взаимодействия между двумя таблицами: издательство Publisher и книги Book. Каждое издательство может иметь множество книг, что соответствует типу связи OneToMany.

Определить класс PublisherTable

Создадим класс PublisherTable, который представляет собой таблицу издательств в базе данных. Этот класс наследуется от DataManager основного класса для работы с данными в Bitrix Framework.

namespace Bitrix\Main\Test\Typography;
// Импорт класса DataManager для работы с данными
use Bitrix\Main\ORM\Data\DataManager;
// Импорт класса OneToMany для создания связей «один ко многим»
use Bitrix\Main\ORM\Fields\Relations\OneToMany;

// Класс PublisherTable наследует DataManager для работы с данными
class PublisherTable extends DataManager
{
    // Метод для определения карты полей таблицы
    public static function getMap()
    {
        return [
            // Определение связи «один ко многим» с таблицей BookTable
            (new OneToMany('BOOKS', BookTable::class, 'PUBLISHER'))
                // Установка типа соединения: INNER JOIN
                ->configureJoinType('inner')
        ];
    }
}
Выбрать данные через связь

Используем связь, чтобы получить данные из таблицы издательств и связанных с ними книг.

Метод getByPrimary возвращает запись по первичному ключу в данном случае ID=253.

Цикл foreach проходит по всем книгам, связанным с текущим издательством, и выводит их названия.

$publisher = \Bitrix\Main\Test\Typography\PublisherTable::getByPrimary(253, [
    // Выбор всех полей издательства и связанных данных книг
    'select' => ['*', 'BOOKS']
])->fetchObject();

// Перебор всех книг, связанных с издательством, цикл выведет "Title 1" и "Title 2"
foreach ($publisher->getBooks() as $book) {
    // Вывод названия каждой книги
    echo $book->getTitle();
}

В объектной модели все связанные данные автоматически объединяются в одну коллекцию. Например, если у издательства есть две книги, результат будет представлен в виде одного объекта, содержащего коллекцию этих книг.

Получить данные в виде массива

Если запросить данные в виде массива, то результат будет содержать дублирование данных об издательстве для каждой связанной книги.

$data = \Bitrix\Main\Test\Typography\PublisherTable::getByPrimary(253, [
    // Выбор всех полей издательства и связанных данных книг с префиксом
    'select' => ['*', 'BOOK_' => 'BOOKS']
])->fetchAll();

// Вернет
Array (
	[0] => Array (
		[ID] => 253
		[TITLE] => Publisher Title 253
		[BOOK_ID] => 2
		[BOOK_TITLE] => Title 2
		[BOOK_PUBLISHER_ID] => 253
		[BOOK_ISBN] => 456-1-05-586920-1
		[BOOK_IS_ARCHIVED] => N
	)
	[1] => Array (
		[ID] => 253
		[TITLE] => Publisher Title 253
		[BOOK_ID] => 1
		[BOOK_TITLE] => Title 1
		[BOOK_PUBLISHER_ID] => 253
		[BOOK_ISBN] => 978-3-16-148410-0
		[BOOK_IS_ARCHIVED] => Y
	)
)
Как добавить объект

Чтобы добавить новую книгу в коллекцию книг издательства, используйте метод addToBooks. Этот метод добавляет существующую книгу в коллекцию. Если книги еще нет в базе данных, она должна быть создана заранее.

// Инициализация объекта издательства, метод getByPrimary загружает объект издательства с первичным ключом 253 из базы данных
$publisher = \Bitrix\Main\Test\Typography\PublisherTable::getByPrimary(253)
    ->fetchObject();

// Инициализация объекта книги, метод getByPrimary загружает объект книги с первичным ключом 2 из базы данных
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(2)
    ->fetchObject();

// Добавление книги в коллекцию книг издательства, метод addToBooks добавляет объект книги в коллекцию книг, связанных с издательством
$publisher->addToBooks($book);

// Сохранение изменений в базе данных, метод save сохраняет изменения в объекте издательства, включая обновленную коллекцию книг
$publisher->save();

Для работы с массивами необходимо использовать стандартный метод ORM add.

Как удалить связь

Чтобы удалить связь со стороны книги, используйте метод setPublisher(null) для разрыва связи между книгой и издательством. Это не удаляет книгу из базы данных.

Чтобы удалить связь со стороны издательства, используйте методы removeFromBooks или removeAllBooks.

// Инициализация объекта книги, метод wakeUp создает объект книги с заданным идентификатором без загрузки данных из базы
$book = \Bitrix\Main\Test\Typography\Book::wakeUp(2);

// Инициализация объекта издательства
$publisher = \Bitrix\Main\Test\Typography\PublisherTable::getByPrimary(253, [
    // Выбор всех полей издательства и связанных данных книг
    'select' => ['*', 'BOOKS']
])->fetchObject();

// Удаление одной конкретной книги из коллекции книг издательства, метод removeFromBooks удаляет связь между издательством и указанной книгой
$publisher->removeFromBooks($book);

// Удаление всех книг из коллекции книг издательства, метод removeAllBooks удаляет все связи между издательством и его книгами
$publisher->removeAllBooks();

// Сохранение изменений в базе данных, метод save сохраняет изменения в объекте издательства, обновляя поле PUBLISHER_ID в книгах на пустое значение
$publisher->save();

Операции removeFrom и removeAll недоступны для массивов, потому что они требуют объектной модели для управления связями. Для работы с массивами необходимо использовать стандартные методы ORM update и delete.

Как проверить заполнение поля отношений

Если вы не уверены, что поле отношений уже загружено, используйте метод fill.

// Инициализация объекта книги, метод wakeUpObject создает объект книги с заданным идентификатором без загрузки данных из базы
$book = \Bitrix\Main\Test\Typography\BookTable::wakeUpObject(2);

// Инициализация объекта издательства с заполнением только первичного ключа, метод wakeUpObject создает объект издательства с заданным идентификатором без загрузки данных из базы
$publisher = \Bitrix\Main\Test\Typography\PublisherTable::wakeUpObject(253);

// Заполнение поля отношения книг, метод fillBooks загружает связанные книги для данного издательства
$publisher->fillBooks();

// Удаление конкретной книги из коллекции книг издательства, метод removeFromBooks удаляет связь между издательством и указанной книгой
$publisher->removeFromBooks($book);

// Удаление всех книг из коллекции книг издательства, метод removeAllBooks удаляет все связи между издательством и его книгами
$publisher->removeAllBooks();

// Сохранение изменений в базе данных, метод save сохраняет изменения в объекте издательства, обновляя поле PUBLISHER_ID в книгах на пустое значение
$publisher->save();

Отношение 1:1, «один к одному»

Отношения 1:1 работают так же, как и 1:N, но с одним отличием: в обеих сущностях вместо пары Reference + OneToMany используются только поля Reference.

Пример связи «один к одному»

Рассмотрим пример, где у каждой книги есть уникальная обложка. Это отношение «один к одному», так как одна книга может иметь только одну обложку, и одна обложка принадлежит только одной книге.

Описать сущность Book

Класс BookTable представляет таблицу книг в базе данных. Он содержит следующие поля:

  • ID первичный ключ таблицы, который автоматически заполняется при создании новой записи
  • TITLE строка, содержащая название книги
  • COVER_ID целое число, которое будет использоваться для связи с таблицей обложек CoverTable
namespace Bitrix\Main\Test\Typography;

use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields\IntegerField;
use Bitrix\Main\ORM\Fields\StringField;

// Класс BookTable наследует DataManager для работы с данными
class BookTable extends DataManager
{
    // Метод для получения имени таблицы
    public static function getTableName()
    {
        // Имя таблицы, которая хранит данные о книгах
        return 'b_book';
    }

    // Метод для определения карты полей таблицы
    public static function getMap()
    {
        return [
            // Определение поля ID как первичного ключа с автозаполнением
            (new IntegerField('ID'))
                // Устанавливает поле как первичный ключ
                ->configurePrimary(true)
                // Включает автозаполнение
                ->configureAutocomplete(true),

            // Определение обязательного строкового поля TITLE
            (new StringField('TITLE'))
                // Устанавливает поле как обязательное
                ->configureRequired(true),

            // Определение целочисленного поля COVER_ID, это поле может использоваться как ссылка на обложку книги
            (new IntegerField('COVER_ID'))
        ];
    }
}
Описать сущность Cover

Класс CoverTable представляет таблицу обложек в базе данных. Он содержит следующие поля:

  • ID первичный ключ таблицы, который автоматически заполняется при создании новой записи
  • IMAGE_URL строка, содержащая URL изображения обложки
namespace Bitrix\Main\Test\Typography;

use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields\IntegerField;
use Bitrix\Main\ORM\Fields\StringField;

// Класс CoverTable наследует DataManager для работы с данными
class CoverTable extends DataManager
{
    // Метод для получения имени таблицы
    public static function getTableName()
    {
        // Имя таблицы, которая хранит данные об обложках
        return 'b_cover';
    }

    // Метод для определения карты полей таблицы
    public static function getMap()
    {
        return [
            // Определение поля ID как первичного ключа с автозаполнением
            (new IntegerField('ID'))
                // Устанавливает поле как первичный ключ
                ->configurePrimary(true)
                // Включает автозаполнение для поля
                ->configureAutocomplete(true),
            // Определение строкового поля IMAGE_URL, Это поле хранит URL изображения обложки
            (new StringField('IMAGE_URL'))
        ];
    }
}
Настроить связь «один к одному»

Для связи «один к одному» между таблицами BookTable и CoverTable используется поле Reference. Это поле позволяет создать связь между таблицами через указанные условия соединения.

namespace Bitrix\Main\Test\Typography;
// Импорт класса Reference для создания связей между таблицами
use Bitrix\Main\ORM\Fields\Relations\Reference;
// Импорт класса Join для определения условий соединения
use Bitrix\Main\ORM\Query\Join;

// Класс BookTable наследует DataManager для работы с данными
class BookTable extends DataManager
{
    // Метод для определения карты полей таблицы
    public static function getMap()
    {
        return [
            // Определение поля ID как первичного ключа с автозаполнением
            (new IntegerField('ID'))
                // Устанавливает поле как первичный ключ
                ->configurePrimary(true)
                // Включает автозаполнение для поля
                ->configureAutocomplete(true),

            // Определение обязательного строкового поля TITLE
            (new StringField('TITLE'))
                // Устанавливает поле как обязательное
                ->configureRequired(true),

            // Определение целочисленного поля COVER_ID, это поле используется как ссылка на обложку
            (new IntegerField('COVER_ID')),

            // Определение связи с таблицей CoverTable
            (new Reference(
                // Имя связи
                'COVER',
                // Класс таблицы, с которой устанавливается связь
                CoverTable::class,
                // Условие соединения: связывает COVER_ID с ID в CoverTable
                Join::on('this.COVER_ID', 'ref.ID')
            ))
                // Устанавливает тип соединения как внутреннее (inner join), это означает, что будут выбраны только те записи, у которых есть соответствующая обложка
                ->configureJoinType('inner')
        ];
    }
}

Отношение N: M, «многие ко многим»

Для описания отношения «многие ко многим» используется поле ManyToMany, которое автоматически создает временную сущность для работы с промежуточной таблицей. Эта сущность скрыта от пользователя, но позволяет добавлять, удалять и обновлять связи.

В конструктор ManyToMany передаются название поля и класс партнерской сущности. Для простых отношений достаточно вызвать метод configureTableName и указать имя таблицы, в которой хранятся связующие данные.

Если кроме первичных ключей исходных сущностей есть дополнительные данные, используйте вместо поля ManyToMany тип OneToMany.

Пример простых отношений без вспомогательных данных

Рассмотрим пример, где у книги может быть несколько авторов, а у автора — несколько книг. Для этого создадим две таблицы: книги BookTable и авторы AuthorTable.

Описать связь «книга — автор»

Используемые параметры:

  • AUTHORS имя свойства, через которое будет осуществляться доступ к связанным авторам
  • AuthorTable::class класс таблицы, с которой устанавливается связь
  • configureTableName('b_book_author') имя промежуточной таблицы, хранящей связи
bitrix/modules/main/lib/test/typography/booktable.phpnamespace Bitrix\Main\Test\Typography;

// Импорт класса ManyToMany для создания связей «многие ко многим»
use Bitrix\Main\ORM\Fields\Relations\ManyToMany;

// Класс BookTable наследует DataManager для работы с данными
class BookTable extends \Bitrix\Main\ORM\Data\DataManager
{
    // Метод для определения карты полей таблицы
    public static function getMap()
    {
        return [
            // Определение связи «многие ко многим» с таблицей AuthorTable
            (new ManyToMany('AUTHORS', AuthorTable::class))
                // Указание имени таблицы-связки
                ->configureTableName('b_book_author')
        ];
    }
}
Описать связь «автор — книга»

Используемые параметры:

  • BOOKS имя свойства, через которое будет осуществляться доступ к связанным книгам
  • BookTable::class класс таблицы, с которой устанавливается связь
  • configureTableName('b_book_author') имя промежуточной таблицы, хранящей связи
bitrix/modules/main/lib/test/typography/authortable.phpnamespace Bitrix\Main\Test\Typography;

// Импорт класса ManyToMany для создания связей «многие ко многим»
use Bitrix\Main\ORM\Fields\Relations\ManyToMany;

// Класс AuthorTable наследует DataManager для работы с данными
class AuthorTable extends \Bitrix\Main\ORM\Data\DataManager
{
    // Метод для определения карты полей таблицы
    public static function getMap()
    {
        return [
            // Определение связи «многие ко многим» с таблицей BookTable
            (new ManyToMany('BOOKS', BookTable::class))
                // Указание имени таблицы-связки
                ->configureTableName('b_book_author')
        ];
    }
}
Как выглядит автоматически созданная временная сущность

В памяти автоматически создается временная сущность для работы с промежуточной таблицей. Это типовая сущность с направленными отношениями 1:N к исходным сущностям-партнерам, где:

  • BOOK_ID и AUTHOR_ID первичные ключи из таблиц BookTable и AuthorTable
  • Reference ссылки на исходные таблицы
class ... extends \Bitrix\Main\ORM\Data\DataManager
{
    // Метод для получения имени таблицы
    public static function getTableName()
    {
        // Имя таблицы-связки
        return 'b_book_author';
    }

    // Метод для определения карты полей таблицы
    public static function getMap()
    {
        return [
            // Определение поля BOOK_ID как первичного ключа
            (new IntegerField('BOOK_ID'))
                ->configurePrimary(true),

            // Определение ссылки на таблицу BookTable
            (new Reference('BOOK', BookTable::class,
                Join::on('this.BOOK_ID', 'ref.ID')))
                ->configureJoinType('inner'),

            // Определение поля AUTHOR_ID как первичного ключа
            (new IntegerField('AUTHOR_ID'))
                ->configurePrimary(true),

            // Определение ссылки на таблицу AuthorTable
            (new Reference('AUTHOR', AuthorTable::class,
                Join::on('this.AUTHOR_ID', 'ref.ID')))
                ->configureJoinType('inner'),
        ];
    }
}

Имена полей формируются на основе имен сущностей и их первичных ключей.

new IntegerField('BOOK_ID') - snake_case от Book + primary поле ID
new Reference('BOOK') - snake_case от Book
new IntegerField('AUTHOR_ID') - snake_case от Author + primary поле ID
new Reference('AUTHOR') - snake_case от Author
Как явно задать имена полей

Если необходимо явно задать имена полей или работать со сложными первичными ключами, используйте конфигурирующие методы configure.

Метод configureLocalPrimary указывает, как будет называться привязка к полю из первичного ключа текущей сущности. Аналогично, configureRemotePrimary задает соответствие полей первичного ключа сущности-партнера. Методы configureLocalReference и configureRemoteReference задают имена референсов к исходным сущностям.

Описание связи «книга — автор»:

bitrix/modules/main/lib/test/typography/booktable.phpnamespace Bitrix\Main\Test\Typography;

// Импорт класса ManyToMany для создания связей «многие ко многим»
use Bitrix\Main\ORM\Fields\Relations\ManyToMany;

// Класс BookTable наследует DataManager для работы с данными
class BookTable extends \Bitrix\Main\ORM\Data\DataManager
{
    // Метод для определения карты полей таблицы
    public static function getMap()
    {
        return [
            // Определение связи "многие ко многим" с таблицей AuthorTable
            (new ManyToMany('AUTHORS', AuthorTable::class))
                // Указание имени таблицы-связки
                ->configureTableName('b_book_author')
                // Указание локального первичного ключа и его соответствия в таблице-связке
                ->configureLocalPrimary('ID', 'MY_BOOK_ID')
                // Указание локальной ссылки
                ->configureLocalReference('MY_BOOK')
                // Указание удаленного первичного ключа и его соответствия в таблице-связке
                ->configureRemotePrimary('ID', 'MY_AUTHOR_ID')
                // Указание удаленной ссылки
                ->configureRemoteReference('MY_AUTHOR')
        ];
    }
}

Описание связи «автор — книга»:

bitrix/modules/main/lib/test/typography/authortable.phpnamespace Bitrix\Main\Test\Typography;

// Импорт класса ManyToMany для создания связей «многие ко многим»
use Bitrix\Main\ORM\Fields\Relations\ManyToMany;

// Класс AuthorTable наследует DataManager для работы с данными
class AuthorTable extends \Bitrix\Main\ORM\Data\DataManager
{
    // Метод для определения карты полей таблицы
    public static function getMap()
    {
        return [
            // Определение связи «многие ко многим» с таблицей BookTable
            (new ManyToMany('BOOKS', BookTable::class))
                // Указание имени таблицы-связки
                ->configureTableName('b_book_author')
                // Указание локального первичного ключа и его соответствия в таблице-связке
                ->configureLocalPrimary('ID', 'MY_AUTHOR_ID')
                // Указание локальной ссылки
                ->configureLocalReference('MY_AUTHOR')
                // Указание удаленного первичного ключа и его соответствия в таблице-связке
                ->configureRemotePrimary('ID', 'MY_BOOK_ID')
                // Указание удаленной ссылки
                ->configureRemoteReference('MY_BOOK')
        ];
    }
}

Системная сущность отношений:

class ... extends \Bitrix\Main\ORM\Data\DataManager
{
    // Метод для получения имени таблицы
    public static function getTableName()
    {
        // Имя таблицы-связки
        return 'b_book_author';
    }

    // Метод для определения карты полей таблицы
    public static function getMap()
    {
        return [
            // Определение поля MY_BOOK_ID как первичного ключа
            (new IntegerField('MY_BOOK_ID'))
                ->configurePrimary(true),

            // Определение ссылки на таблицу BookTable
            (new Reference('MY_BOOK', BookTable::class,
                Join::on('this.MY_BOOK_ID', 'ref.ID')))
                ->configureJoinType('inner'),

            // Определение поля MY_AUTHOR_ID как первичного ключа
            (new IntegerField('MY_AUTHOR_ID'))
                ->configurePrimary(true),

            // Определение ссылки на таблицу AuthorTable
            (new Reference('MY_AUTHOR', AuthorTable::class,
                Join::on('this.MY_AUTHOR_ID', 'ref.ID')))
                ->configureJoinType('inner'),
        ];
    }
}

Как и в случае с Reference и OneToMany, здесь можно переопределить тип джойна с помощью метода configureJoinType. По умолчанию используется тип LEFT.

(new ManyToMany('AUTHORS', AuthorTable::class))
    // Указание имени таблицы-связки
    ->configureTableName('b_book_author')
    // Указание типа соединения INNER JOIN
    ->configureJoinType('inner')
Как работать с данными

Работа с данными аналогична отношениям 1:N.

Добавление связи:

  • addToBooks добавляет книгу в коллекцию книг автора,
  • save сохраняет изменения в базе данных
$author = \Bitrix\Main\Test\Typography\AuthorTable::getByPrimary(17)
    // Получаем объект автора с ID 17
    ->fetchObject();

$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
    // Получаем объект книги с ID 1
    ->fetchObject();

// Добавляем книгу к автору
$author->addToBooks($book);
// Сохраняем изменения
$author->save();

Выборка данных:

  • 'select' => ['*', 'AUTHORS'] выбирает все поля книги и связанных авторов
  • getAuthors() возвращает коллекцию авторов
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(2, [
    // Выбираем все поля книги и связанных авторов
    'select' => ['*', 'AUTHORS']
])->fetchObject();

// Цикл выведет "Last name 17" и "Last name 18"
foreach ($book->getAuthors() as $author) {
    // Выводим фамилию каждого автора
    echo $author->getLastName();
}
Связь между объектами сущностей

Связь со стороны авторов:

$author = \Bitrix\Main\Test\Typography\AuthorTable::getByPrimary(17)
    // Получаем объект автора с ID 17
    ->fetchObject();

$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
    // Получаем объект книги с ID 1
    ->fetchObject();

// Добавляем книгу к автору
$author->addToBooks($book);
// Сохраняем изменения
$author->save();

Связь со стороны книг:

$author = \Bitrix\Main\Test\Typography\AuthorTable::getByPrimary(17)
    // Получаем объект автора с ID 17
    ->fetchObject();

$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
    // Получаем объект книги с ID 1
    ->fetchObject();

// Добавляем автора к книге
$book->addToAuthors($author);
// Сохраняем изменения
$book->save();

Пример отношений со вспомогательными данными

Когда есть дополнительные данные, отношение следует описывать отдельной сущностью. Например, кроме первичных ключей исходных сущностей STORE_ID и BOOK_ID есть еще количество книг QUANTITY.

Промежуточная таблица

В промежуточной таблице используются параметры:

  • STORE_ID и BOOK_ID первичные ключи из таблиц StoreTable и BookTable
  • QUANTITY дополнительное поле для хранения количества книг
namespace Bitrix\Main\Test\Typography;

use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields\IntegerField;
// Импорт класса Reference для создания ссылок на другие таблицы
use Bitrix\Main\ORM\Fields\Relations\Reference;
// Импорт класса Join для определения условий соединения
use Bitrix\Main\ORM\Query\Join;

// Класс StoreBookTable наследует DataManager для работы с данными
class StoreBookTable extends DataManager
{
    // Метод для получения имени таблицы
    public static function getTableName()
    {
        // Имя таблицы, которая хранит данные о книгах в магазинах
        return 'b_store_book';
    }

    // Метод для определения карты полей таблицы
    public static function getMap()
    {
        return [
            // Определение поля STORE_ID как первичного ключа
            (new IntegerField('STORE_ID'))
                ->configurePrimary(true),

            // Определение ссылки на таблицу StoreTable
            (new Reference('STORE', StoreTable::class,
                Join::on('this.STORE_ID', 'ref.ID')))
                ->configureJoinType('inner'),

            // Определение поля BOOK_ID как первичного ключа
            (new IntegerField('BOOK_ID'))
                ->configurePrimary(true),

            // Определение ссылки на таблицу BookTable
            (new Reference('BOOK', BookTable::class,
                Join::on('this.BOOK_ID', 'ref.ID')))
                ->configureJoinType('inner'),

            // Определение поля QUANTITY для хранения количества Книг
            (new IntegerField('QUANTITY'))
                // Установка значения по умолчанию
                ->configureDefaultValue(0)
        ];
    }
}

Использование ManyToMany ограничено, потому что это поле не предоставляет доступ к вспомогательным данным, таким как QUANTITY. С помощью removeFrom*() можно удалить связь, а с помощью addTo*() добавить связь со значением QUANTITY только по умолчанию, без возможности обновить его.

Как работать со вспомогательными данными

Для работы с вспомогательными данными используйте отдельную сущность-посредник.

// Объект книги
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
	->fetchObject();
// Объект магазина
$store = \Bitrix\Main\Test\Typography\StoreTable::getByPrimary(34)
	->fetchObject();
// Новый объект связи книги с магазином
$item = \Bitrix\Main\Test\Typography\StoreBookTable::createObject()
	->setBook($book)
	->setStore($store)
	->setQuantity(5);
// Сохранение
$item->save();

Обновить количество книг:

// Объект существующей связи
$item = \Bitrix\Main\Test\Typography\StoreBookTable::getByPrimary([
	'STORE_ID' => 33, 'BOOK_ID' => 2
])->fetchObject();
// Обновление количества
$item->setQuantity(12);
// Сохранение
$item->save();

Удалить связи:

// Объект существующей связи
$item = \Bitrix\Main\Test\Typography\StoreBookTable::getByPrimary([
	'STORE_ID' => 33, 'BOOK_ID' => 2
])->fetchObject();
// Удаление
$item->delete();

Для массивов используйте стандартные подходы по работе с данными:

// Добавление записи
\Bitrix\Main\Test\Typography\StoreBookTable::add([
	'STORE_ID' => 34, 'BOOK_ID' => 1, 'QUANTITY' => 5
]);
// Обновление записи
\Bitrix\Main\Test\Typography\StoreBookTable::update(
	['STORE_ID' => 34, 'BOOK_ID' => 1],
	['QUANTITY' => 12]
);
// Удаление записи
\Bitrix\Main\Test\Typography\StoreBookTable::delete(
	['STORE_ID' => 34, 'BOOK_ID' => 1]
);

В случае со вспомогательными данными используйте вместо поля ManyToMany тип OneToMany:

bitrix/modules/main/lib/test/typography/booktable.phpnamespace Bitrix\Main\Test\Typography;

// Импорт класса OneToMany для создания связи «один ко многим»
use Bitrix\Main\ORM\Fields\Relations\OneToMany;

// Класс BookTable наследует DataManager для работы с данными
class BookTable extends \Bitrix\Main\ORM\Data\DataManager
{
    public static function getMap() // Метод определяет карту полей таблицы
    {
        return [
            // Здесь могут быть другие поля и связи, определенные для таблицы книг

            // Определение связи «один ко многим» с таблицей StoreBookTable
            (new OneToMany('STORE_ITEMS', StoreBookTable::class, 'BOOK'))
            // 'STORE_ITEMS' — имя связи, используемое для доступа к связанным записям
            // StoreBookTable::class — класс таблицы, с которой устанавливается связь
            // 'BOOK' — поле в StoreBookTable, используемое для связи с таблицей книг
        ];
    }
}
bitrix/modules/main/lib/test/typography/storetable.phpnamespace Bitrix\Main\Test\Typography;

// Импорт класса OneToMany для создания связи «один ко многим»
use Bitrix\Main\ORM\Fields\Relations\OneToMany;

// Класс StoreTable наследует DataManager для работы с данными
class StoreTable extends \Bitrix\Main\ORM\Data\DataManager
{
    // Метод определяет карты полей таблицы
    public static function getMap()
    {
        return [
            // Здесь могут быть другие поля и связи, определенные для таблицы магазинов

            // Определение связи «один ко многим» с таблицей StoreBookTable
            (new OneToMany('BOOK_ITEMS', StoreBookTable::class, 'STORE'))
            // 'BOOK_ITEMS' — имя связи, используемое для доступа к связанным записям
            // StoreBookTable::class — класс таблицы, с которой устанавливается связь
            // 'STORE' — поле в StoreBookTable, используемое для связи с таблицей магазинов
        ];
    }
}

Выборка будет аналогична отношениям 1:N, но на этот раз будут возвращаться объекты отношения StoreBook, а не сущности-партнера.

$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1, [
    // Выбираем все поля книги и связанные записи о наличии в магазинах
    'select' => ['*', 'STORE_ITEMS']
])->fetchObject();

// Перебираем все связанные записи о наличии книги в магазинах
foreach ($book->getStoreItems() as $storeItem)
{
    printf(
        // Форматируем строку для вывода информации
        'store "%s" has %s of book "%s"',
        // Получаем ID магазина
        $storeItem->getStoreId(),
        // Получаем количество книг в магазине
        $storeItem->getQuantity(),
        // Получаем ID книги
        $storeItem->getBookId()
    );
    // Пример вывода: store "33" has 4 of book "1"
}
Заполните форму уже сегодня!
Для начала сотрудничества необходимо заполнить заявку или заказать обратный звонок. В ответ получите коммерческое предложение, которое будет содержать индивидуальную стратегию с учетом требований и поставленных задач
Работаем по будням с 9:00 до 18:00. Заявки, отправленные в выходные, обрабатываем в первый рабочий день до 12:00.
Спасибо, ваш запрос принят и будет обработан!