Bitrix24 Tasks: Основной класс для работы с сущностями на стороне сервера

Пространство имен /lib/item.
Создание, удаление, обновление, поиск, модификации, доступ к задачам через основной апи.

Создание сущности и получение данных

Создание инстанса сущности.

$task = new \Bitrix\Tasks\Item\Task(100); // получение сущности с выбранным id
$task = new \Bitrix\Tasks\Item\Task(); // создание экземпляра новой сущности (например, новой задачи, которой еще нет в базе данных)
$task = new \Bitrix\Tasks\Item\Task(0, 1); // создание экземпляра новой сущности из под пользователя 1
$task = new \Bitrix\Tasks\Item\Task(array('TITLE' => 'LALA')); // создание экземпляра новой сущности задачи с предустановленным полем TITLE

API скрывает под собой работу с орм, напрямую с орм разработчик не работает. Данные из таблиц тянутся лениво при обращении к соответствующим полям сущности. Есть три типа полей: * поля таблета, обслуживающего таблицу сущности (только физически существующие колонки, всякие expression-ы и runtime колонки игнорируются) * поля юзерфилдов * кастомные поля * связи 1-1 и 1-n с другими сущностями * абсолютно любые другие поля, можно прикрутить хоть чтение из файла, написав соответствющий контроллер поля

API реализует интерфейс ArrayAccess

print_r($task['TITLE']); // заголовок
print_r($task['DESCRIPTION']); // описание
print_r($task['SE_CHECKLIST']); // чек-лист
print_r($task['UF_TASK_WEBDAV_FILES']); // список аттачей диска

API реализует интерфейс IteratorAggregate

foreach($task as $key => $value)
{
    print_r($key);
    print_r($value);
    print_r('========================');
}

Также к полям можно обращаться через магию:

print_r($task->title); // заголовок
print_r($task->description); // описание
print_r($task->seCheckList); // чек-лист
print_r($task->ufTaskWebDavFiles); // список аттачей диска

Получение данных оптом

Есть возможность получить значения всех полей оптом, для этого есть метод getData(). Если данный метод вызывается без аргументов, то он заставит апи скачать абсолютно все поля данной сущности, а это может быть не такой уж и быстрой операцией. Поэтому, есть возможность явно указать поля для выборки.

$data = $task->getData(array('ID', 'TITLE')); // в $data только ID и TITLE
$data = $task->getData('#'); // в $data только поля таблета
$data = $task->getData('UF_#'); // в $data только пользовательские поля
$data = $task->getData('~'); // в $data только те поля, которые были скачаны ранее
$data = $task->getData(array('ID', 'TITLE', 'UF_#', 'SE_CHECKLIST')); // в $data только ID, TITLE, чек-лист и все пользовательские поля

Экспорт данных в массив

Метод getData(), кроме обычных скаляров, приходящих из таблицы сущности, возвращает объекты-коллекции связанных сущностей и\или объекты-значения других кастомных полей. Есть возможность получить данные сущности в виде обычного многомерного массива при помощи метода export()

$data = $task->export(array('ID', 'SE_CHECKLIST')); // в $data содержится ID и многомерный массив - список элементов чек-листа

Модификация сущности

Для модификации сущностей можно просто присваивать новые значения полям. При этом, поскольку входящее значение пропускается через контроллер поля, то и значения можно задавать разные. Какие именно — зависит от того, какие значения умеет распознавать контроллер. Например, дата допускает присваивание инстанса \Bitrix\Main\Type\DateTime или строки даты в формате сайта (можно еще сделать, чтобы она понимала таймстамп и строку в формате ISO 8601, что было бы полезно для REST).

Данное API толерантно к ошибочным входным данным. Оно не станет кидать никаких исключений или еще каким-то образом фейлится. С одной стороны это удобно, нет лишнего брутализма, но с другой — все остается на совести разработчика (или клиента, если это rest). То есть, если вы пихаете в булевую колонку какую нибудь строку, то на выходе получите все равно дефолтное значение для этого поля (кстати про булеву колонку это спорно. может, стоит пихать туда значение, соответствующее истине?)

$titleBefore = $task['TITLE']; 

