Элементы

Элемент фабрики в Битрикс24 — это базовая единица данных, используемая для управления и автоматизации бизнес-процессов. Элементы фабрики могут представлять собой различные сущности, такие как сделки, лиды, контакты, компании и другие объекты CRM. Каждый элемент содержит набор полей и свойств, которые описывают его характеристики и состояние.

Каждый элемент описывается наследником абстраткого класса Bitrix\Crm\Item, например сделка описывается классом Bitrix\Crm\Item\Deal наследником Bitrix\Crm\Item.

Получение элементов

На фабрике существует 4 способа для получения элементов:

  • Получение объекта по идентификатору
  • Получение объектов по фильтру
  • Получение объектов по фильтру с учетом прав
  • Низкоуровневые запросы

Я знаю ID элемента, как мне получить объект?

Для получения объекта по идентификатору существует метод getItem(int $id): ?Item. Пример использования:


$elementId = 123;

/**
 * @var \Bitrix\Crm\Item
 */
$itemObject = $factory->getItem($elementId);

Я хочу найти все элементы по фильтру

Предположим мы хотим найти все элементы по какому-то фильтру (например ID больше 10 и меньше 15). Получить элементы можно используя метод getItems(array $parameters = []): array:

$filter = [
    '><ID' => [10, 15],
];

$elements = $factory->getItems([
    'filter' => $filter
]);

Предположим, мы хотим усовершенствовать запрос и получить только те элементы которые доступны текущему пользователю - для этого можно воспользоваться методом getItemsFilteredByPermissions, который имеет следующую сигнатуру:

public function getItemsFilteredByPermissions(
    array $parameters,
    ?int $userId = null,
    string $operation = UserPermissions::OPERATION_READ
): array

Используя этот метод мы можем получить не только элементы доступные текущему пользователю, но так же можем запросить еще и доступные другомому пользователю с учетом его прав на элементы.

Набор параметров $parameters являетися унифицированным и наиболее приближен к параметрам ORM, однако настоятельно не рекомендую использовать в методах поиска что-либо кроме filter, order, limit, offset, а runtime использовать с осторожностью.

В фильтре допустимо использовать следующие примеры:

= равно (работает и с массивами)
% подстрока
> больше
< меньше
@  IN (EXPR), в качестве значения передается массив или объект DB\SqlExpression
!@  NOT IN (EXPR), в качестве значения передается массив или объект DB\SqlExpression

!= не равно
!% не подстрока
>< между, в качестве значения передается массив array(MIN, MAX)
>= больше или равно
<= меньше или равно
=% LIKE
%= LIKE

Пояснение по префиксам
Префиксы %= и =% эквивалентны, все примеры для одного подходят для второго:
'%=NAME' => 'тест' - отбор строки по LIKE (НЕ ПОДСТРОКИ)
'%=NAME' => 'тест%' - отбор записей, содержащих "тест" в начале поля NAME
'%=NAME' => '%тест' - отбор записей, содержащих "тест" в конце поля NAME
'%=NAME' => '%тест%' - отбор записей, содержащих подстроку "тест" в поле NAME
Последний вариант отличается от %NAME => тест итоговым sql-запросом.
== булевое выражение для ExpressionField (например, для EXISTS() или NOT EXISTS())
!>< не между, в качестве значения передается массив array(MIN, MAX)
!=% NOT LIKE
!%= NOT LIKE
'==ID' => null - условие, что поле ID равно NULL (в sql-запросе будет преобразовано в ID IS NULL)
'!==NAME' => null - условие, что поле NAME не равно NULL (в sql-запросе будет преобразовано в NAME IS NOT NULL)

Подсчет количества

Часто возникает необходимо получить только количество элементов удовлетворяющих фильтру и в таком случае запрашивать все поля элементов представляется сильно избыточным механизмом. Для этого у фабрики есть методы getItemsCount и getItemsCountFilteredByPermissions которые находят количество элементов по фильтру и то же самое только с учетом прав. Сигнатуры методов выглядят следующим образом:

public function getItemsCount(array $filter = []): int

public function getItemsCountFilteredByPermissions(
        array $filter = [],
        ?int $userId = null,
        string $operation = UserPermissions::OPERATION_READ
    ): int

Пример подсчета элементов, которые начинаются в буквы “А”:

$filter = [
    '%=NAME' => 'A'
];

$elementCount = $factory->getItemsCount([
    'filter' => $filter
]);

Создание элемента

