In our presented test environment, a book can belong to and published strictly by a single publishing house. We'll get the relation "1 publisher - N books".
Book and a Publisher
In such cases, add the field PUBLISHER_ID to the table Books with value designating the Publisher.
namespace Bitrix\Main\Test\Typography;
use Bitrix\Main\ORM\Fields\IntegerField;
class BookTable extends \Bitrix\Main\ORM\Data\DataManager
{
public static function getMap()
{
return [
// ...
(new IntegerField('PUBLISHER_ID'))
];
}
}
However, a single field is not enough for ORM to recognize relations between Book and Publisher entities. For ORM to recognize such relations, use the multifields Bitrix\Main\ORM\Fields\Relations. In this case, the Reference type field is needed for indicating "multiple to one" relation:
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 [
// ...
(new IntegerField('PUBLISHER_ID')),
(new Reference(
'PUBLISHER',
PublisherTable::class,
Join::on('this.PUBLISHER_ID', 'ref.ID')
))
->configureJoinType('inner')
];
}
}
Reference constructor parameters:
Parameter | Description |
$name | Field name. |
$referenceEntity | Class for bound entity. |
/support/training/course/index.php?COURSE_ID=68&LESSON_ID=24526
$referenceFilter | "Join" conditions. Expects a filter object. In difference to regular filter use, add prefixes "this." and "ref." to column names here to designate association to current and related bounded entities accordingly.
For readability, a created class Bitrix\Main\ORM\Query\Join is available, with a only method on that returns filter object Bitrix\Main\ORM\Query\Filter\ConditionTree, indicating the most popular condition whereColumn. |
|
Additionally, you can configure "join" type. By default, it's left join; example above sets inner join.
Now you can use the described relations during data fetch:
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1, [
'select' => ['*', 'PUBLISHER']
])->fetchObject();
echo $book->getPublisher()->getTitle();
// prints Publisher Title 253
Access to Publisher entity object is implemented via "getter" getPublisher(). This way, you can connect more deeply nested relations chains, and use the "getter" chains to reach terminal objects.
To set a relation, it's sufficient to pass Publisher entity object in a corresponding "settter":
// publisher initialization
$publisher = \Bitrix\Main\Test\Typography\PublisherTable::wakeUpObject(253);
// book initialization
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
// setting object value
$book->setPublisher($publisher);
// saving
$book->save();
PUBLISHER_ID field value will be completed automatically from the passed object.
The array results will not look so compact:
$result = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1, [
'select' => ['*', 'PUBLISHER']
]);
print_r($result->fetch());
/* prints
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
)
*/
Unique names, based at the class name and namespace, are assigned to related entity fields. You can use the "alias" mechanism to get more brief and practical names:
$result = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1, [
'select' => ['*', 'PUB_' => 'PUBLISHER']
]);
print_r($result->fetch());
/* prints
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 and Books
At the moment, access to a relation operates only in a direction "Book" -> "Publisher". To make it two-way, you'll need to describe the relation at the side of Publisher entity:
namespace Bitrix\Main\Test\Typography;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields\Relations\OneToMany;
class PublisherTable extends DataManager
{
public static function getMap()
{
return [
// ...
(new OneToMany('BOOKS', BookTable::class, 'PUBLISHER'))->configureJoinType('inner')
];
}
}
OneToMany constructor parameters:
Parameter | Description |
$name | Field name. |
$referenceEntity | Class for entity to be related/bound. |
$referenceFilter | `Reference` field name in the partner entity, used to establish the relation. |
|
Additionally, you can re-define join type. By default, uses the type, specified in the Reference field for entity to be related.
Now you can use the described relation when selecting data:
$publisher = \Bitrix\Main\Test\Typography\PublisherTable::getByPrimary(253, [
'select' => ['*', 'BOOKS']
])->fetchObject();
foreach ($publisher->getBooks() as $book)
{
echo $book->getTitle();
}
// loop prints "Title 1" and "Title 2"
The example above highlights an essential advantage of object-based model compared to array-based one. Despite the fact that two entities were selected (two books were found for a single Publisher), in actuality, result gets only a single object. System has independently recognized this case and included all the publisher books in a single Collection.
Requesting an array from the result returns a classic data structure with duplicating Publisher data:
$data = \Bitrix\Main\Test\Typography\PublisherTable::getByPrimary(253, [
'select' => ['*', 'BOOK_' => 'BOOKS']
])->fetchAll();
// returns
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
)
)
To add a new Book to the Publisher, use the named "setter" addTo:
// publisher initialization
$publisher = \Bitrix\Main\Test\Typography\PublisherTable::getByPrimary(253)
->fetchObject();
// book initialization
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(2)
->fetchObject();
// adding book into relations collection
$publisher->addToBooks($book);
// saving
$publisher->save();
You can delete relation from the side of a book by assigning another publisher by setting setPublisher() or specifying a null. Specialized "setters" removeFrom() and removeAll() are available to do the same for the Publisher:
// book initialization
$book = \Bitrix\Main\Test\Typography\Book::wakeUp(2);
// publisher initialization
$publisher = \Bitrix\Main\Test\Typography\PublisherTable::getByPrimary(253, [
'select' => ['*', 'BOOKS']
])->fetchObject();
// deleting a specific single publisher book
$publisher->removeFromBooks($book);
// or deleting all publisher books
$publisher->removeAllBooks();
// when saving, the PUBLISHER_ID field is updated in Books to be empty
// the books themselves are not deleted, the only relation is deleted
$publisher->save();
It's important: for ensuring the correct operation the relation field must contain a value. In the example above its specified in the fetched data. You need to preliminarily call the method
fill if you didn't fetch values from the database or aren't sure if they are filled in the specific object:
// book initialization
$book = \Bitrix\Main\Test\Typography\BookTable::wakeUpObject(2);
// the publisher will have a primary key completed only
$publisher = \Bitrix\Main\Test\Typography\PublisherTable::wakeUpObject(253);
// complete relation field
$publisher->fillBooks();
// delete specific single book
$publisher->removeFromBooks($book);
// or delete all books
$publisher->removeAllBooks();
// when saving, the PUBLISHER_ID field in Books will be updated to be empty
// the books themselves won't be deleted
$publisher->save();
In case of arrays, the operations with addTo, removeFrom and removeAll are impossible; you can create relation only from the side of Books entity.