$task['TITLE'] = 'Новый заголовок';
$task['DEADLINE'] = '18.05.1988 10:20'; // будет сконвертировано в инстанс \Bitrix\Main\Type\DateTime

$titleBefore !== $task['TITLE']; // true

$task['SE_CHECKLIST'] = array(array('TITLE' => 'Раз'), array('TITLE' => 'Два')); // задание чек-листа из двух элементов

Сохраниение сущности в базу данных

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

$result = $task->save();
if($result->isSuccess())
{
    print('EEEEEhaaaa!!!');
}
else
{
    print('Suck:');
    print_r($result->dump());
}

Удаление сущности из базы данных

Аналогичным образом работает удаление

$result = $task->delete();
if($result->isSuccess())
{
    print('EEEEEhaaaa!!!');
}
else
{
    print('Suck:');
    print_r($result->dump());
}

Обращение к несуществующему экземпляру сущности

При обращении к полям сущности, для которой нет записи в таблице, или к котрой у пользователя которой нет доступа, всегда возвращается null. Это спорный момент, но я не придумал ничего лучше.

$task = new \Bitrix\Tasks\Item\Task(100500); // задачи с ID 100500 не существует
$task['TITLE'] === null; // true

$task->save()->isSuccess(); // false
$task->delete()->isSuccess(); // false

Работа с закешированными сущностями

Если необходимо только читать данные какой-то сущности, без модификации, то есть возможность получать кешированный инстанс. При этом, у такого инстанса взведен флаг «immutable», то есть такой экземпляр нельзя изменить и\или сохранить в базу данных. Это бывает удобно при использовании функционала модуля задач в других модулях, когда нужно просто получить данные, например, задачи. Если один раз флаг immutable установлен в true, снять его обратно законным способом невозможно.

$task = \Bitrix\Tasks\Item\Task::getInstance(100);
$data = $task->getData(); // ok
$task->title = 'Этот заголовок не будет установлен'; // ничего не изменится
$task->save()->isSuccess(); // false
$task->isImmutable(); // true

Поиск сущностей

Как и в орм, сущности можно искать. Функция поиска возвращает коллекцию, которая реализует интерфейс Iterator, а также может содержать ошибки, если что-то пошло не так. Первый параметр метода find() прямо переходят в параметры orm-вызова.

$tasks = \Bitrix\Tasks\Item\Task::find(array('select' => array('ID', 'TITLE'), 'filter' => array('=STATUS' => 2)));

if($tasks->isSuccess())
{
   foreach($tasks as $task)
   {
        print_r($task);
   }
}
else
{
    print_r($tasks->getErrors()->getMessages());
}

Контроль доступа

В сущностях существует отдельный контроллер доступа, его можно получить через getAccessController(). Контроллер можно включать (enable()), отключать (disable()), также проверять возможность выполнения действий, например canRead(), canUpdate(), etc.

Частый сценарий: отключить проверки прав на какой-то сущности. В старом API для отключения проверок прав просто передавался ID админа, как пользователя, от лица которого производятся действия. Так делать не нужно, это плохо. Поступать нужно так: изначально в сущности используется контроллер по умолчанию, который, по понятным причинам immutable, но можно его клонировать, выключить и назначить заново этой сущности:

$controllerDefault = $task->getAccessController(); // получаем контроллер по умолчанию
$controller = $controllerDefault->spawn();
$controller->disable();
$task->setAccessController($controller); // все, в сущности $task можно выполнять любые действия

Контекст выполнения

В сущностях существует контекст выполнения. Контекст содержит заданный ID пользователя, ID сайта, текущее время. Контекст по-умолчанию также immutable, если есть необходимость его модифицировать — поступать следует так же, как и с контроллером доступа.

$contextDefault = $task->getContext();
$context = $contextDefault->spawn();
$context->setNow(DateTime::createFromUserTime('10.03.2017 09:00:00'));
$context->setUserId(600);
$task->setContext($context);

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

$task->setUser(600);

Под капотом