Мы разобрали ситуацию когда элементы уже существуют, но как правильно создать элементы? Поскольку фабрика является абстрактной (незивестно какие поля она имеет) элемент так же является абстракцией. Чтобы получить объект необходимо использовать метод createItem(array $data = []): Item, при этом в ключе $data можно указать первоначальные значения.

Пример создание объекта(!) сделки с заранее установленным названием:

$newDeal = $factory->createItem([
    'TITLE' => 'New Title'
]);

Не обязательно знать все поля сразу, можно воспользоваться сеттерами позднее:

/**
 * Create deal object
 *
 * @var       \Bitrix\Crm\Item\Deal
 */
$newDeal = $factory->createItem();

// Configure title with margic method

$newDeal->setTitle("NewTitle");

// or configure with "general" method

$newDeal->set('TITLE', 'NewTitle');

Важное примечание: создание объекта сделки это не создание самой сделки. Объект еще необходимо сохранить в базе данных, поэтому если вы не сделали сохранение по завершению скрипта объект будет уничтожен и данные потеряны!

Сохранение самого элемента в базе данных выполняется при помощи операций.

Редактирование элемента

Процесс редактирования элемента аналогичен процессу работы с созданием элемента, с отличием в том что вы используете операцию редактирования и элемент у вас уже существует. Сейчас мы не будем вдаваться в подробности выполнения операции обновления, а покажем пример изменения названия у существующего элемента. Предположим нам необходимо поменять название (на Updated title) и ответственного (на пользователя с ID = 3) у элемента ID:10 смарт-процесса с типом 163:

use \Bitrix\Main\Loader;
use \Bitrix\Crm\Service\Container;

Loader::requireModule('crm');

$dealFactory = Container::getInstance()->getFactory( 163 );

$elementId = 10;

$itemObject = $factory->getItem($elementId);

$itemObject->setTitle("Updated title")
    ->setAssignedById(3);

Важное примечание: выполнение методов на изменение полей в объекте меняет данные самого объекта и не будут сохранены в базу данных до выполнения соответствующей операции. Для сохранения изменений в базу данных необходимо воспользоваться операциями

В приведенном примере мы воспользовались приемом “цепочки вызовов” - почти каждый set* метод возвращает свой экземпляр, поэтому вместо того чтобы копировать одинаковые строчки мы можем использовать более простую конструкцию. Например мы могли бы написать этот код иначе и результат работы от этого не изменился бы:

$itemObject->setTitle("Updated title");
$itemObject->setAssignedById(3);

Подробнее с существующими методами можно ознакомиться в классе bitrix/modules/crm/lib/item.php.

Работа с элементом

Ранее мы изучили работу над элементами и теперь когда мы знаем как создать или получить элемент пришло время поговорить о самом элементе. Представление элемента в коде в универсальном api - объект и это представление конкретного элемента. Можно по разному классифицировать его “свойства”:

  • Делить на общие (присующие всем элементам, например название) и частные (существующие только у определенного, например UF-* поля)
  • Рассматривать с точки зрения хранения - скалярные (число, строка) и связи (товарные позиции, наблюдатели)

Материал не в состоянии охватить все аспекты работы с элементом, поэтому в данной главе мы будем рассматривать лишь некоторые случаи для демонстрации общего подхода.

Массив

Иногда необходимо получить вметсо объектного представления - массив или json. За это отвечают специальные методы:


$elementData = $element->getData();
echo "<pre>";
var_dump($elementData);
echo "</pre>";

$elementArray = $element->getCompatibleData();
echo "<pre>";
var_dump($elementArray);
echo "</pre>";


$jsonString = $element->jsonSerialize();  
$jsonString = $element->toArray(); // Alias for jsonSerialize method  

Не пытайтесь посмотреть элемент через var_dump или print_r - это чревато большим расходом памяти - ваш битрикс может не выдержать и упасть из-за недостатка памяти. Вы получите белую страницу.

Важно обратить внимание - getData и getCompatibleData умеют принимать параметр отвечающий за то какого именно типа данные должны быть извлечены. Допустимы следующие параметры:

  • \Bitrix\Main\ORM\Objectify\Values::ALL - вернутся все значения полей (по-умолчанию)
  • \Bitrix\Main\ORM\Objectify\Values::ACTUAL - вернутся только исходные значения полей
  • \Bitrix\Main\ORM\Objectify\Values::CURRENT - вернутся только текущие значения полей

Общие и uf поля

Получить значение поля можно двумя способами: через магический метод и через общий метод.

Пример получения данных через магический метод:

$title = $element->getTitle();

