Отношения между сущностями
В ORM отношения между сущностями описывают, как данные одной таблицы связаны с данными другой. Основные типы отношений:
1:1
, «один к одному». Каждая запись в первой таблице соответствует одной записи во второй таблице, подробнее1:N
, «один ко многим». Одна запись в первой таблице может быть связана с несколькими записями во второй таблице, подробнееN:M
, «многие ко многим». Записи в первой таблице могут быть связаны с несколькими записями во второй таблице и наоборот, подробнее- Отношения со вспомогательными данными, подробнее
Пример схемы отношений
Рассмотрим пример с сущностями: книга 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
, который представляет собой таблицу издательств в базе данных. Этот класс наследуется от 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
.
Пример связи «один к одному»
Рассмотрим пример, где у каждой книги есть уникальная обложка. Это отношение «один к одному», так как одна книга может иметь только одну обложку, и одна обложка принадлежит только одной книге.
Класс 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'))
];
}
}
Класс 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"
}