Связь кастомных таблиц - uniqcle/Bitrix GitHub Wiki

Настройки -> Производительность -> Таблицы
Настройки -> Инструменты -> SQL запрос

1. Создаем таблицы в БД

Таблица Books
create table books (
	id INTEGER NOT NULL auto_increment PRIMARY KEY,
	name VARCHAR(50),
	text TEXT,
	publish_date DATE,
	ISBN VARCHAR(50),
	author_id TINYINT(4),
	publisher_id INT(11),
	wikiprofile_id INT(11)
);

insert into books (id, name, text, publish_date, ISBN, author_id, publisher_id, wikiprofile_id) values (1, 'Vue.js и Laravel создание SPA приложений', 'Книга содержит подробное описание Vue.js - библиотеки JS', '2024-05-01', '978-5-1243-3454-1', 1, 1, 0);
insert into books (id, name, text, publish_date, ISBN, author_id, publisher_id, wikiprofile_id) values (2, 'Оно', 'Роман написанный в жанре ужасов', '1986-05-01', '978-5-1235-2435-2', 2, 2, 1);
insert into books (id, name, text, publish_date, ISBN, author_id, publisher_id, wikiprofile_id) values (3, 'Мгла', 'Книга вошла в десятку лучших произведений в жанре ужасов', '1980-05-01', '978-5-4366-4234-3', 2, 1, 3);
insert into books (id, name, text, publish_date, ISBN, author_id, publisher_id, wikiprofile_id) values (4, 'Космос', 'Научно-популярная книга охватывает широкий круг тем области антропологии, космологии, биологии.', '2017-05-01','978-5-3464-7534-4', 3, 1, 0);
insert into books (id, name, text, publish_date, ISBN, author_id, publisher_id, wikiprofile_id) values (5, 'Путеводитель по Node.js', 'нига содержит подробное описание технологии Node.js', '2017-05-01', '978-5-1453-6846-5', 1, 2, 0);
insert into books (id, name, text, publish_date, ISBN, author_id, publisher_id, wikiprofile_id) values (6, 'Кладбище домашних животных', 'Роман написанн о страшном месте.', '1983-05-01', '978-5-6785-8657-6', 1, 2, 2);
Таблица Authors
create table authors (
	id INTEGER NOT NULL auto_increment PRIMARY KEY,
	name VARCHAR(50)
);

insert into authors (id, name) values (1,'Эван Ю');
insert into authors (id, name) values (2, 'Стивен Кинг');
insert into authors (id, name) values (3, 'Карл Саган');
Таблица Publishers
create table publishers (
	id INTEGER NOT NULL auto_increment PRIMARY KEY,
	name VARCHAR(50)
);

insert into publishers (id, name) values (1,'Британская академия наук');
insert into publishers (id, name) values (2, 'Viking Press');
Таблица Stores
create table stores (
	id INTEGER NOT NULL auto_increment PRIMARY KEY,
	name VARCHAR(50)
);

insert into stores (id, name) values (1,'Книголюб');
insert into stores (id, name) values (2, 'Читайка');
Таблица book_publisher
create table book_publisher (
	id INTEGER NOT NULL auto_increment PRIMARY KEY,
	book_id INT(11),
	publisher_id INT(11)
);

insert into book_publisher (id, book_id, publisher_id) values (1,1,1);
insert into book_publisher (id, book_id, publisher_id) values (2,2,2);
insert into book_publisher (id, book_id, publisher_id) values (3,2,2);
insert into book_publisher (id, book_id, publisher_id) values (4,2,2);
insert into book_publisher (id, book_id, publisher_id) values (5,4,2);
insert into book_publisher (id, book_id, publisher_id) values (7,6,2);
Таблица book_store
create table book_store (
	id INTEGER NOT NULL auto_increment PRIMARY KEY,
	book_id INT(11),
	store_id INT(11)
);

insert into book_store (id, book_id, store_id) values (1,1,2);
insert into book_store (id, book_id, store_id) values (2,2,1);
insert into book_store (id, book_id, store_id) values (3,2,2);
Таблица wikiprofiles
create table wikiprofiles (
	id INTEGER NOT NULL auto_increment PRIMARY KEY,
	wikiprofile_ru VARCHAR(50),
	wikiprofile_en VARCHAR(50),
	book_id INT(11)
);