Пример получения данных через общий метод:

$title = $element->get('TITLE');

Аналогично методам get существуют set методы:

$element->setTitle("Your title here");

И общий метод:

$element->set('TITLE', "Your title here");

Как проверить что значение изменилось:

$this->isChangedTitle();
$this->isChanged('TITLE');

Какие еще данные можно получать? Список достаточно большой, например включая но не ограничиваясь:

/**
 * Class Item
 * @package Bitrix\Crm
 *
 * @method string|null getTitle()
 * @method Item setTitle(string $title)
 * @method bool isChangedTitle()
 * @method DateTime|null getCreatedTime()
 * @method Item setCreatedTime(DateTime $dateTime)
 * @method DateTime|null getUpdatedTime()
 * @method Item setUpdatedTime(DateTime $dateTime)
 * @method DateTime|null getMovedTime()
 * @method Item setMovedTime(DateTime $dateTime)
 * @method int|null getCreatedBy()
 * @method Item setCreatedBy(int $createdBy)
 * @method int|null getUpdatedBy()
 * @method Item setUpdatedBy(int $updatedBy)
 * @method int|null getMovedBy()
 * @method Item setMovedBy(int $movedBy)
 * @method int|null getAssignedById()
 * @method Item setAssignedById(int $assignedById)
 * @method bool|null getOpened()
 * @method Item setOpened(bool $isOpened)
 * @method Date|null getBegindate()
 * @method Item setBegindate(Date $begindate)
 * ...
 * ...
 * ...
 */

Получить UTM-метки элемента можно через метод getUtm:

$utmFields = $element->getUtm();

echo "<pre>";
var_dump($utmFields);
echo "</pre>";

Подробнее с существующими методами можно ознакомиться в классе bitrix/modules/crm/lib/item.php.

Важное примечание: поскольку элемент это абстракция некоторые поля могут иметь одинаковое семантическое значение, но разный код. Например поле “Статус лида” и “Стадия сделки” - они оба хранят статус элемента но имеют разные коды. Для таких полей в классе \Bitrix\Crm\Item и его наследниках есть константы полей позволяющие унифицировать работу с элементом - FIELD_NAME_* константы.

Например для получения стадии лида И сделки можно использовать следующий универсальный код:

$element->get( $element::FIELD_NAME_STAGE_ID ); 

Работа с пользовательскими полями не отличается от общих полей, разве что именем служит код такого поля. Например для изменения поля UF_CRM_9_UDTGLCGT будет одинаково работать как код:

$element->set('UF_CRM_9_UDTGLCGT', $value);

так и:

$element->setUfCrm9Udtglcgt($value);

Однако во избежание ошибок трансформации названия вариант с camelCase строго не рекомендуется.

Что вообще за ошибка трансформации? Битрикс пытается привести к camelCase к snakeCase варианту через замену подчеркивание на uppercase символ, таким образом ASSIGNED_BY_ID становится assignedById, но у цифр uppercase варианта нет, а в системе технически могут существовать 2 разных поля UF_CRM_1_1 и UF_CRM_11. Представьте себя на месте Битрикса - куда положить значение поля ufCrm11 ?

Товарные позиции

Элемент может так же работать с товарными позициями, причем делать это как в ООП стилистике, так и поддерживать обратную совместимость через массивы данных.

Упрощенный пример работы с методом обратной-совместимости:


use \Bitrix\Main\Loader;
use \Bitrix\Crm\Service\Container;

Loader::requireModule('crm');

$element = Container::getInstance()
    ->getFactory( OUR_ENTITY_TYPE_ID )
    ->getItem( OUR_ID );

/**
 * Product parse result
 * @var        \Bitrix\Main\Result
 */
$productParseResult = $element->setProductRowsFromArrays([
    // Add product position for exist product
    [
        'PRODUCT_NAME' => 'Product name',
        'PRODUCT_ID' => '222', // Exist product or variation id
        'PRICE' => 100, // Price
        'QUANTITY' => 1,
    ],

    // Add product position without product
    [
        'PRODUCT_NAME' => 'Non exist product name',
        'PRODUCT_ID' => '',
        'PRICE' => 150,
        'QUANTITY' => 2,
    ],
]);

if ( !$productParseResult->isSuccess() )
{
    // $productParseResult->getErrorMessages()
    // Something wrong with products - no add invoice
    return null;
}

Важное примечание: выполнение методов на изменение полей в объекте меняет данные самого объекта и не будут сохранены в базу данных до выполнения соответствующей операции. Для сохранения изменений в базу данных необходимо воспользоваться операциями