Основной класс лежит в /lib/item.php — \Bitrix\Tasks\Item. Класс содержит метод getMap(), который, как и в орм, предоставляет карту полей сущности. Каждое поле это объект-контроллер, базовым классом которого является \Bitrix\Tasks\Item\Field\Scalar. Значения полей сущности могут быть также обычными скалярами (из таблицы) или объектами абсолютно разной природы, например \Bitrix\Main\Type\DateTime или \Bitrix\Tasks\Util\Collection. За счет того, что все операции с полями производятся через контроллеры-объекты, а таже значения полей это тоже объекты, мы можем запилить очень гибкую систему полиморфизма.

Трансформация одной сущности в другую

Для преобразвания сущностей из одной в другую написан набор классов-конвертеров. Базовый класс: \Bitrix\Tasks\Item\Converter. Конкретные реализации класса можно найти в пространствах имен \Bitrix\Tasks\Item\Converter\Task и \Bitrix\Tasks\Item\Converter\Task\Template. Примеры трансформаций:

$template = $task->transform(new \Bitrix\Tasks\Item\Converter\Task\ToTemplate())->getInstance();
$task = $task->transform(new \Bitrix\Tasks\Item\Converter\Task\ToTask())->getInstance();

Можно преобразовать элемент чек-листа в задачу (такой функционал точно скоро захотят):

$task = $checklistElement->transform(new \Bitrix\Tasks\Item\Converter\Task\CheckList\ToTask())->getInstance();

Лишние запросы

Поскольку прямого доступа к орм нет, то апи само решает, как, когда и каким образом обращаться к базе данных за информацией. Как и в любой автоматизированной системе, бывают хорошие ситуации, а бывают плохие. Поля скачиваются из базы данных блоками, но иногда API может выполнять лишние запросы, которых можно было бы избежать, немного изменив вызывающий код. Поэтому, профилирование, тавагищи, профилирование…

Пере-ООП

Апи вдоль и поперек объектное. Версии PHP до 5.6 (а может и после) медленно работают с объектами. Конечно, апи еще молодое, и внутри него можно было бы подкрутить гайки в плане производительности. Где-то не создать объект, а массив, где-то не делать лишнего. А если предположить, что PHP тратит много времени на выделение памяти при создании и сносе объекта, то ситуацию еще может улучшить использование паттерна object pool, но не того, к которому мы привыкли в Битриксе, а этого: https://en.wikipedia.org/wiki/Object_pool_pattern. Однако, оптимизации стоит проводить обдуманно, чтобы избежать object cesspool при отдаче инстанса во внешний клиентский код.

Изменение связанной сущности

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

$task->seChecklist->push(array('TITLE' => 'Новый пункт!'));
$task->save(); // новый пункт не будет добавлен

Эту задачу можно решить, введя двухсторонние ссылки между объектом базовой сущности и объектами значений полей этой сущности, тогда объекты значений будут «информировать» базовую сущность о том, что «эй чувак, я поменялся». Пока данная ситуация лечится явным указанием базовой сущности, что поле поменялось:

$task->seChecklist->push(array('TITLE' => 'Новый пункт!'));
$task->setFieldModified('SE_CHECKLIST');
$task->save(); // новый пункт не будет добавлен

Донорство объектов

Не понятно, что делать в таких ситуациях:

$task1 = new Task(1);
$task2 = new Task(2);

// такое действие не приведет ни к чему хорошему:
$task2->seCheckList = $task1->seCheckList;
$task2->save();

Варианты действий при «донорстве» объекта значения от одной базовой сущности другой:

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

Циклические запросы в результате работы метода find()

На данный момент find() не умеет выбирать связанные сущности, вроде чек-листа задач. Таким образом, такой код будет выполнять циклические запросы:

$items = Task:find(array('select' => array('ID', 'TITLE', 'SE_CHECKLIST')));
foreach($items as $item)
{
 print_r($item['SE_CHECKLIST']); // каждый раз новый запрос
}

Решение может быть следующим: внутри find() перед вызовом орм метода getList() выбирать список связанных сущностей, а после получения списка задач выбирать их по перечню ID задач и «дописывать» в объекты.

Bitrix24 Tasks: Основной класс для работы с сущностями на стороне сервера: 2 комментария

Оставьте комментарий