insert into wikiprofiles (id, wikiprofile_ru, wikiprofile_en, book_id) values (1,'https://ru.wikipedia.org/wiki/Оно_(роман)','https://en.wikipedia.org/wiki/It_(novel)',2);
insert into wikiprofiles (id, wikiprofile_ru, wikiprofile_en, book_id) values (2,'https://ru.wikipedia.org/wiki/Кладбище_домашних_животных_(роман','https://en.wikipedia.org/wiki/Pet_Sematary',6);
insert into wikiprofiles (id, wikiprofile_ru, wikiprofile_en, book_id) values (3,'https://ru.wikipedia.org/wiki/Туман_(повесть)','https://en.wikipedia.org/wiki/The_Mist_(novella)',3);
Таблица book_author
create table book_author (
	id INTEGER NOT NULL auto_increment PRIMARY KEY,
	book_id INT(11),
	author_id INT(11)
);

insert into book_author (id, book_id, author_id) values (1,2,2);
insert into book_author (id, book_id, author_id) values (2,3,2);
insert into book_author (id, book_id, author_id) values (3,4,3);

2 Генерируем ORM

Включаем галочку Разрешить генерацию таблетов для ORM

Настройки-Настройки продукта-Настройки модулей-Монитор производительности-Генератор таблетов-Разрешить генерацию таблетов для ORM

В контекстом меню таблицы генерируем ORM Далее добавляем в модели.

/local/app/Models/BookTable.php

BookTable
namespace Models;

use Bitrix\Main\Localization\Loc;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields\DateField;
use Bitrix\Main\ORM\Fields\IntegerField;
use Bitrix\Main\ORM\Fields\StringField;
use Bitrix\Main\ORM\Fields\TextField;
use Bitrix\Main\ORM\Fields\Validators\LengthValidator;

// также добавили пространства имен
use Bitrix\Main\ORM\Fields\Validator\Base,
	Bitrix\Main\ORM\Fields\Validators\RegExpValidator,
	Bitrix\Main\ORM\Fields\Relations\Reference,
	Bitrix\Main\ORM\Fields\Relations\OneToMany,
	Bitrix\Main\ORM\Fields\Relations\ManyToMany,
	Bitrix\Main\Entity\Query\Join;

use Models\WikiprofileTable as Wikiprofile;
use Models\PublisherTable as Publisher;
use Models\AuthorTable as Author;

/**
 * Class BookTable
 * @package Models
 **/

class BookTable extends DataManager
{
	/**
	 * Returns DB table name for entity.
	 *
	 * @return string
	 */
	public static function getTableName()
	{
		return 'books';
	}

	/**
	 * Returns entity map definition.
	 *
	 * @return array
	 */
	public static function getMap()
	{
		return [
			new IntegerField(
				'id',
				[
					'primary' => true,
					'autocomplete' => true,
					'title' => Loc::getMessage('_ENTITY_ID_FIELD'),
				]
			),
			new StringField(
				'name',
				[
					'validation' => [__CLASS__, 'validateName'],
					'title' => Loc::getMessage('_ENTITY_NAME_FIELD'),
				]
			),
			new TextField(
				'text',
				[
					'title' => Loc::getMessage('_ENTITY_TEXT_FIELD'),
				]
			),
			new DateField(
				'publish_date',
				[
					'title' => Loc::getMessage('_ENTITY_PUBLISH_DATE_FIELD'),
				]
			),
			new StringField(
				'ISBN',
				[
					'validation' => [__CLASS__, 'validateIsbn'],
					'title' => Loc::getMessage('_ENTITY_ISBN_FIELD'),
				]
			),
			new IntegerField(
				'author_id',
				[
					'title' => Loc::getMessage('_ENTITY_AUTHOR_ID_FIELD'),
				]
			),
			new IntegerField(
				'publisher_id',
				[
					'title' => Loc::getMessage('_ENTITY_PUBLISHER_ID_FIELD'),
				]
			),
			new IntegerField(
				'wikiprofile_id',
				[
					'title' => Loc::getMessage('_ENTITY_WIKIPROFILE_ID_FIELD'),
				]
			),
			// один к одному
			(new Reference('WIKIPROFILE',
				Wikiprofile::class,
				 Join::on('this.wikiprofile_id', 'ref.id')))
				->configureJoinType('inner'),

			// один ко многим. Одна книга, много издателей
			(new Reference('PUBLISHER',
				Publisher::class,
				 Join::on('this.publisher_id', 'ref.id')))
				->configureJoinType('inner'),

			// один ко многим
			(new ManyToMany('AUTHORS', Author::class))
				->configureTableName('book_author')

				->configureLocalPrimary('id', 'book_id')
				->configureLocalReference('BOOKS')

				->configureRemotePrimary('id', 'author_id')
				->configureRemoteReference('AUTHORS'),
		];
	}