Нестатический метод setProductRowsFromArrays принимает на вход массив описания товарных позициий, а сама товарная позиция может описываться следующими полями:

  • ID (int) - идентификатор товарной позиции (если существует)
  • PRODUCT_ID (int) - идентификатор товарной позиции (или товара) из каталога CRM. Для товаров отсутствующих в каталоге = 0.
  • PRODUCT_NAME (string, 256) - Название товарной позиции
  • PRICE (float, 2) - Стоимость товарной позиции
  • PRICE_ACCOUNT (float, 2) - стоимость товарной позиции в базовой валюте для отчетов
  • PRICE_EXCLUSIVE (float, 2) - стоимость товарной позиции без налога и без скидки.
  • PRICE_NETTO (float, 2) - стоимость товарной позиции с налогом и без скидки.
  • PRICE_BRUTTO (float, 2) - стоимость товарной позиции без налога, но со скидкой.
  • QUANTITY (float, 4) - количество товарной позиции
  • DISCOUNT_TYPE_ID (int) - идентификатор типа скидки (см. таблицу типов ниже)
  • DISCOUNT_RATE (float, 2) - размер скидки в процентах
  • DISCOUNT_SUM (float, 2) - размер скидки в базовой валюте
  • TAX_RATE (float, 2) - величина налога в базовой валюте
  • TAX_INCLUDED (string, 1) - если налог включен в цену - Y, иначе N
  • CUSTOMIZED (string, 1) - если товар выбран из торгового каталога (имеет заполненный PRODUCT_ID), при этом пользователь не менял его настройки (скидку, налог, цену, название) то N, иначе Y
  • MEASURE_CODE (int) - идентификатор единицы измерения
  • MEASURE_NAME (string, 50) - название единицы измерения
  • SORT (int) - число определения порядка элементов
  • XML_ID (string) - внешний код товарной позиции
  • TYPE (int) - тип товара (см. ниже типы товаров)
  • STORE_ID (int) - идентификатор склада
  • INPUT_RESERVE_QUANTITY (float) - количество товара в резерве
  • DATE_RESERVE_END (date) - дата окончания резервирования

Идентификатор типа скидки определяется классом Bitrix\Crm\Discount:

  • \Bitrix\Crm\Discount::UNDEFINED (значение: 0) - неизвестно (ошибка)
  • \Bitrix\Crm\Discount::MONETARY (значение: 1) - произвольная сумма
  • \Bitrix\Crm\Discount::PERCENTAGE (значение: 2) - процент

Идентификаторы типов товаров определяются в классе \Bitrix\Crm\ProductType и повторяют собой типы товаров из модуля “Каталог”:

  • \Bitrix\Crm\ProductType::TYPE_PRODUCT (знач. 1) - простой товар (из модуля каталог \Bitrix\Catalog\ProductTable::TYPE_PRODUCT)
  • \Bitrix\Crm\ProductType::TYPE_SET (знач. 2) - комплект (из модуля каталог \Bitrix\Catalog\ProductTable::TYPE_SET)
  • \Bitrix\Crm\ProductType::TYPE_SKU (знач. 3) - товар с торговыми предложениями (из модуля каталог \Bitrix\Catalog\ProductTable::TYPE_SKU)
  • \Bitrix\Crm\ProductType::TYPE_OFFER (знач. 4) - торговое предложение (из модуля каталог \Bitrix\Catalog\ProductTable::TYPE_OFFER)
  • \Bitrix\Crm\ProductType::TYPE_FREE_OFFER (знач. 5) - торговое предложение, у которого нет товара (не указан или удален). (из модуля каталог \Bitrix\Catalog\ProductTable::TYPE_FREE_OFFER)
  • \Bitrix\Crm\ProductType::TYPE_EMPTY_SKU (знач. 6) - специфический тип, означает невалидный товар с торговыми предложениями. (из модуля каталог \Bitrix\Catalog\ProductTable::TYPE_EMPTY_SKU)
  • \Bitrix\Crm\ProductType::TYPE_SERVICE (знач. 7) - услуга (из модуля каталог \Bitrix\Catalog\ProductTable::TYPE_SERVICE)

Альтернативой упрощенному способу работы становится традиционный подход через работу c объектом \Bitrix\Crm\ProductRow.

Создание объекта товарной позиции:

use \Bitrix\Crm\ProductRow;

