Начало работы
Введение
Исторически так сложилось, что манипуляция данными об элементах основных сущностей CRM осуществляется с помощью массивов.
У этого подхода есть ряд недостатков:
- Названия полей у разных сущностей отличаются, хотя имеют одинаковый смысл. Надо всегда помнить их названия.
- Нет autocomplete в IDE.
- Нет типизации и др.
С точки зрения публичного интерфейса доступа к данным об элементе сущности, особенных различий между элементами разных сущностей нет. Общего гораздо больше.
Чтобы унифицировать подход, закрыть перечисленные выше недостатки, в новом API был введён абстрактный класс Bitrix\Crm\Item.
Этот класс содержит в себе Bitrix\Main\ORM\Objectify\EntityObject и имитирует его поведение.
Каждый тип сущности имеет свою реализацию этого класса, где содержатся особенности этого типа.
Класс воспроизводит интерфейс \ArrayAccess, при обращении к объекту как к массиву, будут вызваны метод get и set.
Коды "общих" полей этого класса основаны на соответствующих кодах полей таблицы элементов смарт-процесса.
Т.к. сам по себе класс не обращается к базе данных напрямую, он полностью полагается на состояние внутреннего EntityObject.
Это значит, что если у этого объекта не заполнены какие-то данные (не были переданы в select), то и класс не будет иметь к ним доступ.
Именованные методы
Класс имеет набор именованных методов (по аналогии с EntityObject) для работы с состоянием и значениями полей.
Например, метод getStageId вернет строковый идентификатор стадии элемента. Даже если поле называется не STAGE_ID, а STATUS_ID, метод всё равно будет работать.
Аналогично будет работать метод setStageId - он запишет новое значение в поле для стадии, независимо от его кода в таблице элементов.
И так же будет работать метод isChangedStageId. Остальные кейсы, доступные в EntityObject, не реализованы.
Работа с именованными методами реализована через магический метод __call. Некоторые именованные методы реализованы в явном виде.
Вот полный набор методов, доступных для работы с полем "Заголовок" (TITLE):
getTitle(): ?string- вернет текущее значение;setTItle(string $title): Item- запишет новое значение;isChangedTitle(): bool- вернет true, если значение поля было изменено.
Аналогичным образом можно обращаться к методам других полей. Код поля должен быть преобразован в UpperCamelCase нотацию.
Примеры:
getCreatedTime(): ?DateTimegetUpdatedTime(): ?DateTimegetMovedTime(): ?DateTimegetCreatedBy(): ?intgetUpdatedBy(): ?intgetMovedBy(): ?intgetAssignedById(): ?intgetOpened(): boolgetBegindate(): ?DateTimegetClosedate(): ?DateTimegetCompanyId(): ?intgetContactId(): ?intgetStageId(): ?stringgetCategoryId(): ?intgetOpportunity(): ?floatgetIsManualOpportunity(): boolgetTaxValue(): floatgetCurrencyId(): ?stringgetOpportunityAccount(): ?floatgetTaxValueAccount(): ?floatgetAccountCurrencyId(): ?stringgetMycompanyId(): ?intgetProductRows(): ?ProductRowCollectiongetClosed(): boolgetSourceId(): ?stringgetSourceDescription(): ?stringgetWebformId(): ?int
Аналогичным образом можно работать с полями UTM-меток. Хотя значения этих полей не хранятся непосредственно в таблице элементов, к ним есть доступ аналогичным способом из этого класса.
getUtmSource(): ?stringgetUtmMedium(): ?stringgetUtmCampaign(): ?stringgetUtmContent(): ?stringgetUtmTerm(): ?string
Общие методы
| Метод | Описание | С версии |
|---|---|---|
public function __construct(int $entityTypeId, EntityObject $entityObject, array $fieldsMap = [], array $disabledFieldNames = [])
|
Использовать конструктор напрямую настоятельно не рекомендуется. Для получения объектов необходимо использовать соответствующие методы фабрики. | |
public function hasField(string $fieldName): bool |
Вернет true, если элемент имеет поле с названием $fieldName.
Здесь и далее $fieldName может быть как "общим", так и специфическим для сущности.
Например, hasField('STAGE_ID') и hasField('STATUS_ID') для элемента сущности "Лид" оба вернут true.
| |
public function getDefaultValue(string $fieldName) |
Вернет значение по умолчанию для поля $fieldName. | |
public function get(string $fieldName) |
Вернет текущее значение поля $fieldName. | |
public function set(string $fieldName, $value): self |
Запишет новое текущее значение $value у поля $fieldName. | |
public function reset(string $fieldName): self |
Сбросит значение поля $fieldName в его исходное состояние. | |
public function unset(string $fieldName): self |
Запишет пустое значение в поле $fieldName. | |
public function remindActual(string $fieldName) |
Вернет исходное значение поля $fieldName. | |
public function isChanged(string $fieldName): bool |
Вернет true, если поле $fieldName было изменено (текущее значение отличается от исходного). | |
public function getData(int $valuesType = Values::ALL): array |
Вернет массив значений полей элемента, где ключами являются "общие" коды полей, а значениями - значения этих полей, отобранные по маске $valuesType.
$valuesType может принимать значения, описанные в классе \Bitrix\Main\ORM\Objectify\Values:
\Bitrix\Main\ORM\Objectify\Values::ACTUAL - вернутся только исходные значения полей\Bitrix\Main\ORM\Objectify\Values::CURRENT - вернутся только текущие значения полей\Bitrix\Main\ORM\Objectify\Values::ALL - вернутся все значения полей. | |
public function getCompatibleData (int $valuesType = Values::ALL): array $valuesType принимает те же значения и с тем же смыслом, что и в методе getData
|
Вернет массив значений полей элемента в том же формате, в котором работа с ними производилась в "старом" API.
Здесь коды полей отдаются в кодах, специфических для конкретного типа. Значения полей преобразуются в строки / числа / массивы. | |
public function getCompatibleData (int $valuesType = Values::ALL): array $valuesType принимает те же значения и с тем же смыслом, что и в методе getData
|
Вернет массив значений полей элемента в том же формате, в котором работа с ними производилась в "старом" API.
Здесь коды полей отдаются в кодах, специфических для конкретного типа. Значения полей преобразуются в строки / числа / массивы. | |
public function setFromCompatibleData(array $data): self |
Метод запишет новые значения полей из массива $data. Данные в массиве должны быть в формате "старого" API.
Ключами массива должны являться коды полей, специфических для конкретного типа. Если элемент является новым, и значение какого-то поля не передано, то в элемент будет записано значение этого поля по умолчанию (см. метод getDefaultValue). | |
public function isNew(): bool |
Вернет true, если элемент ещё не сохранен в базу данных. | |
public function getId(): int |
Вернет идентификатор элемента. | |
public function getEntityTypeId(): int |
Вернет идентификатор типа сущности CRM. | |
public function getTitlePlaceholder(): ?string |
Вернет заголовок по умолчанию, предназначенный для показа в форме создания. | |
public function save(bool $isCheckUserFields = true): Result |
Сохранит текущее состояние элемента в базу данных. Вернет объект Bitrix\Main\Result.
Флаг $isCheckUserFields говорит о том, надо ли проверять корректность заполнения пользовательских полей при сохранении.
Этот метод выполняет только сохранение в базу данных. Внутри него не выполняются никакие дополнительные действия (кроме обработчиков событий, подписанных на таблет).
Все дополнительные действия производятся через операции. | |
public function delete(): Result |
Удаляет элемент из базы данных, возвращает Bitrix\Main\Result.
Флаг $isCheckUserFields говорит о том, надо ли проверять корректность заполнения пользовательских полей при сохранении.
Метод выполняет только непосредственно удаление.
Для учета всех дополнительных действий надо воспользоваться операцией. | |
public function jsonSerialize(): array |
Вернет данные об элементе в подготовленном для фронта или REST виде (см. Service\Converter). | |
public function getEntityFieldNameIfExists (string $commonFieldName): ?string |
Вернет специфический для типа код поля по его "общему" названию. | |
public function isCategoriesSupported(): bool |
Вернет true, если тип элемента поддерживает работу с направлениями. | |
public function isStagesEnabled(): bool |
Вернет true, если для типа элемента должны отображаться стадии в интерфейсе. | |
public function getCategoryIdForPermissions(): int |
Вернет идентификатор направления для учета при проверке прав доступа. Если направления не поддерживаются, возвращает 0. |
Работа с коллекциями
Все методы, изменяющие состояние привязанных коллекций, не производят запись изменений в базу данных. Они только изменяют состояние объекта (аналогично поведению методов get/ set).
Чтобы приведенные ниже методы работали корректно, из БД должны быть выбраны соответствующие данные: поля Item::FIELD_NAME_CONTACTS или Item::FIELD_NAME_CONTACT_BINDINGS для работы с контактами, поле Item::FIELD_NAME_OBSERVERS и поле Item::FIELD_NAME_PRODUCTS для товарных позиций.
Сделать это можно, передав в select метода Factory::getItems необходимые поля.
Если же необходимо получить один элемент, то можно воспользоваться методом Factory::getItem. Он вернет объект, для которого из БД получены все связанные записи.
Чтобы изменения были сохранены, необходимо вызвать метод save.
| Метод | Описание | С версии |
|---|---|---|
public function getPrimaryContact(): ?Contact |
Вернет orm-объект с "основным" контактом, если есть хотя бы один. | |
public function getContacts(): array |
Вернет массив orm-объектов привязанных контактов. | |
public function bindContacts(array $contactBindings): void |
Запишет связь с контактами $contactBindings элемента.
Данные $contactBindings должны быть в формате, который отдает метод \Bitrix\Crm\Binding\EntityBinding::prepareEntityBindings().
| |
public function unbindContacts(array $contactBindings): void |
Удалит связь с контактами $contactBindings элемента.
Данные $contactBindings должны быть в формате, который отдает метод \Bitrix\Crm\Binding\EntityBinding::prepareEntityBindings().
| |
public function getContactBindings(): array |
Вернет данные о текущих привязках контактов.
Результат будет в формате, который отдает метод \Bitrix\Crm\Binding\EntityBinding::prepareEntityBindings().
| |
public function getObservers(): array |
Вернет массив идентификаторов пользователей, которые являются наблюдателями у элемента. | |
public function setObservers($observerIds): Item |
Запишет идентификаторы пользователей $observerIds в данные о наблюдателях.
Все необходимые действия по сохранению атрибутов доступа и созданию чатов будут произведены при выполнении операции в классе Bitrix\Crm\Field\Observers. | |
public function addToProductRows(ProductRow $product): Result |
Метод добавит orm-объект товарной позиции $product к текущей коллекции товарных позиций. | |
public function removeFromProductRows(ProductRow $product): void |
Метод удалит orm-объект товарной позиции $product из текущей коллекции товарных позиций. | |
public function setProductRowsFromArrays(array $productArrays): Result |
Метод перезапишет данные о коллекции товаров из массива $productArrays.
Каждый элемент массива $productArrays - это массив данных о товарной позиции. | |
public function setProductRows($products): Result |
Метод перезапишет данные о коллекции товаров из параметра $products. $products может быть либо массивом orm-объектов ProductRow, либо коллекцией ProductRowCollection. |
Пользовательские поля. Файловые поля
Для класса нет особенной разницы, с какими полями он работает - полями самой таблицы или пользовательскими. Главное, чтобы эти поля были доступны в EntityObject.
Есть некоторые особенности при обработке полей типа "Файл".
Особенности работы с файловыми полями
С точки зрения базы данных тип поля "Файл" - это число. Но в старом API файл перед сохранением можно было передать в виде массива с описанием загруженного файла (см. \CFile::MakeFileArray())
Чтобы этот способ работал, при передаче таких данных в метод setFromCompatibleData файл сразу сохраняется в таблицу b_file, а его ID записывается в поле EntityObject.
Загрузка производится через сервис загрузки файлов, который очистит файлы, которые не были сохранены.
Примеры
Создание элемента
use Bitrix\Crm\Service;
use Bitrix\Crm\Item;
$factory = Service\Container::getInstance()->getFactory(\CCrmOwnerType::Quote);
$newFile = [
'name' => 'document.docx',
'size' => 145961,
'type' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'description' => '',
'tmp_name' => '/tmp/d1gadg',
];
$fields = [
'UF_CRM_FIELD' => 'some value',
'UF_CRM_FILE' => $newFile,
];
$item = $factory->createItem([
Item::FIELD_NAME_STAGE_ID => 'D128_3:CLIENT' ,
]);
$item->setFromCompatibleData($fields);
// here we can get new file identifier. But if item not saved, this file will be deleted.
$newFileId = $item->get('UF_CRM_FILE');
$result = $item->save();
Изменение элемента
use Bitrix\Crm\Service;
use Bitrix\Crm\Item;
$factory = Service\Container::getInstance()->getFactory(\CCrmOwnerType::Quote);
$item = $factory->getItem(1);
if ($item)
{
$item->setStageId('SENT');
$result = $item->save();
}
Удаление элемента
use Bitrix\Crm\Service;
use Bitrix\Crm\Item;
$factory = Service\Container::getInstance()->getFactory(\CCrmOwnerType::Quote);
$item = $factory->getItem(1);
if ($item)
{
$result = $item->delete();
}
Изменение контактов
Представим, что у нас есть коммерческое предложение с id = 1, привязанное к двум контактам с id = 2 и id = 3. Мы хотим, чтобы оно было привязано к контактам 3 и 4.
use Bitrix\Crm\Binding\EntityBinding;
use Bitrix\Crm\Item;
use Bitrix\Crm\Service;
$factory = Service\Container::getInstance()->getFactory(\CCrmOwnerType::Quote);
$item = $factory->getItem(1);
if ($item)
{
// add new contact with id = 4
$item->bindContacts(EntityBinding::prepareEntityBindings(\CCrmOwnerType::Contact, [4]));
// remove contact with id = 2
$item->unbindContacts(EntityBinding::prepareEntityBindings(\CCrmOwnerType::Contact, [2]));
}
Преимущества и недостатки
Работа с этим классом имеет ряд преимуществ:
- Легко определить, было ли изменено значение поля (через метод
isChanged). - Прозрачный доступ к значениям полей, соответственно их смыслу, независимо от их кода в таблице.
- Типизация и autocomplete.
- Возможность работать с абстракцией.
При работе с этим классом есть ряд недостатков, связанных с особенностями поведения EntityObject:
- Значения полей приводятся к соответствующему типу. Из-за этого числовые поля с незаполненными значениями отдают 0. И нет никакой возможности определить, там записан 0 или поле не заполнено.
- При работе непосредственно с объектом нет возможности определить, было ли значение поля передано с фронта, или нет. Метод
isChangedговорит только о том, изменено ли значение.
Эти недостатки известны, возможно, через некоторое время будут добавлены новые методы, позволяющие их нивелировать.