	/**
	 * Returns validators for name field.
	 *
	 * @return array
	 */
	public static function validateName()
	{
		return [
			new LengthValidator(3, 50),
		];
	}


	/**
	 * Returns validators for ISBN field.
	 *
	 * @return array
	 */
	public static function validateIsbn()
	{
		return
			array(function($value) {
				$clean = str_replace('-', '', $value);
				if (preg_match('/[\d-]{13,}/', $clean))
				{
					return true;
				}
				else
				{
					return 'Код ISBN должен содержать 13 цифр.';
				}
			});
	}
}
WikiprofileTable
namespace Models;

use Bitrix\Main\Localization\Loc;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields\IntegerField;
use Bitrix\Main\ORM\Fields\StringField;
use Bitrix\Main\ORM\Fields\Validators\LengthValidator;

use Bitrix\Main\ORM\Fields\Relations\Reference,
	Bitrix\Main\ORM\Fields\Relations\OneToMany,
	Bitrix\Main\ORM\Fields\Relations\ManyToMany,
	Bitrix\Main\Entity\Query\Join;

use Models\BookTable as Book;

/**
 * Class Table
 *
 * Fields:
 * <ul>
 * <li> id int mandatory
 * <li> wikiprofile_ru string(50) optional
 * <li> wikiprofile_en string(50) optional
 * <li> book_id int optional
 * </ul>
 *
 * @package Bitrix\
 **/

class WikiprofileTable extends DataManager
{
	/**
	 * Returns DB table name for entity.
	 *
	 * @return string
	 */
	public static function getTableName()
	{
		return 'wikiprofiles';
	}

	/**
	 * Returns entity map definition.
	 *
	 * @return array
	 */
	public static function getMap()
	{
		return [
			new IntegerField(
				'id',
				[
					'primary' => true,
					'autocomplete' => true,
					'title' => Loc::getMessage('_ENTITY_ID_FIELD'),
				]
			),
			new StringField(
				'wikiprofile_ru',
				[
					'validation' => function()
					{
						return[
							new LengthValidator(null, 50),
						];
					},
					'title' => Loc::getMessage('_ENTITY_WIKIPROFILE_RU_FIELD'),
				]
			),
			new StringField(
				'wikiprofile_en',
				[
					'validation' => function()
					{
						return[
							new LengthValidator(null, 50),
						];
					},
					'title' => Loc::getMessage('_ENTITY_WIKIPROFILE_EN_FIELD'),
				]
			),
			new IntegerField(
				'book_id',
				[
					'title' => Loc::getMessage('_ENTITY_BOOK_ID_FIELD'),
				]
			),

			(new Reference('BOOK',
				Book::class,
				Join::on('this.book_id', 'ref.id'))
			)->configureJoinType('inner')
		];
	}
}
PublisherTable
namespace Models;

use Bitrix\Main\Localization\Loc;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields\IntegerField;
use Bitrix\Main\ORM\Fields\StringField;
use Bitrix\Main\ORM\Fields\Validators\LengthValidator;

use Bitrix\Main\ORM\Fields\Relations\Reference,
	Bitrix\Main\ORM\Fields\Relations\OneToMany,
	Bitrix\Main\ORM\Fields\Relations\ManyToMany,
	Bitrix\Main\Entity\Query\Join;