// Same as product position without product above
$productRowFields = [
    'PRODUCT_NAME' => 'Non exist product name',
    'PRODUCT_ID' => '',
    'PRICE' => 150,
    'QUANTITY' => 2,
];

$productRow = ProductRow::createFromArray($productRowFields);

Добавление товарной позиции в элемент осуществляется методом addToProductRows который возвращает результат (\Bitrix\Main\Result) добавления товарной позиции в элемент:


// $productRow = ProductRow::createFromArray($productRowFields);

/**
 * @var    \Bitrix\Main\Result
 */
$addProductRowResult = $element->addToProductRows($productRow);

if ( !$addProductRowResult->isSuccess() )
{
    // Add failed, get error from `Bitrix\Main\Result` ($addProductRowResult->getErrorMessages())
}

Важное примечание: выполнение методов на изменение полей в объекте меняет данные самого объекта и не будут сохранены в базу данных до выполнения соответствующей операции. Для сохранения изменений в базу данных необходимо воспользоваться операциями

Удаление товарной позиции из элемента осуществляется методом removeFromProductRows (ничего не возвращает):

$element->removeFromProductRows($productRow);

Важное примечание: выполнение методов на изменение полей в объекте меняет данные самого объекта и не будут сохранены в базу данных до выполнения соответствующей операции. Для сохранения изменений в базу данных необходимо воспользоваться операциями

Изменение товарной позиции по ее идентификатору осуществляется через метод updateProductRow, который принимате на вход идентификатор товарной позиции и массив изменяемых полей товарной позиции. Необходимо передавать измененные поля относительно “родного объекта”, так как все остальные поля будут сброшены в изначальное положение.

Важное примечание: метод не позволяет работать с резервированием товаров!

/**
 * Product row identifier
 *
 * @var        int
 */
$productRowId = 123;

/**
 * Changed product row fields
 *
 * @var        array
 */
$productRowArray = [
    'PRICE' => 123.00
];

$updateRowResult = $element->updateProductRow($productRowId, $productRowArray);
if ( !$updateRowResult->isSuccess() )
{
    // Update failed, get error from `Bitrix\Main\Result` ($updateRowResult->getErrorMessages())
}

Важное примечание: выполнение методов на изменение полей в объекте меняет данные самого объекта и не будут сохранены в базу данных до выполнения соответствующей операции. Для сохранения изменений в базу данных необходимо воспользоваться операциями

Замена товарных позиций или изменение списком осуществляется через метод setProductRows, который принимает массив объектов ProductRow или коллекцию товарных позций (EO_ProductRow_Collection). При отсутствии товарных позиций они будут установлены, а при наличии - элементы которые будут отсутствовать удалены. Пример работы:


use \Bitrix\Crm\ProductRow;

/**
 * List of product rows
 * @var        ProductRow[]
 */
$productRows = [
    ProductRow::createFromArray([
        'PRODUCT_NAME' => 'Product one',
        'PRICE' => 100,
        'QUANTITY' => 2,
    ]),

    ProductRow::createFromArray([
        'PRODUCT_NAME' => 'Product two',
        'PRICE' => 200,
        'QUANTITY' => 4,
    ]),
];

/**
 * @var    \Bitrix\Main\Result
 */
$setRowsResult = $element->setProductRows($productRows);

if ( !$setRowsResult->isSuccess() )
{
    // Setup failed, get error from `Bitrix\Main\Result` ($setRowsResult->getErrorMessages())
}

Важное примечание: выполнение методов на изменение полей в объекте меняет данные самого объекта и не будут сохранены в базу данных до выполнения соответствующей операции. Для сохранения изменений в базу данных необходимо воспользоваться операциями

Файлы

Работа с файлами в универсальном API более сложная: для того чтобы добавить/изменить поле с типом “Файл” у элемента помимо загрузки файла необходимо будет еще зарегистрировать его в загрузкиче файлов. Наши вводные данные:

  • В переменной $factory содержится объект фабрики для элемента
  • В переменной $element содержится объект элемента
  • В переменной $fileId содержится идентификатор файла, который был загружен (через \CFile::SaveFile или получен из таблицы b_file)
  • Код изменяемого поля содержитя в $ufFieldCode

Код будет выглядеть следующим образом:

use \Bitrix\Crm\Service\Container;

//
// Step 1: Register file in uploader
//
$fileFieldObject = $factory->getFieldsCollection()
    ->getField($ufFieldCode);

$fileUploader = Container::getInstance()->getFileUploader();
$fileUploader->registerFileId($fileFieldObject, $fileId); //Clone row if we need to set more than one file


