Объекты ORM
В Bitrix Framework объект представляет собой экземпляр класса. Класс соответствует записи в базе данных. Это позволяет работать с данными в виде объектов, а не массивов, что делает код более удобным.
Чтобы использовать объекты, необходимо сначала описать сущность. Метод fetchObject
получает объект сущности из базы данных. В отличие от метода fetch
, который возвращает данные в виде массива, fetchObject
возвращает данные в виде объекта.
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
Теперь $book
это объект сущности Book
, который предоставляет методы для работы с данными книги и ее связями с другими сущностями. Методы объекта можно использовать для получения и изменения данных, например, $book->getTitle()
для получения названия книги или $book->setTitle('New Title')
для его изменения.
Как создать свой класс
Если необходимо использовать собственное имя класса или добавить дополнительный функционал, можно создать свой класс объекта.
bitrix/modules/main/lib/test/typography/book.phpnamespace Bitrix\Main\Test\Typography;
class Book extends EO_Book
{
// Здесь можно добавить свои методы и функционал
}
Класс Book
наследует базовый класс EO_Book
.
bitrix/modules/main/lib/test/typography/booktable.phpnamespace Bitrix\Main\Test\Typography;
class BookTable extends Bitrix\Main\ORM\Data\DataManager
{
public static function getObjectClass()
{
return Book::class;
}
}
Класс BookTable
наследует класс Bitrix\Main\ORM\Data\DataManager
и управляет взаимодействием с базой данной для сущности Книга
.
Метод getObjectClass
определяет, что каждая запись из таблицы будет представлена как объект класса Book
.
Теперь метод fetchObject
будет возвращать объекты вашего класса Bitrix\Main\Test\Typography\Book
.
Рекомендации
- Чтобы использовать конкретное имя или расширить функционал, нужно определить собственный класс объектов
- Стандартные имена классов, такие как
instanceof EO_Book
,new EO_Book
илиEO_Book::class
, не следует использовать. Лучше описать свой класс с подходящим именем или использовать обезличенные методыBookTable::getObjectClass()
,BookTable::createObject()
,BookTable::wakeUpObject()
- В своем классе можно добавлять функционал и переопределять методы. Свойства с именами
primary
,entity
иdataClass
не рекомендуется описывать, так как они уже используются базовым классом
Именованные и именованные методы
Большинство методов для работы с полями сущностей именованные. Это значит, что для каждого поля доступны персональные методы: getTitle
, setTitle
, remindActualTitle
и другие.
$book->getTitle();
$book->setTitle($value);
$book->remindActualTitle();
$book->resetTitle();
// и так далее
Особенности именованных методов:
- Инкапсуляция. Позволяет контролировать доступ к каждому полю индивидуально
- Удобство. Не нужно запоминать названия полей, IDE подскажет их и ускорит ввод с помощью автодополнения
- Читаемость, код выглядит целостно и понятно
Универсальные методы
Существуют универсальные методы: get
. Они принимают имя поля, $fieldName
как аргумент.
$fieldName = 'TITLE';
$book->get($fieldName);
$book->set($fieldName, $value);
$book->remindActual($fieldName);
$book->reset($fieldName);
// и так далее
Имена полей хранятся в переменных, что позволяет работать с полями обезличенно.
Если для объекта создан собственный класс и переопределен именованный метод, то универсальный метод вызовет измененный метод.
namespace Bitrix\Main\Test\Typography;
class Book extends EO_Book
{
public function getTitle()
{
return 'custom title';
}
}
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
// 'custom title'
echo $book->getTitle();
// 'custom title'
echo $book->get('TITLE');
Исключение, метод fill
, он не вызывает именованные методы, так как оптимизирован для работы с базой данных и заполнения нескольких полей одновременно.
Магические методы
Именованные методы реализованы с помощью магического метода __call
. Это позволяет автоматически обрабатывать все поля без ручного описания. Такой подход экономит память и устраняет необходимость кеширования сгенерированных классов.
Недостаток магических методов, повышенная нагрузка на процессор. Это можно решить, явно определив часто используемые методы как getId
в базовом классе. Процессорные ресурсы проще масштабировать горизонтально, добавляя новые машины.
Приведение типов
В объектах значения строго приводятся к типу поля. Это значит, что числа всегда остаются числами, а строки строками.
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
// int 1
var_dump($book->getId());
// string 'Title 1' (length=7)
var_dump($book->getTitle());
Поля типа BooleanField
ожидают значения true
или false
. Если в базе данных такие значения представлены как Y
или N
, они автоматически преобразуются.
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
// boolean true
var_dump($book->getIsArchived());
// При установке значений также ожидается boolean
$book->setIsArchived(false);
Как прочитать данные объекта
Методы чтения данных позволяют получать значения полей, проверять их наличие и отслеживать изменения.
get
Метод возвращает значение поля или null
, если поле отсутствует или если оно не указано в select
.
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
$title = $book->getTitle();
require
Метод можно использовать, если поле должно быть заполнено. Если поле пустое, сценарий не будет выполнен, и будет выведена ошибка.
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
$title = $book->requireTitle();
В данном случае результат requireTitle()
не будет отличаться от вызова getTitle()
, так как поле TITLE
заполнено. В следующем примере поле не заполнено, поэтому будет выброшено исключение.
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1, ['select' => ['ID', 'PUBLISHER_ID', 'ISBN']])
->fetchObject();
// SystemException: "TITLE value is required for further operations"
$title = $book->requireTitle();
remindActual
Метод позволяет отличить оригинальное значение от измененного, но еще не сохраненного.
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
// "Title 1"
echo $book->getTitle();
$book->setTitle("New title");
// "New title"
echo $book->getTitle();
// "Title 1"
echo $book->remindActualTitle();
Можно использовать универсальные методы, если имена полей хранятся в переменных.
$fieldName = 'TITLE';
$title = $book->get($fieldName);
$title = $book->require($fieldName);
$title = $book->remindActual($fieldName);
primary
Метод primary
реализован в виде виртуального свойства, доступного только для чтения. Он возвращает значения первичного ключа.
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
// ['ID' => 1]
$primary = $book->primary;
// 1
$id = $book->getId();
collectValues
Метод collectValues
возвращает все значения объекта в виде массива.
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
$values = $book->collectValues();
Можно использовать фильтры для уточнения набора полей и типа данных.
$values = $book->collectValues(\Bitrix\Main\ORM\Objectify\Values::ACTUAL);
$values = $book->collectValues(\Bitrix\Main\ORM\Objectify\Values::CURRENT);
$values = $book->collectValues(\Bitrix\Main\ORM\Objectify\Values::ALL);
Вторым аргументом передается маска, определяющая типы полей.
$values = $book->collectValues(
\Bitrix\Main\ORM\Objectify\Values::CURRENT,
\Bitrix\Main\ORM\Fields\FieldTypeMask::SCALAR
);
$values = $book->collectValues(
\Bitrix\Main\ORM\Objectify\Values::ALL,
\Bitrix\Main\ORM\Fields\FieldTypeMask::ALL & ~\Bitrix\Main\ORM\Fields\FieldTypeMask::USERTYPE
);
runtime
Для runtime
полей используется только универсальный метод get.
$author = \Bitrix\Main\Test\Typography\AuthorTable::query()
->registerRuntimeField(
new \Bitrix\Main\Entity\ExpressionField(
'FULL_NAME', 'CONCAT(%s, " ", %s)', ['NAME', 'LAST_NAME']
)
)
->addSelect('ID')
->addSelect('FULL_NAME')
->where('ID', 17)
->fetchObject();
// 'Name 17 Last name 17'
echo $author->get('FULL_NAME');
Значения runtime
полей изолированы от штатных полей, поэтому другие методы для них неактуальны.
Записать данные объекта
Методы записи позволяют устанавливать, отменять и удалять значения.
set
Метод set
устанавливает новое значение для поля.
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
$book->setTitle("New title");
Объект запоминает исходные значения.
// текущее значение
$book->getTitle();
// актуальное значение
$book->remindActualTitle();
Значения первичного ключа primary
можно установить только в новых объектах. В существующих объектах изменить его нельзя. Для этого следует создать новый объект и удалить старый. Поля Bitrix\Main\ORM\Fields\ExpressionField
не изменяются вручную, так как их значения рассчитываются автоматически. Если новое значение не отличается от актуального, оно не изменится. Такое значение не будет включено в SQL-запрос при сохранении.
reset
Метод reset
отменяет установку нового значения и возвращает исходное.
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
// "Title 1"
echo $book->getTitle();
$book->setTitle("New title");
// "New title"
echo $book->getTitle();
$book->resetTitle();
// "Title 1"
echo $book->getTitle();
unset
Метод unset
удаляет значение объекта, как будто оно не выбиралось и не устанавливалось.
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
// "Title 1"
echo $book->getTitle();
$book->unsetTitle();
// null
echo $book->getTitle();
Можно использовать универсальные методы с именем поля.
$fieldName = 'TITLE';
$book->set($fieldName, "New title");
$book->reset($fieldName);
$book->unset($fieldName);
Все изменения происходят только во время сеанса. Чтобы сохранить их в базе данных, объект нужно сохранить.
Проверить значения полей
В Bitrix Framework есть методы для проверки полей объектов. Они определяют, заполнены ли поля, изменялись ли они или содержат значения.
isFilled
Метод isFilled
проверяет, содержит ли объект актуальное значение из базы данных.
use \Bitrix\Main\Test\Typography\Book;
// актуальными считаются значения из методов fetch* и wakeUp в примере при инициализации объекта передается только первичный ключ
$book = Book::wakeUp(1);
// false
var_dump($book->isTitleFilled());
$book->fillTitle();
// true
var_dump($book->isTitleFilled());
isChanged
Метод isChanged
определяет, было ли установлено новое значение в течение сеанса.
use \Bitrix\Main\Test\Typography\Book;
// объект может иметь исходное значение, а может и не иметь, это не повлияет на дальнейшее поведение
$book = Book::wakeUp(['ID' => 1, 'TITLE' => 'Title 1']);
// false
var_dump($book->isTitleChanged());
$book->setTitle('New title 1');
// true
var_dump($book->isTitleChanged());
Этот метод также применим к новым объектам, пока их значения не сохранены в базе данных.
has
Метод has проверяет, есть ли в объекте какое-либо значение поля — актуальное из базы данных или установленное в сеансе. Это эквивалентно isFilled() || isChanged()
.
use \Bitrix\Main\Test\Typography\Book;
$book = Book::wakeUp(['ID' => 1, 'TITLE' => 'Title 1']);
$book->setIsArchived(true);
// true
var_dump($book->hasTitle());
// true
var_dump($book->hasIsArchived());
// false
var_dump($book->hasIsbn());
Проверить состояние объекта
Объект может находиться в одном из четырех состояний:
RAW
новый. Объект только что создан, и его данные еще не были сохранены в базе данныхACTUAL
актуальный. Данные объекта совпадают с теми, что хранятся в базе данныхCHANGED
измененный. Данные объекта были изменены и отличаются от тех, что хранятся в базе данныхDELETED
удаленный. Объект был удален из базы данных
Переход между состояниями происходит автоматически на основе изменений данных. Состояние объекта можно проверить с помощью публичного read-only
свойства state
и констант класса \Bitrix\Main\ORM\Objectify\State
.
use \Bitrix\Main\Test\Typography\Book;
use \Bitrix\Main\ORM\Objectify\State;
$book = new Book;
$book->setTitle('New title');
// true
var_dump($book->state === State::RAW);
$book->save();
// true
var_dump($book->state === State::ACTUAL);
$book->setTitle('Another one title');
// true
var_dump($book->state === State::CHANGED);
$book->delete();
// true
var_dump($book->state === State::RAW);
Сохранить объект
Для сохранения изменений объекта в базе данных используется метод save
. Этот метод фиксирует все изменения в объекте, и обновляет данные в базе данных.
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
$book->setTitle("New title");
$book->save();
Если выполнить этот пример с тестовой сущностью из пространства имен Bitrix\Main\Test\Typography
, то можно получить SQL-ошибку. Это связано с тем, что тестовые данные могут быть неполными или содержать некорректные значения. Однако часть запроса с данными будет построена корректно.
После сохранения все текущие значения объекта становятся актуальными.
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
// "Title 1"
echo $book->remindActualTitle();
$book->setTitle("New title");
// "Title 1"
echo $book->remindActualTitle();
$book->save();
// "New title"
echo $book->remindActualTitle();
Как создать новый объект
Есть два способа создания новых объектов.
Прямое создание объекта
$newBook = new \Bitrix\Main\Test\Typography\Book;
$newBook->setTitle('New title');
$newBook->save();
$newAuthor = new \Bitrix\Main\Test\Typography\EO_Author;
$newAuthor->setName('Some name');
$newAuthor->save();
Этот способ подходит как для стандартных классов с префиксом EO_
, так и для собственных классов. Если вы сначала использовали стандартный класс, а затем создали свой, переписывать код не нужно, обратная совместимость сохранится. Системный класс с префиксом EO_
станет алиасом вашему классу.
Использование фабрики сущности
$newBook = \Bitrix\Main\Test\Typography\BookTable::createObject();
$newBook->setTitle('New title');
$newBook->save();
В новом объекте автоматически устанавливаются значения по умолчанию, описанные в маппинге getMap
. Для создания объекта без этих значений следует передать соответствующий аргумент в конструктор.
$newBook = new \Bitrix\Main\Test\Typography\Book(false);
$newBook = \Bitrix\Main\Test\Typography\BookTable::createObject(false);
Состояние значений меняется аналогично редактированию. До сохранения значения считаются текущими, после сохранения в базе данных переходят в статус актуальных.
Удалить объект
Метод delete()
удаляет объект из базы данных.
// Удаление записи
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
$book->delete();
// Удаление по primary ключу
$book = \Bitrix\Main\Test\Typography\Book::wakeUp(1);
$book->delete();
Метод delete()
удаляет только ту запись, которая соответствует конкретному объекту. Если нужно удалить или изменить связанные данные, это необходимо сделать явно.
При удалении объекта через метод delete()
срабатывают все необходимые события. Дополнительные действия можно описать в обработчике события onDelete
.
Восстановить объект
Метод wakeUp
восстанавливает объект из имеющихся данных без повторного запроса к базе. Это удобно, когда есть данные записи, и нужно создать объект на их основе.
Объект можно восстановить с помощью значения первичного ключа.
$book = \Bitrix\Main\Test\Typography\Book::wakeUp(1);
Помимо первичного ключа, разрешается указать частичный или полный набор данных.
$book = \Bitrix\Main\Test\Typography\Book::wakeUp(['ID' => 1, 'TITLE' => 'Title 1', 'PUBLISHER_ID' => 253]);
Метод wakeUp
можно использовать как для классов с префиксом EO_
, так и для вызова непосредственно из сущности.
// Свой класс
$book = \Bitrix\Main\Test\Typography\Book::wakeUp(['ID' => 1, 'TITLE' => 'Title 1']);
// Системный класс
$book = \Bitrix\Main\Test\Typography\EO_Book::wakeUp(['ID' => 1, 'TITLE' => 'Title 1']);
// Через фабрику сущности
$book = \Bitrix\Main\Test\Typography\BookTable::wakeUpObject(['ID' => 1, 'TITLE' => 'Title 1']);
В wakeUp
можно передавать не только скалярные значения, но и значения отношений. Значения отношений позволяют восстановить связанные объекты, например, издателя книги или авторов.
$book = \Bitrix\Main\Test\Typography\Book::wakeUp([
'ID' => 2,
'TITLE' => 'Title 2',
'PUBLISHER' => ['ID' => 253, 'TITLE' => 'Publisher Title 253'],
'AUTHORS' => [
['ID' => 17, 'NAME' => 'Name 17'],
['ID' => 18, 'NAME' => 'Name 18']
]
]);
Заполнить объект
Чтобы дозаполнить объект с неполными данными, нужно использовать именованный метод fill
.
// Изначально у нас есть только ID и NAME
$author = \Bitrix\Main\Test\Typography\EO_Author::wakeUp(
['ID' => 17, 'NAME' => 'Name 17']
);
// Добавляем LAST_NAME из базы данных
$author->fillLastName();
Метод fill
позволяет корректно дозаполнить объект, считая данные актуальными.
Кроме именованных методов, существует универсальный метод fill
, который предоставляет больше возможностей.
$author = \Bitrix\Main\Test\Typography\EO_Author::wakeUp(17);
// Заполнение нескольких полей
$author->fill(['NAME', 'LAST_NAME']);
// Заполнение всех незаполненных полей
$author->fill();
// Заполнение полей по маске, например, все незаполненные скалярные поля
$author->fill(\Bitrix\Main\ORM\Fields\FieldTypeMask::SCALAR);
// Незаполненные скалярные и пользовательские поля
$author->fill(
\Bitrix\Main\ORM\Fields\FieldTypeMask::SCALAR
| \Bitrix\Main\ORM\Fields\FieldTypeMask::USERTYPE
);
Маски бывают:
SCALAR
скалярные поляORM\ScalarField
EXPRESSION
выраженияORM\ExpressionField
USERTYPE
пользовательские поляREFERENCE
отношения 1:1 и N:1ORM\Fields\Relations\Reference
ONE_TO_MANY
отношения 1: NORM\Fields\Relations\OneToMany
MANY_TO_MANY
отношения N: MORM\Fields\Relations\ManyToMany
FLAT
скалярные поля и выраженияRELATION
все отношенияALL
все доступные поля
Если нужно дозаполнить несколько объектов, используйте метод для коллекции объектов вместо выполнения операций в цикле. Это минимизирует количество запросов к базе данных.
Как управлять отношениями
Для управления связями между объектами используются специальные методы. Эти методы позволяют добавлять, удалять и очищать связи между объектами, такими как книги и издатели.
Для работы с отношениями предназначены методы addTo
, removeFrom
и removeAll
. Изменения связей необходимо выполнять через эти методы, а не напрямую через объект коллекции, чтобы изменения были корректно зафиксированы.
addTo
Метод addTo
добавляет новую связь между объектами. Например, можно добавить книгу к издателю.
// Инициализация издателя
$publisher = \Bitrix\Main\Test\Typography\PublisherTable::getByPrimary(253)
->fetchObject();
// Инициализация книги
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(2)
->fetchObject();
// Добавление книги в коллекцию отношения
$publisher->addToBooks($book);
// Сохранение
$publisher->save();
Метод addTo
связывает объекты только в памяти. Чтобы изменения вступили в силу, необходимо вызвать метод save
.
removeFrom
Метод removeFrom
удаляет конкретную связь между объектами. Например, можно удалить книгу из списка книг издателя.
// Инициализация издателя
$publisher = \Bitrix\Main\Test\Typography\PublisherTable::getByPrimary(253)
->fetchObject();
// Инициализация книги
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(2)
->fetchObject();
// Удаление одной конкретной книги издателя
$publisher->removeFromBooks($book);
// Сохранение
$publisher->save();
Метод removeFrom
удаляет связь только в памяти. После removeFrom необходимо вызвать метод save
.
removeAll
Метод removeAll
удаляет все связи для указанного поля. Например, можно удалить все книги, связанные с издателем.
// Инициализация издателя
$publisher = \Bitrix\Main\Test\Typography\PublisherTable::getByPrimary(253)
->fetchObject();
// Удаление всех книг издателя
$publisher->removeAllBooks();
// Сохранение
$publisher->save();
Для такой операции необходимо знать исходные значения — какие книги у издателя есть в данный момент. Если значение поля BOOKS
не было выбрано изначально, оно будет выбрано автоматически перед удалением.
Метод removeAll
удаляет связи только в памяти. После removeAll необходимо вызвать метод save
.
Универсальные методы
Вы также можете использовать универсальные методы, указывая имя поля.
$fieldName = 'BOOKS';
$publisher->addTo($fieldName, $book);
$publisher->removeFrom($fieldName, $book);
$publisher->removeAll($fieldName);
Интерфейс ArrayAccess
Интерфейс ArrayAccess
позволяет работать с объектами так, как если бы они были массивами. Это упрощает переход от использования массивов к объектам, сохраняя привычный синтаксис.
Пример использования ArrayAccess
:
$author = \Bitrix\Main\Test\Typography\AuthorTable::getByPrimary(17)->fetchObject();
// Аналогично вызову метода $author->getName()
echo $author['NAME'];
// Аналогично вызову метода $author->setName('New name')
$author['NAME'] = 'New name';
Таким образом, можно обращаться к полям объекта, используя синтаксис массивов, что делает код более читаемым и удобным.
Важно помнить, что для runtime
полей можно только считывать значения. Установка значений для этих полей через интерфейс ArrayAccess
недопустима и приведет к ошибке.
$author = \Bitrix\Main\Test\Typography\AuthorTable::query()
->registerRuntimeField(
new \Bitrix\Main\Entity\ExpressionField('FULL_NAME', 'CONCAT(%s, " ", %s)', ['NAME', 'LAST_NAME'])
)
->addSelect('ID')
->addSelect('FULL_NAME')
->where('ID', 17)
->fetchObject();
// Аналогично вызову метода $author->get('FULL_NAME')
echo $author['FULL_NAME'];
// Вызовет исключение
$author['FULL_NAME'] = 'New name';
Использование интерфейса ArrayAccess
позволяет работать с объектами, как с массивами, что упрощает переход от массивов к объектам в коде. Однако, для runtime
полей установка значений через этот интерфейс недопустима и приведет к исключению.