use Models\BookTable as Books;

/**
 * Class PublisherTable
 * @package Models
 **/

class PublisherTable extends DataManager
{
	/**
	 * Returns DB table name for entity.
	 *
	 * @return string
	 */
	public static function getTableName()
	{
		return 'publishers';
	}

	/**
	 * Returns entity map definition.
	 *
	 * @return array
	 */
	public static function getMap()
	{
		return [
			new IntegerField(
				'id',
				[
					'primary' => true,
					'autocomplete' => true,
					'title' => Loc::getMessage('_ENTITY_ID_FIELD'),
				]
			),
			new StringField(
				'name',
				[
					'validation' => function()
					{
						return[
							new LengthValidator(null, 50),
						];
					},
					'title' => Loc::getMessage('_ENTITY_NAME_FIELD'),
				]
			),
			// Один издатель много книг
			(new OneToMany('BOOKS',
				Books::class,
				'PUBLISHER')
			)->configureJoinType('inner')
		];
	}
}
AuthorTable
namespace Models;

use Bitrix\Main\Localization\Loc;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields\IntegerField;
use Bitrix\Main\ORM\Fields\StringField;
use Bitrix\Main\ORM\Fields\Validators\LengthValidator;

use Bitrix\Main\ORM\Fields\Relations\Reference,
	Bitrix\Main\ORM\Fields\Relations\OneToMany,
	Bitrix\Main\ORM\Fields\Relations\ManyToMany,
	Bitrix\Main\Entity\Query\Join;

use Models\BookTable as Books;

/**
 * Class Table
 * @package Bitrix\
 **/

class AuthorTable extends DataManager
{
	/**
	 * Returns DB table name for entity.
	 *
	 * @return string
	 */
	public static function getTableName()
	{
		return 'authors';
	}

	/**
	 * Returns entity map definition.
	 *
	 * @return array
	 */
	public static function getMap()
	{
		return [
			new IntegerField(
				'id',
				[
					'primary' => true,
					'autocomplete' => true,
					'title' => Loc::getMessage('_ENTITY_ID_FIELD'),
				]
			),
			new StringField(
				'name',
				[
					'validation' => function()
					{
						return[
							new LengthValidator(null, 50),
						];
					},
					'title' => Loc::getMessage('_ENTITY_NAME_FIELD'),
				]
			),

			(new ManyToMany('BOOKS', Books::class))
				->configureTableName('book_author')

				->configureLocalPrimary('id', 'author_id')
				->configureLocalReference('AUTHORS')

				->configureRemotePrimary('id', 'book_id')
				->configureRemoteReference('BOOKS')
		];
	}
}

Простой вывод списка таблицы

use Models\BookTable as Books;

// получем коллекцию книг
$collection = Books::getList([
	'select' => [
		'id',
		'name',
		'publish_date'
	]
])->fetchCollection();

foreach ($collection as $key => $book) {
	debug('название '.$book->getName(). ' дата выхода:' .$book->getPublishDate());
}

Связь Один к одному

use Models\BookTable as Books;
use Models\WikiprofileTable as Wikiprofiles;

// отношение OneToOne
// выборка википрофиля со сороны книги
$book = Books::getByPrimary(3, [
	'select' => [
		'*',
		'WIKIPROFILE'
	]
])->fetchObject();

debug($book->getWikiprofile()->getWikiprofileRu());

/// Аналогично и из таблицы Wiki
$wikiprofile = Wikiprofiles::getByPrimary(3, [
	'select' => [
		'*',
		'BOOK'
	]
]) ->fetchObject();

debug($wikiprofile->getWikiprofileRu());
debug($wikiprofile->getBook()->getName());
debug($wikiprofile->getBook()->getPublishDate()->format("Y-m-d"));

В ORM BookTable в методы getMap() прописываем связь

// один к одному
(new Reference('WIKIPROFILE',
	Wikiprofile::class,
	 Join::on('this.wikiprofile_id', 'ref.id')))
	->configureJoinType('inner'),