//
// Step 2: set field value
//

// If `$ufFieldCode` - single field:
$element->set($ufFieldCode, $fileId);

// If `$ufFieldCode` - multiple use instead: 
// $element->set($ufFieldCode, [$fileId]);


// Then save $element with operation

Важное примечание: выполнение методов на изменение полей в объекте меняет данные самого объекта и не будут сохранены в базу данных до выполнения соответствующей операции. Для сохранения изменений в базу данных необходимо воспользоваться операциями

Связи

Наблюдатели

Элементы могут поддерживать работу с наблюдателями - пользователями которые имеют доступ для просмотра элемента без возможности его редактировать. Для получения списка наблюдателей используется метод getObservers, который возвращает список идентификаторов пользователей:


/**
 * List of element observers
 *
 * @var    int[]
 */
$observerIds = $element->getObservers();

echo "<pre>";
var_dump($observerIds);
echo "</pre>";

Установить наблюдателей можно через метод setObservers - он удалит существующих, но не переданных наблюдателей и добавит новых. Пример работы:

/**
 * @var        int[]
 */
$newObserversIds = [1, 2, 3];

$element->setObservers($newObserversIds);

Метод возвращает объект текущего класса, поэтому его можно использовать в цепочке вызовов.

Важное примечание: выполнение методов на изменение полей в объекте меняет данные самого объекта и не будут сохранены в базу данных до выполнения соответствующей операции. Для сохранения изменений в базу данных необходимо воспользоваться операциями

Поле Клиент

Элемент CRM поддерживает работу виртуального поля “Клиент”, которое физически может выражаться в заполненных полях: Компания, Контакт, Контакты. При этом у одного элемента CRM может быть выбрана только одна компания и несколько контактов. Возможные комбинации:

  • Только Компания
  • Только Контакт
  • Компания и Контакт (один) этой компании
  • Контакты (несколько, любые)
  • Компания и контакты этой компании
  • Компания, Контакт (этой компании) и контакты (этой компании)

Проверить заполнено ли это поле можно методом isClientEmpty:


/**
 * @var    bool
 */
$isClientEmpty = $element->isClientEmpty();

Получаем идентификатор компании:

/**
 * @var int|null Company identifier
 */
$companyId = $element->getCompanyId();

Получаем данные главного контакта:

/**
 * @var    \Bitrix\Crm\Contact
 */
$primaryContact = $element->getPrimaryContact();

Важно! Это не наследник \Bitrix\Crm\Item, просто данные контакта.

Получить существующе привязки к контактам можно через метод getContactBindings():

/**
 * @var    array
 */
$bindings = $element->getContactBindings();

Добавление контакта к элементу CRM осуществляется через метод bindContacts, который принимает массив привязок которые нужно установить. Приведенный ниже фрагмент показывать как привязать к элементу несколько контактов с идентификаторами ID:1, ID:2, ID:3, из которых ID:1 (первый по сортировке) будет назначен основным контактом.

use Bitrix\Crm\Binding\EntityBinding;

$bindings = EntityBinding::prepareEntityBindings(
    \CCrmOwnerType::Contact,
    [1, 2, 3]
);

$element->bindContacts($bindings);

Обратите внимание на $bindings, альетрнативой такому способу заполнения может стать последовательное добавление элементов:

use Bitrix\Crm\Binding\EntityBinding;

$bindings = [];

EntityBinding::addEntityBinding(\CCrmOwnerType::Contact, 1, $bindings);
EntityBinding::addEntityBinding(\CCrmOwnerType::Contact, 2, $bindings);
EntityBinding::addEntityBinding(\CCrmOwnerType::Contact, 3, $bindings);

Для уделаения привязок, необходимо собрать аналогичную $bindings структуру и передать ее в unbindContacts-метод:

use Bitrix\Crm\Binding\EntityBinding;

$unbindList = [];

EntityBinding::addEntityBinding(\CCrmOwnerType::Contact, 1, $unbindList);
EntityBinding::addEntityBinding(\CCrmOwnerType::Contact, 2, $unbindList);
EntityBinding::addEntityBinding(\CCrmOwnerType::Contact, 3, $unbindList);

$element->unbindContacts($unbindList);

Важное примечание: выполнение методов на изменение полей в объекте меняет данные самого объекта и не будут сохранены в базу данных до выполнения соответствующей операции. Для сохранения изменений в базу данных необходимо воспользоваться операциями

Полезные ссылки

Элементы в документации Особенности работы с коллекциями в документации