А также в таблице WikiprofileTable

(new Reference('BOOK',
	Book::class,
	Join::on('this.book_id', 'ref.id'))
)->configureJoinType('inner')

Связь Один ко многим

У одного издателя много книг. У книги один издатель.

use Models\BookTable as Books;
use Models\PublisherTable as Publishers;
//////////////////////////////////////
// отношение OneToMany
//////////////////////////////////////

// Одна книга много издателей
// получем коллекцию книг и издателей
$collection = Books::getList([
	'select' => [
		'id',
		'name',
		'publish_date',
		'publisher_id',
		'PUBLISHER'
	]
])->fetchCollection();

foreach ($collection as $key => $book) {
	debug('название '.$book->getName().
		' дата выхода:' .$book->getPublishDate().
		' издатель:'.$book->getPublisher()->getName()
	);
}

// Один издатель и много книг по нему
// Один издателя и книги по нему
$publisher = Publishers::getByPrimary(1, [
	'select' => [
		'*',
		'BOOKS'
	]
])->fetchObject();

foreach ($publisher->getBooks() as $book){
	echo $book->getName();
}

В таблице BookTable в методе getMap прописываем связь

// один ко многим. Одна книга, много издателей
(new Reference('PUBLISHER',
	Publisher::class,
	 Join::on('this.publisher_id', 'ref.id')))
	->configureJoinType('inner'),

В таблице PublisherTable в методе getMap прописываем связь

// Один издатель много книг
(new OneToMany('BOOKS',
	Books::class,
	'PUBLISHER')
)->configureJoinType('inner')

Связь Многое ко многим

use Models\BookTable as Books;
use Models\AuthorTable as Authors;

// отношение ManyToMany (если у книги несколько авторов)
// выборка книг со стороны автора
$author = Authors::getByPrimary(2, [
	'select' => [
		'*',
		'BOOKS'
	]
])->fetchObject();

foreach ($author->getBooks() as $book){
	echo $book->getName().'<br/>';
}

// выборка авторов со сороны книги
// Одна книга - несколько авторов
$book = Books::getByPrimary(2, [
	'select' => [
		'*',
		'AUTHORS'
	]
])->fetchObject();

foreach ($book->getAuthors() as $author){
	echo ' книга: '.$book->getName().' автор: '.$author->getName().'<br/>';
}

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

// один ко многим
(new ManyToMany('AUTHORS', Author::class))
	->configureTableName('book_author')

	->configureLocalPrimary('id', 'book_id')
	->configureLocalReference('BOOKS')

	->configureRemotePrimary('id', 'author_id')
	->configureRemoteReference('AUTHORS')

А в таблице AuthorTable прописываем связь на таблицу BookTable через промежуточную таблицу book_author

(new ManyToMany('BOOKS', Books::class))
	->configureTableName('book_author')

	->configureLocalPrimary('id', 'author_id')
	->configureLocalReference('AUTHORS')

	->configureRemotePrimary('id', 'book_id')
	->configureRemoteReference('BOOKS')

Добавление элемента в таблицу

use Bitrix\Main\Type;
use Models\BookTable as Books;

// добавление записи в таблицу books
$record = [
	'name'=>'Жизнь замечательного человека',
	'publish_date' => new Type\Date('1988-09-17', 'Y-m-d'),
	'ISBN' =>'1234567891223'
];
$res = Books::add($record);
if(!$res->isSuccess()){
	debug($res->getErrorMessages());
}

Обновление элемента таблицы

use Bitrix\Main\Type;
use Models\BookTable as Books;

// обновление записи в таблицу books
$record = [
	'name'=>'Жизнь замечательного человека 2',
	'publish_date' => new Type\Date('1988-09-17', 'Y-m-d'),
	'ISBN' =>'1234567891223'
];
$res = Books::update(7, $record);

if(!$res->isSuccess()){
	debug($res->getErrorMessages());
}

Удаление элемента из таблицы

use Models\BookTable as Books;

$res = Books::delete(15);
if(!$res->isSuccess()){
	debug($res->getErrorMessages());
}
⚠️ **GitHub.com Fallback** ⚠️