Открытые члены |
|
__construct ($message, $code=’BX_ERROR’) | |
getCode () | |
getMessage () | |
getCustomData () | |
__toString () | |
jsonSerialize () | |
Защищенные данные |
|
$code | |
$message | |
$customData | |
См. определение в файле entityerror.php строка 11
◆ __construct()
__construct | ( | $message, | |
$code = 'BX_ERROR' |
|||
) |
◆ __toString()
◆ getCode()
Returns the code of the error.
- Возвращает
- int|string
См. определение в файле error.php строка 42
◆ getCustomData()
|
inherited |
- Возвращает
- mixed|null
См. определение в файле error.php строка 59
◆ getMessage()
Returns the message of the error.
- Возвращает
- string
См. определение в файле error.php строка 51
◆ jsonSerialize()
|
inherited |
◆ $code
◆ $customData
◆ $message
Объявления и описания членов класса находятся в файле:
- C:/Bitrix/modules/main/lib/orm/entityerror.php
В старом ядре
На каждую сущность программируется свой GetList, Update, Add, Delete. В основном копи-пастом. Недостатки: разный набор параметров; разный синтаксис полей фильтров; события могут быть или не быть; иногда разный код под разные БД (Add).
Какая цель поставлена в новом ядре
Сделать операции выборки и сохранения в БД однотипными, с одинаковыми параметрами и фильтрами. По возможности таблицы сущностей должны обслуживаться с минимумом нового кода. Стандартные события добавления/изменения/удаления должны быть доступны автоматически.
Реализация
Введены три понятия:
— сущности (BitrixMainEntityBase)
— поля сущностей (BitrixMainEntityField и его наследники)
— датаменеджер (BitrixMainEntityDataManager)
Сущность описывает таблицу в БД, в т.ч. содержит поля сущностей. Датаменеджер производит операции выборки и изменения сущности. На практике работа в основном ведется на уровне датаменеджера.
Пример
Начнем с простого примера использования датаменеджера для своей сущности:
use BitrixMainEntity; class CultureTable extends EntityDataManager { const LEFT_TO_RIGHT = 'Y'; const RIGHT_TO_LEFT = 'N'; public static function getFilePath() { return __FILE__; } public static function getTableName() { return 'b_culture'; } public static function getMap() { return array( 'ID' => array( 'data_type' => 'integer', 'primary' => true, 'autocomplete' => true, ), 'CODE' => array( 'data_type' => 'string', ), 'NAME' => array( 'data_type' => 'string', 'required' => true, 'title' => Loc::getMessage("culture_entity_name"), ), 'FORMAT_DATE' => array( 'data_type' => 'string', 'required' => true, 'title' => Loc::getMessage("culture_entity_date_format"), ), 'FORMAT_DATETIME' => array( 'data_type' => 'string', 'required' => true, 'title' => Loc::getMessage("culture_entity_datetime_format"), ), 'FORMAT_NAME' => array( 'data_type' => 'string', 'required' => true, 'title' => Loc::getMessage("culture_entity_name_format"), ), 'WEEK_START' => array( 'data_type' => 'integer', ), 'CHARSET' => array( 'data_type' => 'string', 'required' => true, 'title' => Loc::getMessage("culture_entity_charset"), ), 'DIRECTION' => array( 'data_type' => 'boolean', 'values' => array(self::RIGHT_TO_LEFT, self::LEFT_TO_RIGHT), ), ); } }
1. Название сущности CultureTable состоит из собственно названия сущности и обязательного суффикса Table. Почему суффикс? Название класса Culture резервируется на будущее для «настоящей» ORM.
2. Класс должен быть наследован от BitrixMainEntityDataManager.
3. Должны быть перекрыты два абстрактных метода:
— getFilePath() возвращает путь к файлу сущности (в TODO есть избавиться от этого)
— getMap() возвращает массив, описывающий поля сущности. О полях ниже.
4. Метод getTableName() может быть определен и должен вернуть название таблицы. Если метод не переопределен, то базовая сущность попытается получить имя таблицы автоматически, например из BitrixSaleOrderTable получилось бы b_sale_order.
Поля сущностей
Рассмотрим подробнее, как описываются поля сущности. Метод getMap() должен вернуть массив полей сущности вида:
array( "FIELD_CODE" => array( 'data_type' => 'тип поля', другие атрибуты ), … )
Атрибут data_ type описывает тип поля. Сейчас доступны типы:
boolean (наследует ScalarField)
date (наследует ScalarField)
datetime (наследует DateField)
enum (наследует ScalarField)
float (наследует ScalarField)
integer (наследует ScalarField)
string (наследует ScalarField)
text (наследует StringField)
«Другие атрибуты» могут зависеть от типа поля.
Первичный ключ указывается с помощью атрибута ‘primary’=> true. Если первичный ключ — целочисленный с авто увеличением, то нужно указать атрибут ‘autocomplete’ => true.
Атрибут ‘required’ => true указывает на обязательность поля.
Атрибут ‘format’ => ‘паттерн’ валидирует значение по шаблону регулярного выражения, например ‘/^[A-Z][A-Za- z0-9]+$/’.
Атрибут expression позволяет получить вычисляемое значение, например:
'VAT_RATE_PRC' => array( 'data_type' => 'float', 'expression' => array( '100 * %s', 'VAT_RATE' ) ),
Сущности могут быть связаны через поле типа reference. При этом в качестве типа указывается название сущности, на которую идет ссылка, например:
'ORDER' => array( 'data_type' => 'Order', 'reference' => array( '=this.ORDER_ID' => 'ref.ID' ) ),
Атрибут values задает словарь для поля типа bool , например:
'DIRECTION' => array( 'data_type' => 'boolean', 'values' => array(self::RIGHT_TO_LEFT, self::LEFT_TO_RIGHT), ),
Атрибут title задает текстовое название поля.
Датаменеджер
Как видим, класс сущности практически не содержит кода. Базовый класс датаменеджера дает нам следующие методы:
— CultureTable::update($ID, $arFields)
— CultureTable::add($arFields)
— CultureTable::Delete ($ID)
— и наш любимый CultureTable::getList($arParams)
— есть также базовый checkFields(Result $result, array $data, $id = null)
Обратите внимание, все методы статические. Сущность может переопределить эти базовые методы, если это необходимо.
Выборка данных
Для выборки данных используется статический метод getList(). На вход принимается массив параметров:
‘select’ – массив выбираемых в SELECT полей;
‘filter’ – массив условий фильтра для WHERE;
‘group’ – массив полей для группировки;
‘order’ – массив сортировки;
‘limit’ – ограничение количества записей;
‘offset’ – смещение начала выборки;
‘count_total’ – нужно ли считать количество записей выборки.
Пример использования:
$cultureList = CultureTable::getList(array( 'select' =>array('ID', 'NAME'), 'order' => array('NAME' =>'ASC'), 'filter'=>array('=CHARSET'=>'Windows-1251'), ));
Метод возвращает объект нового ядра BitrixMainDBResult, с помощью которого можно выбирать записи:
$cultureRes = CultureTable::getList(array('order'=>array('name'=>'asc'))); while($cult = $cultureRes->fetch()) { var_dump($cult); }
Сортировка может быть задана по нескольким полям. Фильтр задается в расширенном синтаксисе, который применяется в инфоблоках. Использование параметров, неизвестных методу, приведет к выбросу исключения.
Для выборки одной записи по первичному ключу может применяться метод getById():
$cultureDb = CultureTable::getById($cultureId);
Модификация данных
Статические методы add(), update(), delete() служат для модификации данных сущности и вызывают соответствующие события. Методы add() и update() производят встроенную проверку полей сущностей, вызывая метод checkFields().
Методы модификации данных возвращают объекты типов BitrixMainEntityAddResult, BitrixMainEntityUpdateResult и BitrixMainEntityDeleteResult. Эти классы наследованы от BitrixMainEntityResult. Назначение этих объектов – сообщить об успешности операции и дать доступ к ошибкам.
checkFields()
Метод checkFields() датаменеджера в настоящее время проверяет поля на обязательные значения (атрибут поля сущности required) и соответствие маске (атрибут format). Для дополнительных проверок класс сущности может переопределить этот метод:
public static function checkFields(Result $result, array $data, $id = null)
Параметр $result – объект результата. В случае ошибки метод должен добавить ошибку в этот объект. Ошибка может быть либо общая для всей сущности, либо относиться к конкретному полю. В первом случае добавляется объект типа BitrixMainEntityEntityError, вот втором — BitrixMainEntityFieldError:
$result->addError(new EntityEntityError("Нельзя изменить запись номер 1.")); $result->addError(new EntityFieldError(static::getEntity()->getField('NAME'), 'Поле «имя» очень странное.'));
Если ни одна ошибка не была добавлена, то проверка считается успешной.
Параметр $data содержит массив вида ‘код поля’=>’значение поля’.
Параметр $id содержит значение первичного ключа записи, если производится update, либо не указан, если производится add.
Метод не возвращает результат. Фактический результат содержится в объекте $result.
add()
public static function add(array $data)
Параметр $data содержит массив вида ‘код поля’=>’значение поля’.
Метод возвращает объект типа AddResult. Кроме проверки на успешность, объект результата содержит также идентификатор добавленной записи (только для целочисленных первичных ключей с автоинкрементом). Пример:
$arFields = array( "NAME" => $request['NAME'], "FORMAT_DATE" => $request['FORMAT_DATE'], "FORMAT_DATETIME" => $request['FORMAT_DATETIME'], "WEEK_START" => intval($request["WEEK_START"]), "FORMAT_NAME" => CSite::GetNameFormatByValue($request["FORMAT_NAME"]), "CHARSET" => $request['CHARSET'], "DIRECTION" => $request['DIRECTION'], "CODE" => $request['CODE'], ); if($ID > 0) { $result = CultureTable::update($ID, $arFields); } else { $result = CultureTable::add($arFields); $ID = $result->getId(); } if($result->isSuccess()) { if($request["save"] <> '') LocalRedirect(BX_ROOT."/admin/culture_admin.php?lang=".LANGUAGE_ID); else LocalRedirect(BX_ROOT."/admin/culture_edit.php?lang=".LANGUAGE_ID."&ID=".$ID."&".$tabControl->ActiveTabParam()); } else { $errors = $result->getErrorMessages(); }
update()
public static function update($primary, array $data)
Параметр $primary содержит значение первичного ключа. Для составных ключей можно передать массив значений.
Параметр $data содержит массив вида ‘код поля’=>’значение поля’.
Метод возвращает объект типа UpdateResult. Пример см. выше.
delete()
public static function delete($primary)
Параметр $primary содержит значение первичного ключа. Для составных ключей можно передать массив значений.
Метод возвращает объект типа DeleteResult. Пример:
$result = CultureTable::delete($ID); if(!$result->isSuccess()) { $adminList->AddGroupError("(ID=".$ID.") ".implode("<br>", $result->getErrorMessages()), $ID); }
Результат
Как видно из примеров, объект результата содержит в себе признак успешности (isSuccess() возвращает булевое значение) и список ошибок (getErrorMessages() возвращает массив строк с ошибками):
if(!$result->isSuccess()) $errors = $result->getErrorMessages();
Метод getErrors() возвращает массив объектов типов EntityEntityError или EntityFieldError.
Класс AddResult содержит метод getId(), который возвращает идентификатор добавленной записи.
События
Датаменеджер при модификации данных отправляет события. Названия событий строятся автоматически и имеют вид <КодСущности> On( Before|<пусто>| After)( Add| Update| Delete), например CultureOnBeforeDelete. Отменить операцию могут только обработчики событий OnBefore.
При добавлении записи порядок событий следующий (указаны параметры для обработчика):
OnBeforeAdd (array(«fields»=>$data))
checkFields()
OnAdd(array(«fields»=>$data))
add
OnAfterAdd(array(«id»=>$id, «fields»=>$data))
Если обработчик OnBeforeAdd вернет ошибку, то добавление не будет произведено. О реализации обработчиков см. ниже.
При обновлении записи порядок событий следующий:
OnBeforeUpdate(array(«id»=>$primary, «fields»=>$data))
checkFields()
OnUpdate(array(«id»=>$primary, «fields»=>$data))
update
OnAfterUpdate(array(«id»=>$primary, «fields»=>$data))
Если обработчик OnBeforeUpdate вернет ошибку, то обновление не будет произведено.
При удалении записи порядок событий следующий:
OnBeforeDelete(array(«id»=>$primary))
OnDelete(array(«id»=>$primary))
delete
OnAfterDelete(array(«id»=>$primary))
Если обработчик OnBeforeDelete вернет ошибку, то удаление не будет произведено.
Обработчики событий
Разберем код обработчика на основе примера:
use BitrixMain; use BitrixMainEntity; $eventManager = MainEventManager::getInstance(); $eventManager->addEventHandler("main", "CultureOnBeforeUpdate", "CultureBeforeUpdate"); function CultureBeforeUpdate(EntityEvent $event) { $errors = array(); $primary = $event->getParameter("id"); if($primary["ID"] == 1) { $errors[] = new EntityEntityError("Нельзя изменить запись номер 1."); } $fields = $event->getParameter("fields"); if($fields["CHARSET"] == "UTF-8") { $entity = $event->getEntity(); $errors[] = new EntityFieldError($entity->getField("CHARSET"), "На этом проекте недопустима кодировка UTF-8."); } $changedFields = array(); //slightly change encoding if($fields["CHARSET"] == "Windows-1252") { $changedFields["CHARSET"] = "Windows-1251"; } //impossible to change day of week $unsetFields = array("WEEK_START"); $result = new EntityEventResult(); if(!empty($errors)) { $result->setErrors($errors); } else { $result->modifyFields($changedFields); $result->unsetFields($unsetFields); } return $result; }
Функция обработчика получает на вход экземпляр объекта события EntityDataManagerEvent $event. Класс DataManagerEvent является наследником BitrixMainEvent.
Событие содержит в себе входящие параметры, переданные при отправке события. Получить значение параметра мы можем так:
$primary = $event->getParameter("id");
Либо сразу получить все параметры в виде ассоциативного массива:
$params = $event->getParameters();
Обработчик может вернуть результат в виде объекта типа EntityEventResult (наследник MainEventResult). Результат проверяется только для событий OnBefore, для остальных событий он игнорируется.
Результат по умолчанию создается как успешный. Если нужно вернуть ошибки (и прервать тем самым обновление данных), то используется метод, в который передается массив ошибок:
$result->setErrors($errors);
Массив может содержать объекты типа EntityEntityError (для всей сущности) или EntityFieldError (для поля сущности).
UPD. Теперь обработчик OnBefore может вернуть массив измененных полей (14.0.4). Этот массив мержится с исходными данными.
UPD2. Объект результата события EntityEventResult позволяет указать поля, которые будут изменены или удалены из исходных данных:
$result->modifyFields($changedFields); $result->unsetFields($unsetFields);
UPD3. Начиная с ядра 14.0.6 объекты результата UpdateResult и AddResult содержат сохраненные данные, доступные через $result->getData().
Работа с Highload блоками Битрикс через API D7
Order site
By contacting me you will receive a reliable and knowledgeable contractor who will quickly and efficiently implement any task for you and your business.
All English-language materials have been translated fully automatically using the Google service
Module connection
// Old way
CModule :: IncludeModule ("highloadblock");
// D7
use Bitrix Main Loader;
Loader :: includeModule ("highloadblock");
Adding elements to highload block
use Bitrix Main Loader,
Bitrix Highloadblock as HL,
Bitrix Main Entity;
Loader :: includeModule ("highloadblock");
$ hlblock = HL HighloadBlockTable :: getById ($ ID) -> fetch (); // where ID is the id of the highloadblock block to which we will add elements
$ entity = HL HighloadBlockTable :: compileEntity ($ hlblock);
$ entity_data_class = $ entity-> getDataClass ();
// Array of added parameters
$ data = array (
"UF_FIELD_1" => 'Value 1',
"UF_FIELD_2" => 'Value 2',
"UF_FIELD_3" => 'Value 3'
);
$ result = $ entity_data_class :: add ($ data);
if ($ result-> isSuccess ()) {
echo 'added successfully';
} else {
echo 'Error:'. implode (',', $ otvet-> getErrors ()). "";
}
Getting elements from highload block
use Bitrix Main Loader,
Bitrix Highloadblock as HL,
Bitrix Main Entity
Loader :: includeModule ("highloadblock");
$ hlblock = HL HighloadBlockTable :: getById ($ ID) -> fetch (); // where ID is the id of the highloadblock block from which we will receive data
$ entity = HL HighloadBlockTable :: compileEntity ($ hlblock);
$ entity_data_class = $ entity-> getDataClass ();
$ data = $ entity_data_class :: getList (array (
"select" => array ("*"),
"order" => array ("ID" => "DESC"),
"filter" => array ("UF_FIELD_1" => "Value 1", "UF_FIELD_2" => 'Value 2', "UF_FIELD_3" => 'Value 3') // Filtering the selection
));
while ($ arData = $ data-> Fetch ()) {
// Get the value of the list type
$ rsType = CUserFieldEnum :: GetList (array (), array (
'USER_FIELD_NAME' => 'UF_TYPE',
// 'USER_FIELD_ID' => 'ID'
));
foreach ($ rsType-> arResult as $ arType) {
print_r (arType);
}
// Process the result
}
Updating elements in the highload block
use Bitrix Main Loader,
Bitrix Highloadblock as HL,
Bitrix Main Entity;
Loader :: includeModule ("highloadblock");
$ hlblock = HL HighloadBlockTable :: getById ($ ID) -> fetch (); // where ID is the id of the highloadblock block in which we will update the data
$ entity = HL HighloadBlockTable :: compileEntity ($ hlblock);
$ entity_data_class = $ entity-> getDataClass ();
// Array of fields to update
$ data = array (
"UF_FIELD_1" => 'Value 1',
"UF_FIELD_2" => 'Value 2',
"UF_FIELD_3" => 'Value 3'
);
$ result = $ entity_data_class :: update ($ ELEMENT_ID, $ data); // where $ ELEMENT_ID is the id of the record being updated
Removing elements from highload block
use Bitrix Main Loader,
Bitrix Highloadblock as HL,
Bitrix Main Entity;
Loader :: includeModule ("highloadblock");
$ hlblock = HL HighloadBlockTable :: getById ($ ID) -> fetch (); // where ID is the id of the highloadblock block from which we will delete data
$ entity = HL HighloadBlockTable :: compileEntity ($ hlblock);
$ entity_data_class = $ entity-> getDataClass ();
$ entity_data_class :: Delete ($ ELEMENT_ID); // where $ ELEMENT_ID is the id of the record to be deleted
Getting highload fields of a block
use Bitrix Highloadblock HighloadBlockTable as HL;
Loader :: includeModule ("highloadblock");
$ hlblock = HL :: getById ($ ID) -> fetch (); // where ID is the id of the highloadblock block
$ entity = HL :: compileEntity ($ hlblock);
$ fields = $ entity-> getFields ();
Getting the number of highload block elements
CModule :: IncludeModule ('highloadblock');
$ entity_data_class = GetEntityDataClass ($ ID); // where ID is the id of the highloadblock block
$ count = $ entity_data_class :: getCount ();
Highload block filtering by field Yes No
'filter' => array (
'UF_BOOLEAN_FIELD' => '1'
)
Highload block filtering with logic
$ arFilter = Array (
Array (
"LOGIC" => "AND", // Logical AND
Array (
"UF_TITLE" => 'a%' // Get records starting with a
),
Array (
'UF_BOOLEAN_FIELD' => '1' // Yes No field is set
)
)
);
Highload block complex filtering
We select user 20, as well as users with the value of the custom field UF_SHMS = 770
, which are in group 6
$ arFilter = Array (
Array (
"LOGIC" => "OR",
Array (
"ID" => 20
),
Array (
"UF_SHMS" => 770,
"Bitrix Main UserGroupTable: USER.GROUP_ID" => 6
)
)
);
Due to the fact that a user can belong to several groups. We get several identical records. This is solved by adding the parameter "data_doubling" => false
to getList
$ res = Bitrix Main UserTable :: getList (Array (
"select" => Array ("ID", "NAME"),
"filter" => $ arFilter,
"data_doubling" => false // Exclude duplicate records from the selection
));
Types of filtration from office. documentation
"!" - for strings, an expression that does not match the mask, or not equal (for other types of fields).
"?" - using logic, works only for string properties.
"<" - less;
"<=" - less or equal;
">" - more;
"> =" - more or equal.
"=" - equal;
"! =" - not equal.
"%" is a substring;
"!%" is not a substring.
"> <" - between;
"!> <" - not between.
Private filtering rules from office. documentation
$ arFilter = array ("PROPERTY_CML2_SCAN_CODE" => false) - used to select all elements with an empty property;
$ arFilter = array ("PROPERTY_CML2_SCAN_CODE" => "") - used to select all items;
$ arFilter = array ("PROPERTY_CML2_SCAN_CODE" => "qwe") - when filtering elements, the exact match of the property with the specified string is checked;
$ arFilter = array ("? PROPERTY_CML2_SCAN_CODE" => "qwe") - when filtering elements, the presence of the specified substring in the property is checked.
$ arFilter = array ("! PROPERTY_CML2_SCAN_CODE" => false) - used to select only elements with a filled property;
$ arFilter = array ("! PROPERTY_CML2_SCAN_CODE" => "qwe") - when filtering elements, the absence of an exact match with the given string is checked;
$ arFilter = array ("!? PROPERTY_CML2_SCAN_CODE" => "qwe") - when filtering elements, the absence of the specified substring in the property is checked.
Highload block events
OnBeforeAdd - The event is called before adding the highload (HL) element of the block and validating the fields. You can modify the data, add your own validation, return an error if the validation fails.
OnAdd - The event is called before adding to the database, after the fields are validated. Data modification is not available.
OnAfterAdd - The event is called after adding an HL block element. You can add additional logic that will run after adding a new element of this HL block.
OnBeforeUpdate - The event is called before the highload (HL) element of the block is updated and the fields are validated. You can modify the data, add your own validation, return an error if the validation fails.
OnUpdate - The event is called before updating to the database, after the fields are validated. Data modification is not available.
OnAfterUpdate - The event is called after updating the HL element of the block. You can add additional logic that will run after the element of this HL block is updated.
OnBeforeDelete - The event is called before the block's highload (HL) element is deleted. You can undo the deletion.
OnDelete - The event is called before deleting from the database. Cancellation is not available.
OnAfterDelete - The event is called after deleting an HL block element. You can add additional logic that will run after you remove an element of this HL block.
Catching the block highload event
$ eventManager = Bitrix Main EventManager :: getInstance ();
$ eventManager-> addEventHandler ('', 'WebinarOptionsOnAfterAdd', 'OnAfterAdd'); // where "WebinarOptions" is the name of the highload block
function OnAfterAdd ( Bitrix Main Entity Event $ event) {}
Block Highload Event — OnBeforeAdd
The event is called before the block highload (HL) element is added and the fields are validated.
You can modify data, add your own validation, return an error if the validation fails.
$ eventManager = Bitrix Main EventManager :: getInstance ();
$ eventManager-> addEventHandler ('', 'WebinarOptionsBeforeAdd', 'OnBeforeAdd');
function OnBeforeAdd ( Bitrix Main Entity Event $ event) {
$ entity = $ event-> getEntity ();
$ entityDataClass = $ entity-> GetDataClass ();
$ eventType = $ event-> getEventType (); // event type. will return WebinarOptionsBeforeAdd
$ arFields = $ event-> getParameter ("fields"); // get an array of fields for the highload block
$ arParameters = $ event-> getParameters (); // get all data available in this event
$ result = new Bitrix Main Entity EventResult ();
// data modification
if (empty ($ arFields ['UF_FULL_DESCRIPTION'])) {
$ arFields ['UF_FULL_DESCRIPTION'] = $ arFields ['UF_DESCRIPTION'];
$ result-> modifyFields ($ arFields);
}
// to test data modification comment out this block
if (empty ($ arFields ['UF_DESCRIPTION'])) {
$ arErrors = Array ();
$ arErrors [] = new Bitrix Main Entity FieldError ($ entity-> getField ("UF_DESCRIPTION"), "Error in the UF_DESCRIPTION field. The field must not be empty!");
$ result-> setErrors ($ arErrors);
}
return $ result;
}
Block Highload Event — OnAdd
The event is called before adding to the database, after the fields are validated.
Data modification is not available.
$ eventManager = Bitrix Main EventManager :: getInstance ();
$ eventManager-> addEventHandler ('', 'ColorsOnAdd', 'OnAdd');
function OnAdd ( Bitrix Main Entity Event $ event) {
$ entity = $ event-> getEntity ();
$ entityDataClass = $ entity-> GetDataClass ();
// event type. will return ColorsOnAdd
$ eventType = $ event-> getEventType ();
// get an array of fields for the highload block
$ arFields = $ event-> getParameter ("fields");
}
Block Highload Event — OnAfterAdd
The event is called after adding an HL block element. You can add additional logic that will run after adding a new element of this HL block.
$ eventManager = Bitrix Main EventManager :: getInstance ();
$ eventManager-> addEventHandler ('', 'ColorsOnAfterAdd', 'OnAfterAdd');
function OnAfterAdd ( Bitrix Main Entity Event $ event) {
// id of the added element
$ id = $ event-> getParameter ("id");
$ entity = $ event-> getEntity ();
$ entityDataClass = $ entity-> GetDataClass ();
// event type. will return ColorsOnAfterAdd
$ eventType = $ event-> getEventType ();
// get an array of fields for the highload block
$ arFields = $ event-> getParameter ("fields");
}
Block Highload Event — OnBeforeUpdate
The event is called before the highload (HL) element of the block is updated and the fields are validated.
You can modify data, add your own validation, return an error if the validation fails.
$ eventManager = Bitrix Main EventManager :: getInstance ();
$ eventManager-> addEventHandler ('', 'ColorsOnBeforeUpdate', 'OnBeforeUpdate');
function OnBeforeUpdate ( Bitrix Main Entity Event $ event) {
$ id = $ event-> getParameter ("id");
// id of the updated element
$ id = $ id ["ID"];
$ entity = $ event-> getEntity ();
$ entityDataClass = $ entity-> GetDataClass ();
// event type. will return ColorsOnBeforeUpdate
$ eventType = $ event-> getEventType ();
// get an array of fields for the highload block
$ arFields = $ event-> getParameter ("fields");
$ result = new Bitrix Main Entity EventResult ();
// data modification
if (empty ($ arFields ['UF_FULL_DESCRIPTION'])) {
$ arFields ['UF_FULL_DESCRIPTION'] = $ arFields ['UF_DESCRIPTION'];
$ result-> modifyFields ($ arFields);
}
// to test data modification comment out this block
if (empty ($ arFields ['UF_DESCRIPTION'])) {
$ arErrors = Array ();
$ arErrors [] = new Bitrix Main Entity FieldError ($ entity-> getField ("UF_DESCRIPTION"), "Error in the UF_DESCRIPTION field. The field must not be empty!");
$ result-> setErrors ($ arErrors);
}
return $ result;
}
Block Highload Event — OnUpdate
The event is called before updating to the database, after validating the fields.
Data modification is not available.
$ eventManager = Bitrix Main EventManager :: getInstance ();
$ eventManager-> addEventHandler ('', 'ColorsOnUpdate', 'OnUpdate');
function OnUpdate ( Bitrix Main Entity Event $ event) {
$ id = $ event-> getParameter ("id");
// id of the updated element
$ id = $ id ["ID"];
$ entity = $ event-> getEntity ();
$ entityDataClass = $ entity-> GetDataClass ();
// event type. will return ColorsOnUpdate
$ eventType = $ event-> getEventType ();
// get an array of fields for the highload block
$ arFields = $ event-> getParameter ("fields");
}
Block Highload Event — OnAfterUpdate
The event is called after updating the HL element of the block. You can add additional logic that will run after the element of this HL block is updated.
$ eventManager = Bitrix Main EventManager :: getInstance ();
$ eventManager-> addEventHandler ('', 'ColorsOnAfterUpdate', 'OnAfterUpdate');
function OnAfterUpdate ( Bitrix Main Entity Event $ event) {
$ id = $ event-> getParameter ("id");
// id of the updated element
$ id = $ id ["ID"];
$ entity = $ event-> getEntity ();
$ entityDataClass = $ entity-> GetDataClass ();
// event type. will return ColorsOnAfterUpdate
$ eventType = $ event-> getEventType ();
// get an array of fields for the highload block
$ arFields = $ event-> getParameter ("fields");
}
Block Highload Event — OnBeforeDelete
The event is called before the highload (HL) element of the block is removed.
You can undo the deletion.
$ eventManager = Bitrix Main EventManager :: getInstance ();
$ eventManager-> addEventHandler ('', 'ColorsOnBeforeDelete', 'OnBeforeDelete');
function OnBeforeDelete ( Bitrix Main Entity Event $ event) {
// fields in this event are not available, only id
$ id = $ event-> getParameter ("id");
// id of the element to remove
$ id = $ id ["ID"];
$ entity = $ event-> getEntity ();
$ entityDataClass = $ entity-> GetDataClass ();
// event type. will return ColorsOnBeforeDelete
$ eventType = $ event-> getEventType ();
$ result = new Bitrix Main Entity EventResult ();
if ($ id <= 20) {
$ arErrors = Array ();
$ arErrors [] = new Bitrix Main Entity EntityError ("Error! You cannot delete the first 20 elements!");
$ result-> setErrors ($ arErrors);
}
return $ result;
}
Block Highload Event — OnDelete
The event is called before deleting from the database. Cancellation is not available.
$ eventManager = Bitrix Main EventManager :: getInstance ();
$ eventManager-> addEventHandler ('', 'ColorsOnDelete', 'OnDelete');
function OnDelete ( Bitrix Main Entity Event $ event) {
// fields in this event are not available, only id
$ id = $ event-> getParameter ("id");
// id of the element to remove
$ id = $ id ["ID"];
$ entity = $ event-> getEntity ();
$ entityDataClass = $ entity-> GetDataClass ();
// event type. will return ColorsOnDelete
$ eventType = $ event-> getEventType ();
}
Block Highload Event — OnAfterDelete
The event is called after removing an HL block element. You can add additional logic that will run after an element of this HL block is deleted.
$ eventManager = Bitrix Main EventManager :: getInstance ();
$ eventManager-> addEventHandler ('', 'ColorsOnAfterDelete', 'OnAfterDelete');
function OnAfterDelete ( Bitrix Main Entity Event $ event) {
$ id = $ event-> getParameter ("id");
// id of the element to remove
$ id = $ id ["ID"];
$ entity = $ event-> getEntity ();
$ entityDataClass = $ entity-> GetDataClass ();
// event type. will return ColorsOnAfterAdd
$ eventType = $ event-> getEventType ();
if ($ id> 30 && $ id <1000) {
// your logic ....
}
}
Materials used:
- User selection: complex logic in the filter.
- Event handlers for highload blocks
Теги:
1C BitrixphpD7eventsinit.php
- Главная
- Сниппеты Битрикс d7
Сентябрь 17, 2018
Теги:
Хранение данных, События, Хайлоадблоки, ORM
Использование событий при работе с хайлоадблоками или ORM.
Данные примеры можно использовать при работе как с «чистым» ORM, так и с хайлоадблоками. При использовании для хайлоадблоков, в качестве модуля используется пустая строка — ».
Подключение обработчика событий для хайлоадблока с прямым указанием сущности «Myentity»:
namespace PartnerMyentity;
$eventManager = BitrixMainEventManager::getInstance();
$eventManager->addEventHandler('', 'MyentityOnAdd', 'PartnerMyentityOnAdd');
Подключение обработчика события хайлоадблока с получением названия сущности по идентификатору хайлоадблока (12):
namespace PartnerMyentity;
$hlblock = BitrixHighloadblockHighloadBlockTable::getById(12)->fetch();
$entity = BitrixHighloadblockHighloadBlockTable::compileEntity($hlblock);
$eventManager = BitrixMainEventManager::getInstance();
$eventManager->addEventHandler('', $entity->getName().'OnAdd', 'PartnerMyentityOnAdd');
Подключение обработчика события для сущности ORM «GrainForumForum» из модуля «grain.forum»:
namespace PartnerMyentity;
$eventManager = BitrixMainEventManager::getInstance();
$eventManager->addEventHandler('grain.forum', 'GrainForumForum::onAfterAdd', 'PartnerMyentityOnAfterAdd');
Получение различных данных из объекта «$event»:
$arFields = $event->getParameter("fields"); // получаем список полей
$event->setParameter("fields",$arFields); // обновляем список полей
$entity = $event->getEntity(); // получаем объект сущности
$eventType = $event->getEventType(); // получаем тип события (например, "MyentityOnUpdate")
$moduleId = $event->getModuleId(); // получаем код модуля
Использование событий «OnBeforeAdd», «OnBeforeUpdate»:
namespace PartnerMyentity;
$eventManager = BitrixMainEventManager::getInstance();
$eventManager->addEventHandler('', 'MyentityOnBeforeUpdate', 'PartnerMyentityOnBeforeAddUpdate');
$eventManager->addEventHandler('', 'MyentityOnBeforeAdd', 'PartnerMyentityOnBeforeAddUpdate');
function
OnBeforeAddUpdate(BitrixMainEntityEvent $event)
{
$id = $event->getParameter("id");
if(is_array($id))
$id = $id["ID"];
if(!
$id)
return;
$entity = $event->getEntity();
$entityDataClass = $entity->GetDataClass();
$eventType = $event->getEventType();
$arFields = $event->getParameter("fields");
$price = ...
$error = ...
$result = new BitrixMainEntityEventResult();
if(
$error)
{
$arErrors = Array();
$arErrors[] = new BitrixMainEntityFieldError($entity->getField("MYFIELD"), "Ошибка в поле MYFIELD");
// или $arErrors[] = new BitrixMainEntityEntityError("Общая ошибка");
$result->setErrors($arErrors);
}
else
{
$arFields["UF_AVG_PRICE"] = $price;
$event->setParameter("fields",$arFields);
$changedFields = Array();
$changedFields["UF_AVG_PRICE"] = $price;
$result->modifyFields($changedFields);
//$result->unsetFields($arUnsetFields);
}
return
$result;
}
События «OnAdd», «OnUpdate» вызываются после проверки полей на правильность перед добавлением или обновлением элемента:
namespace PartnerMyentity;
$eventManager = BitrixMainEventManager::getInstance();
$eventManager->addEventHandler('', 'MyentityOnUpdate', 'PartnerMyentityOnAddUpdate');
$eventManager->addEventHandler('', 'MyentityOnAdd', 'PartnerMyentityOnAddUpdate');
function
OnAddUpdate(BitrixMainEntityEvent $event)
{
$id = $event->getParameter("id");
if(is_array($id))
$id = $id["ID"];
$entity = $event->getEntity();
$entityDataClass = $entity->GetDataClass();
$eventType = $event->getEventType();
$arParameters = $event->getParameters();
//$event->setParameters($arParameters);
//$arFields = $event->getParameter("fields");
//$event->setParameter("fields",$arFields);
}
События «OnAfterAdd», «OnAfterUpdate» вызываются после добавления/изменения записи (статическая переменная $bHandlerStop используется для предотвращения рекурсии при вызове Update внутри обработчика, т.к. событие при этом также будет вызываться):
namespace PartnerMyentity;
$eventManager = BitrixMainEventManager::getInstance();
$eventManager->addEventHandler('', 'MyentityOnAfterUpdate', 'PartnerMyentityOnAfterAddUpdate');
$eventManager->addEventHandler('', 'MyentityOnAfterAdd', 'PartnerMyentityOnAfterAddUpdate');
function
OnAfterAddUpdate(BitrixMainEntityEvent $event)
{
static $bHandlerStop;
if($bHandlerStop===true)
return;
$id = $event->getParameter("id");
if(is_array($id))
$id = $id["ID"];
if(!$id)
return;
$entity = $event->getEntity();
$entityDataClass = $entity->GetDataClass();
$eventType = $event->getEventType();
$arParameters = $event->getParameters();
//$event->setParameters($arParameters);
//$arFields = $event->getParameter("fields");
//$event->setParameter("fields",$arFields);
$bHandlerStop = true;
$result = $entityDataClass::update($id, Array("UF_AVG_PRICE"=>2.0));
$bHandlerStop = false;
}
Пример передачи данных между обработчиками «OnBeforeUpdate» и «OnAfterUpdate» через статическую переменную класса:
namespace Partner;
$eventManager = BitrixMainEventManager::getInstance();
$eventManager->addEventHandler('', 'MyentityOnBeforeUpdate', 'PartnerMyClass::onBeforeUpdate');
$eventManager->addEventHandler('', 'MyentityOnAfterUpdate', 'PartnerMyClass::onAfterUpdate');
class
MyClass
{
const props = array(
'UF_KINDS_OF_WORK',
'UF_CONSTRUCTION_TYPE',
'UF_REGIONS',
);
private static
$temp = null;
function
onBeforeUpdate(BitrixMainEntityEvent $event)
{
$id = $event->getParameter("id");
if(is_array($id))
$id = $id["ID"];
$entity = $event->getEntity();
self::$temp = array(
'id' => $id,
'enumId' => self::getAllEnumId($entity,$id),
);
}
function
onAfterUpdate(BitrixMainEntityEvent $event)
{
$id = $event->getParameter("id");
if(is_array($id))
$id = $id["ID"];
$entity = $event->getEntity();
if(!
is_array(self::$temp) || self::$temp['id']!=$id)
return;
$oldEnumId = self::$temp['enumId'];
$newEnumId = self::getAllEnumId($entity,$id);
// ... делаем что-то имея данные, которые были до, и стали после
}
function
getAllEnumId($entity,$id)
{
$entityDataClass = $entity->GetDataClass();
$result = $entityDataClass::getList(array(
"select" => self::props,
"filter" => Array("=ID"=>intval($id)),
"limit" => 1,
));
$allEnumId = array();
if($arRow = $result->Fetch())
{
foreach(self::props as $propCode)
if(is_array($arRow[$propCode]))
foreach($arRow[$propCode] as $enumId)
$allEnumId[] = $enumId;
}
return
$allEnumId;
}
}
Событие «OnBeforeDelete», возникающее перед удалением элемента (может быть использовано для отмены удаления):
namespace PartnerMyentity;
$eventManager = BitrixMainEventManager::getInstance();
$eventManager->addEventHandler('', 'MyentityOnBeforeDelete', 'PartnerMyentityOnBeforeDelete');
function
OnBeforeDelete(BitrixMainEntityEvent $event)
{
// All $event functions /bitrix/modules/main/lib/event.php
$id = $event->getParameter("id");
if(is_array($id))
$id = $id["ID"];
$entity = $event->getEntity();
$entityDataClass = $entity->GetDataClass();
$result = new BitrixMainEntityEventResult();
if(
$ID==15) {
$arErrors = Array();
$arErrors[] = new BitrixMainEntityEntityError("Нельзя удалить запись с ID=".$id);
$result->setErrors($arErrors);
}
return
$result;
}
Событие «OnDelete», возникающее перед удалением элемента (уже не может быть использовано для отмены удаления):
namespace PartnerMyentity;
$eventManager = BitrixMainEventManager::getInstance();
$eventManager->addEventHandler('', 'MyentityOnDelete', 'PartnerMyentityOnDelete');
function
OnDelete(BitrixMainEntityEvent $event)
{
// All $event functions /bitrix/modules/main/lib/event.php
$id = $event->getParameter("id");
if(is_array($id))
$id = $id["ID"];
$entity = $event->getEntity();
$entityDataClass = $entity->GetDataClass();
define("ERROR_EMAIL", "my@email.ru");
SendError("Запись с ID=$id будет удаленаnn");
}
Событие «OnAfterDelete», возникающее после удаления элемента:
namespace PartnerMyentity;
$eventManager = BitrixMainEventManager::getInstance();
$eventManager->addEventHandler('', 'MyentityOnAfterDelete', 'PartnerMyentityOnAfterDelete');
function
OnAfterDelete(BitrixMainEntityEvent $event)
{
// All $event functions /bitrix/modules/main/lib/event.php
$id = $event->getParameter("id");
if(is_array($id))
$id = $id["ID"];
$entity = $event->getEntity();
$entityDataClass = $entity->GetDataClass();
define("ERROR_EMAIL", "my@email.ru");
SendError("Запись с ID=$id была удаленаnn");
}
← Переход к списку
There are three methods available for writing inside the described class: BookTable::add, BookTable::update, BookTable:delete.
BookTable::add
The add-entry method admits as an input parameter an array with values containing entity field names as the keys:
namespace SomePartnerMyBooksCatalog; use BitrixMainType; $result = BookTable::add(array( 'ISBN' => '978-0321127426', 'TITLE' => 'Patterns of Enterprise Application Architecture', 'PUBLISH_DATE' => new TypeDate('2002-11-16', 'Y-m-d') )); if ($result->isSuccess()) { $id = $result->getId(); }
The method returns the result object EntityAddResult, and the example above shows how to check the successful adding of an entry and obtain the ID of the added entry.
Note. The objects of the class BitrixMainTypeDate and BitrixMainTypeDateTime must be used as values of the fields DateField and DateTimeField and also for user-defined fields Date and Date with Time. By default, the designer receives a line date in the website format but the format of the date to be submitted can also be indicated explicitly.
BookTable::update
Entry update follows a similar procedure; only the value of the primary key is added to the array of values in the parameters:
$result = BookTable::update($id, array( 'PUBLISH_DATE' => new TypeDate('2002-11-15', 'Y-m-d') ));
In the example, the date indicated in the new entry is corrected. As a result, the object EntityUpdateResult is returned, and it also has a test method isSuccess() (to make sure that there was no errors in the query) and, additionally, it is possible to learn whether the entry was actually updated: getAffectedRowsCount().
BookTable::delete
Only the primary key is needed to delete the record:
$result = BookTable::delete($id);
Operation Results
If one or more errors occur during the operation, their text can be obtained from the result:
$result = BookTable::update(...); if (!$result->isSuccess()) { $errors = $result->getErrorMessages(); }
Default Values
Sometimes the majority of new entries always contain the same value for a certain field or it is calculated automatically. Let us assume that the book catalog has today’s date as the issue/publishing date by default (it is logical to add a book to the catalog on the day of its issue). Let us return to the description of the field in the entity and use the parameter default_value:
new EntityDateField('PUBLISH_DATE', array( 'default_value' => new TypeDate ))
Now, when adding an entry with no expressly indicated issue date, its value will be the current day:
$result = BookTable::add(array( 'ISBN' => '978-0321127426', 'TITLE' => 'Some new book' ));
Let us consider a more complicated task: there is no possibility to promptly add books on the day of their issue but it is known that, as a rule, new books are issued on Fridays. Accordingly, they will be added only in the course of the following week:
new EntityDateField('PUBLISH_DATE', array( 'default_value' => function () { // figure out last friday date $lastFriday = date('Y-m-d', strtotime('last friday')); return new TypeDate($lastFriday, 'Y-m-d'); } ))
Any callable value can be the value of the parameter default_value: a function name, an array from class/object and a name of the method, or an anonymous function.
Validators
Before writing new data to the database their correctness must be checked without fail. This can be done with the help of validators:
new EntityStringField('ISBN', array( 'required' => true, 'column_name' => 'ISBNCODE', 'validation' => function() { return array( new EntityValidatorRegExp('/[d-]{13,}/') ); } ))
Now each time you add or edit an entry, the ISBN will be checked using the template [d-]{13,}
– the code must contain only numbers and a hyphen, with a minimum of 13 digits.
Validation is set using the parameter validation in the field designer and is a callback that returns an array of validators.
Note: Why validation – callback, and not just an array of validators? It is a kind of deferred load: validators will be instantiated only when data validation is really needed. Generally no validation is needed for data sampling from the database.
An inheritor EntityValidatorBase
or any callable that is to return a true or a text of an error, or an object of EntityFieldError
(if you want to use own code of the error) is accepted as a validator.
It is known for sure that the ISBN code must contain 13 digits, and these digits can be separated by several hyphens:
978-0321127426
978-1-449-31428-6
9780201485677
To make sure that there are exactly 13 digits, let us write our own validator:
new EntityStringField('ISBN', array( 'required' => true, 'column_name' => 'ISBNCODE', 'validation' => function() { return array( function ($value) { $clean = str_replace('-', '', $value); if (preg_match('/^d{13}$/', $clean)) { return true; } else { return ‘The ISBN code must contain 13 digits.’; } } ); } ))
The value of this field is submitted to the validator as the first parameter, but more optional information is also available:
new EntityStringField('ISBN', array( 'required' => true, 'column_name' => 'ISBNCODE', 'validation' => function() { return array( function ($value, $primary, $row, $field) { // value – field value // primary – an array with the primary key, in this case [ID => 1] // row – all arrays of data submitted to ::add or ::update // field – an object of the field under validation – EntityStringField('ISBN', ...) } ); } ))
This set of data allows for a much wider range of complex checks.
If several validators are attached to a field and there is a need to find out on the program level which of them exactly has worked, the error code can be used. For example, the last digit of the ISBN code is a control digit that serves to check the correctness of the numerical part of ISBN. You have to add a validator for checking it and to process its result in a specific way:
// describing validator in the entity field new EntityStringField('ISBN', array( 'required' => true, 'column_name' => 'ISBNCODE', 'validation' => function() { return array( function ($value) { $clean = str_replace('-', '', $value); if (preg_match('/^d{13}$/', $clean)) { return true; } else { return ‘ISBN code must contain 13 digits.’; } }, function ($value, $primary, $row, $field) { // checking the last digit // ... // if the number is wrong, a special error is returned return new EntityFieldError( // if the number is wrong, a special error is returned ); } ); } ))
// performing the operation $result = BookTable::update(...); if (!$result->isSuccess()) { // checking which errors have been revealed $errors = $result->getErrors(); foreach ($errors as $error) { if ($error->getCode() == 'MY_ISBN_CHECKSUM') { // our validator has worked } } }
2 standard error codes are available by default: BX_INVALID_VALUE if the validator has worked, and BX_EMPTY_REQUIRED if no required field is indicated when adding an entry.
Validators work both when adding new entries and when updating the existing entries. This behavior is based on the general purpose of validators consisting in guaranteeing correct and integral data in the database. The event mechanism is available in order to check data only upon their addition or updating and also for other manipulations.
We recommend that you use standard validators in standard situations:
EntityValidatorRegExp
– check by regular expression,EntityValidatorLength
– check the minimum/maximum line length,EntityValidatorRange
– check the minimum/maximum number value,EntityValidatorUnique
– check the uniqueness of a value.
The validators described above cannot apply to the User-defined fields. Their values shall be configured in field settings through the administrative interface.
Events
In the example with validators, one of the checks for the ISBN field consisted in checking the availability of 13 digits. In addition to numbers, ISBN code may include hyphens, but technically speaking they have no value. In order to store only “clean” data in the database (13 digits only, without hyphens), we can use an internal event handler:
class BookTable extends EntityDataManager { ... public static function onBeforeAdd(EntityEvent $event) { $result = new EntityEventResult; $data = $event->getParameter("fields"); if (isset($data['ISBN'])) { $cleanIsbn = str_replace('-', '', $data['ISBN']); $result->modifyFields(array('ISBN' => $cleanIsbn)); } return $result; } }
The method onBeforeAdd set up in the entity is automatically recognized by the system as a handler for the event “before addition” thus allowing change of data or additional checks to be done in it. In the example, we have changed the ISBN code using the method modifyFields.
// before transformation 978-0321127426 978-1-449-31428-6 9780201485677 // after transformation 9780321127426 9781449314286 9780201485677
After such a transformation, we can return again to the neat validator RegExp instead of using an anonymous function (because we already know that the value will contain no acceptable hyphens and only numbers must remain):
'validation' => function() { return array( //function ($value) { // $clean = str_replace('-', '', $value); // // if (preg_match('/^d{13}$/', $clean)) // { // return true; // } // else // { // return 'The ISBN code must contain 13 digits.'; // } //}, new EntityValidatorRegExp('/d{13}/'), ... ); }
In addition to data change, the event handler makes it possible to delete data or even abort the operation. For example, let us assume that the updating of the ISBN code for the books that already exist in the catalog must be prohibited. It can be done in the event handler onBeforeUpdate using one of two ways:
public static function onBeforeUpdate(EntityEvent $event) { $result = new EntityEventResult; $data = $event->getParameter("fields"); if (isset($data['ISBN'])) { $result->unsetFields(array('ISBN')); } return $result; }
In this option, the ISBN will be deleted “with no fuss” as if it were not submitted. The second option consists in prohibiting its update and generating an error:
public static function onBeforeUpdate(EntityEvent $event) { $result = new EntityEventResult; $data = $event->getParameter("fields"); if (isset($data['ISBN'])) { $result->addError(new EntityFieldError( $event->getEntity()->getField('ISBN'), 'Changing the ISBN code for the existing books is prohibited' )); } return $result; }
If an error is returned, we have formed the object EntityFieldError
in order for us to learn during subsequent error processing in which field, exactly, the check was activated. If the error applies to more than one field or to the entire entry, the use of the object EntityEntityError
will be more appropriate:
public static function onBeforeUpdate(EntityEvent $event) { $result = new EntityEventResult; $data = $event->getParameter("fields"); if (...) // comprehensive data check { $result->addError(new EntityEntityError( 'Impossible to update an entry' )); } return $result; }
Two events were used in the examples: onBeforeAdd and onBeforeUpdate, there are nine such events in total:
- onBeforeAdd (parameters: fields)
- onAdd (parameters: fields)
- onAfterAdd (parameters: fields, primary)
- onBeforeUpdate (parameters: primary, fields)
- onUpdate (parameters: primary, fields)
- onAfterUpdate (parameters: primary, fields)
- onBeforeDelete (parameters: primary)
- onDelete (parameters: primary)
- onAfterDelete (parameters: primary)
The following diagram shows the sequence in which the event handlers are called, and the actions a handler may carry out.
It goes without saying that these events can be handled in the entity itself as well as in the methods with the same name. In order to subscribe to an event in an arbitrary point of script execution, call for the event manager:
$eventManager = MainEventManager::getInstance(); $eventManager->addEventHandler( "main", "SomePartnerMyBooksCatalogBook::OnBeforeUpdate", );
Value formatting
Sometimes it may become necessary to store data in one format and work with them in the program in another. The most common example: work with an array and its serialization before saving into the database. For this, the field parameters save_data_modification and fetch_data_modification are available. They are set up similarly to the validators through callback.
Let us use the example of a book catalog in order to describe the text field EDITIONS_ISBN: it will store the ISBN codes of other editions of the book, if any:
new EntityTextField('EDITIONS_ISBN', array( 'save_data_modification' => function () { return array( function ($value) { return serialize($value); } ); }, 'fetch_data_modification' => function () { return array( function ($value) { return unserialize($value); } ); } ))
We have indicated the serialization of the value before saving into the database in the parameter save_data_modification, and we have set up de-serialization during sampling from the database in the parameter fetch_data_modification. Now, when writing business logic you can simply work with the array without having to look into conversion issues.
Attention! Before creating a serialized field, make sure the serialization will not interfere during filtering or linking tables. Search by a single value in WHERE among serialized lines is highly inefficient. You may want to opt for a normalized data storage scheme.
Since serialization is the most typical example for conversion of values it is singled out into a separate parameter serialized:
new EntityTextField('EDITIONS_ISBN', array( 'serialized' => true ))
However, you can still describe your callables for other data modification options.
Value calculating
More often than not, developers have to implement counters where a new value is to be calculated on the database side for the sake of data integrity instead of selecting the old value and recalculating it on the application side. In other words, the queries of the following type must be executed:
UPDATE my_book SET READERS_COUNT = READERS_COUNT + 1 WHERE ID = 1
If the numeric field, READERS_COUNT is described in the entity, the counter increment can be launched as follows:
BookTable::update($id, array( 'READERS_COUNT' => new DBSqlExpression('?# + 1', 'READERS_COUNT') ));
The placeholder ?# means that the following argument in the designer is the database ID – the name of the database, table, or column, and this value will be masked appropriately. For all variable parameters, the use of placeholders is highly recommended. This approach will help to avoid problems with SQL injections.
For example, if an increment number of readers is variable, it would be better to describe the expression as follows:
// correct BookTable::update($id, array( 'READERS_COUNT' => new DBSqlExpression('?# + ?i', 'READERS_COUNT', $readersCount) )); // incorrect BookTable::update($id, array( 'READERS_COUNT' => new DBSqlExpression('?# + '.$readersCount, 'READERS_COUNT') ));
The list of placeholders currently available:
- ? or ?s – the value is masked in put between single quotes ‘
- ?# – the value is masked as an identifier
- ?i – the value is reduced to integer
- ?f – the value is reduced to float
Error warnings
The examples above have a peculiarity that the data update query is called without checking the result.
// call without checking successful query execution BookTable::update(...); // with check $result = BookTable::update(...); if (!$result->isSuccess()) { // error processing }
The second option is undoubtedly preferable from the point of view of control. However, if the code is executed only in the agent mode, we have no use for the list of errors occurred during validation. In this case, if the query has not gone through due to a “failed” validation and isSuccess() check was not called, the system will generate E_USER_WARNING with a list of errors which may be seen in the website log (provided that .settings.php is set up properly).
In view of the results of this chapter, some changes have occurred in the entity description. It looks as follows now:
namespace SomePartnerMyBooksCatalog; use BitrixMainEntity; use BitrixMainType; class BookTable extends EntityDataManager { public static function getTableName() { return 'my_book'; } public static function getUfId() { return 'MY_BOOK'; } public static function getMap() { return array( new EntityIntegerField('ID', array( 'primary' => true, 'autocomplete' => true )), new EntityStringField('ISBN', array( 'required' => true, 'column_name' => 'ISBNCODE', 'validation' => function() { return array( new EntityValidatorRegExp('/d{13}/'), function ($value, $primary, $row, $field) { // check the last digit // ... // if the digit is incorrect we will return a special error return new EntityFieldError( $field, 'ISBN control digit does not match', 'MY_ISBN_CHECKSUM' ); } ); } )), new EntityStringField('TITLE'), new EntityDateField('PUBLISH_DATE', array( 'default_value' => function () { // figure out last friday date $lastFriday = date('Y-m-d', strtotime('last friday')); return new TypeDate($lastFriday, 'Y-m-d'); } )), new EntityTextField('EDITIONS_ISBN', array( 'serialized' => true )), new EntityIntegerField('READERS_COUNT') ); } public static function onBeforeAdd(EntityEvent $event) { $result = new EntityEventResult; $data = $event->getParameter("fields"); if (isset($data['ISBN'])) { $cleanIsbn = str_replace('-', '', $data['ISBN']); $result->modifyFields(array('ISBN' => $cleanIsbn)); } return $result; } }
Copy this code and play with all the options described above.
Ошибка
Entity "BasketItem" not found (510) /document_root/bitrix/modules/sale/lib/shipmentitemcollection.php:333 #0: BitrixSaleShipmentItemCollection->save() /document_root/bitrix/modules/sale/lib/shipment.php:682 #1: BitrixSaleShipment->save() /document_root/bitrix/modules/sale/lib/shipmentcollection.php:366 #2: BitrixSaleShipmentCollection->save() /document_root/bitrix/modules/sale/lib/order.php:931 #3: BitrixSaleOrder->save() /document_root/bitrix/modules/sale/lib/compatible/ordercompatibility.php:1618 #4: BitrixSaleCompatibleOrderCompatibility::modifyOrder(string, array) /document_root/bitrix/modules/sale/lib/compatible/ordercompatibility.php:1406 #5: BitrixSaleCompatibleOrderCompatibility::add(array) /document_root/bitrix/modules/sale/mysql/order.php:60 #6: CSaleOrder::Add(array) /document_root/export/orders/import.php:238
Решение
Через API bitrix и админку битрикс побороть данную ошибку не смог!
Ошибку поправил в базе данных битрикс, точнее в таблице — b_sale_order_dlv_basket
В этой таблице идет связь между созданной отгрузкой заказа (ORDER_DILIVERY_ID) и товара в корзине (BASKET_ID)
В моем случае потерялась эта связь. Почему? Это останется загадкой
Создал дополнительную запись — строчку в таблице.
И все заработало.
Концепция, описание сущности
При создании интернет-проектов на платформе Bitrix Framework доступен обширный функционал «из коробки», использовать который можно посредством вызовов API соответствующих модулей. При этом каждый модуль в концепции фреймворка является самостоятельной рабочей единицей, которая обеспечивает решение определенного круга задач.
Как правило, API каждого модуля разрабатывается исходя из специфики задач, и нередко формат вызовов отличается от модуля к модулю. Чтобы свести эти различия к минимуму базовый функционал, который присутствует практически в каждом модуле, стандартизирован. Это CRUD-операции: Create, Read, Update, Delete (создание, чтение, обновление и удаление данных).
Концепция сущностей
Сущность — совокупность коллекции объектов с присущей им базовой (низкоуровневой) бизнес-логикой. Сущность обладает набором характеристик, значения которых подчиняются определенным правилам обработки.
Например, сущность Пользователь — это множество пользователей с набором полей:
- ID
- Имя
- Фамилия
- Пароль
- Логин
- и т.д.
При этом ID автоматически выдается базой данных, Имя и Фамилия ограничены длиной 50 символов, Логин должен состоять только из латинских букв, цифр и знака подчеркивания и так далее.
Вместо программирования каждой такой сущности, мы бы хотели описывать ее в определенном формате. Такое описание обрабатывалось бы ядром системы, являлось для него своего рода конфигурацией:
Book ID int [autoincrement, primary] ISBN str [match: /[0-9X-]+/] TITLE str [max_length: 50] PUBLISH_DATE date
Например, похожим образом можно описать каталог книг, в котором система сама будет следить за корректностью и целостностью данных: проверять формат и вхождение в диапазон допустимых значений.
Типизация полей
Для конфигурации сущностей не используются средства разметки (xml, yml и т.п.), вместо этого используется php. Такой вариант дает максимум возможностей развития и гибкости.
Так выглядит определение типов данных из приведенного выше примера:
namespace SomePartnerMyBooksCatalog; use BitrixMainEntity; class BookTable extends EntityDataManager { public static function getTableName() { return 'my_book'; } public static function getMap() { return array( new EntityIntegerField('ID'), new EntityStringField('ISBN'), new EntityStringField('TITLE'), new EntityDateField('PUBLISH_DATE') ); } }
Внимание! Несмотря на то, что в примере под сущностью подразумевается Книга (Book), к имени класса дописан постфикс: BookTable. Это сделано специально — имя описательного класса сущности всегда должно завершаться словом Table. Основное имя Book в этом же пространстве имен считается зарезервированным, в будущем предполагается использовать основное имя (в данном случае — класс Book) для представления элементов сущности в виде объектов (в настоящий момент данные сущности представлены массивами, как и в старых методах getList).
За описание структуры сущности отвечает метод getMap(), который возвращает массив экземплярами полей.
Каждый тип поля представлен в виде класса-наследника EntityScalarField — эти поля работают с простыми скалярными значениями, которые сохраняются в базу данных «как есть». По умолчанию доступно 8 таких типов:
- Целое число
- Число
- Строка
- Текст
- Дата
- Дата/Время
- Да/Нет
- Значение из списка
В рамках соблюдения стандартов кодирования рекомендуется называть поля в верхнем регистре. Имена должны быть уникальными в рамках одной сущности.
Как правило, в конструкторе поля первым параметром передается имя поля, а вторым параметром — дополнительные настройки. Общие настройки будут рассмотрены далее в этой главе, но есть и специфическая настройка для BooleanField и EnumField:
new EntityBooleanField('NAME', array( 'values' => array('N', 'Y') )) new EntityEnumField('NAME', array( 'values' => array('VALUE1', 'VALUE2', 'VALUE3') ))
Для BooleanField, поскольку true и false не могут храниться в таком виде в БД, задается маппинг значений в виде массива, где первый элемент заменяет при хранении false, а второй true.
Примечание: при описании сущности можно задать имя таблицы в методе getTableName, в данном примере это `my_book`. Если не определить этот метод, то имя таблицы будет сформировано автоматически из неймспейса и названия класса, для данной сущности это будет `b_somepartner_mybookscatalog_book`.
Primary & autoincrement & required
В большинстве случаев у сущности есть первичный ключ по одному полю. Он же, как правило, является автоинкрементным. Чтобы рассказать об этом сущности, необходимо воспользоваться параметрами в конструкторе поля:
new EntityIntegerField('ID', array( 'primary' => true ))
Примечание: составной первичный ключ тоже возможен. Например, в отношениях двух сущностей составным ключом будут ID обеих сущностей. Подробнее узнать об этом и посмотреть пример можно в разделе N:M relations.
Так заявляется о принадлежности поля к первичному ключу. Благодаря этой опции, сущность будет контролировать вставку данных и не даст добавить запись без указания значения для первичного ключа. При обновлении и удалении записей их можно будет идентифицировать только по первичному ключу.
Часто не указывается явно значение ID, а оно получается из базы данных уже после успешного добавления записи. В таком случае нужно сообщить об этом сущности:
new EntityIntegerField('ID', array( 'primary' => true, 'autocomplete' => true ))
Флаг ‘autocomplete’, для сущности означает, что при добавлении новой записи не нужно требовать от разработчика установки значения для данного поля. По умолчанию, такое требование применяется только к полям из первичного ключа, но можно попросить систему требовать установку и любого другого поля:
new EntityStringField('ISBN', array( 'required' => true ))
Теперь нельзя будет добавить новую книгу, не указав ее ISBN код.
Маппинг имени колонки
При описании сущности для уже имеющейся таблицы может возникнуть желание по-другому назвать колонку. Например, изначально в таблице `my_book` поле ISBN называлось как ISBNCODE, и старый код использует это название колонки в SQL запросах. Если в новом API необходимо оптимизировать название до более читаемого ISBN, то в этом поможет параметр ‘column_name’:
new EntityStringField('ISBN', array( 'required' => true, 'column_name' => 'ISBNCODE' ))
Бывают и другие случаи, когда в одной физической колонке в таблице хранятся разные по смыслу значения. В таком случае можно создать несколько полей сущности, у которых будет одинаковый ‘column_name’.
Выражения ExpressionField
Предусмотрено не только хранение данных как есть, но и их преобразование при выборке. Допустим, возникла потребность наравне с датой издания сразу же получать возраст книги в днях. Хранить это число в БД накладно: придется каждый день пересчитывать обновлять данные. Можно просто считать возраст на стороне базы данных:
SELECT DATEDIFF(NOW(), PUBLISH_DATE) AS AGE_DAYS FROM my_book
Для этого нужно описать в сущности виртуальное поле, значение которого базируется на SQL-выражении с другим полем или полями:
new EntityExpressionField('AGE_DAYS', 'DATEDIFF(NOW(), %s)', array('PUBLISH_DATE') )
Первым параметром, как и у остальных полей, задается имя. Вторым параметром нужно передать текст SQL выражения, но при этом другие поля сущности нужно заменить на плейсхолдеры согласно формату sprintf. Третьим параметром нужно передать массив с именами полей сущности в определенном порядке, который был задан в выражении.
Примечание: в качестве плейсхолдеров рекомендуется использовать `%s` или `%1$s`, `%2$s` и так далее. Например, когда в выражении EXPR участвует несколько полей (FIELD_X + FIELD_Y) * FIELD_X
, то выражение можно описать так: '(%s + %s) * %s', [FIELD_X, FIELD_Y, FIELD_X];
или так: '(%1$s + %2$s) * %1$s', [FIELD_X, FIELD_Y]
.
Очень часто выражения могут применяться для агрегации данных (например, COUNT(*) или SUM(FIELD)), такие примеры будут рассмотрены в главе Выборка данных.
Примечание: expression поля можно использовать только при выборке данных: выбирать, фильтровать, группировать и сортировать по ним. Поскольку физически таких колонок в таблице БД нет, то записать значение поля некуда: система сгенерирует исключение.
Пользовательские поля
Помимо полей ScalarField и ExpressionField, сущность может содержать Пользовательские поля. Они конфигурируются через Административный интерфейс и не требуют дополнительного описания на стороне сущности. Все, что требуется указать в сущности, это выбранный Объект пользовательского поля:
class BookTable extends EntityDataManager { ... public static function getUfId() { return 'MY_BOOK'; } ... }
В дальнейшем именно этот идентификатор нужно указывать при прикреплении пользовательских полей к сущности:
Таким образом, можно выбирать и обновлять значения пользовательских полей наравне со значениями штатных полей сущности.
Пример
По результатам данной главы получена следующая сущность:
namespace SomePartnerMyBooksCatalog; use BitrixMainEntity; class BookTable extends EntityDataManager { public static function getTableName() { return 'my_book'; } public static function getUfId() { return 'MY_BOOK'; } public static function getMap() { return array( new EntityIntegerField('ID', array( 'primary' => true, 'autocomplete' => true )), new EntityStringField('ISBN', array( 'required' => true, 'column_name' => 'ISBNCODE' )), new EntityStringField('TITLE'), new EntityDateField('PUBLISH_DATE') ); } } // код для создания таблицы в MySQL // (получен путем вызова BookTable::getEntity()->compileDbTableStructureDump()) CREATE TABLE `my_book` ( `ID` int NOT NULL AUTO_INCREMENT, `ISBNCODE` varchar(255) NOT NULL, `TITLE` varchar(255) NOT NULL, `PUBLISH_DATE` date NOT NULL, PRIMARY KEY(`ID`) );
Таким образом можно описать в сущности обычные скалярные поля, выделить из них первичный ключ, указать автоинкрементные поля и какие поля должны быть обязательно заполнены. При расхождении имени колонки в таблице и желаемого имени в сущности будет возможность уладить этот момент.
Внимание! Метод getMap используется только как получение первичной конфигурации сущности. Если вы хотите получить действительный список полей сущности, воспользуйтесь методом BookTable::getEntity()->getFields().
Осталось только зафиксировать код сущности в проекте. Согласно общим правилам именования файлов в D7, код сущности нужно сохранить в файле: local/modules/somepartner.mybookscatalog/lib/book.php
После чего система автоматически будет подключать файл при нахождении вызовов класса BookTable.
Примечание: В примере выше использована рекомендуемая форма записи данных. Старая форма записи в виде массива:
'ID' => array( 'data_type' => 'integer', 'primary' => true, 'autocomplete' => true, ),
оставлена для совместимости. При инициализации все равно создаются объекты классов BitrixMainEntity*
. Использовать можно оба варианта, правильней — через объекты.
Назад в раздел
public static function checkFields(Result $result, $primary, array $data) { if ($result instanceof EntityAddResult) { if (isset($data['REAL_OBJECT_ID']) && isset($data['FILE_ID']) && !empty($data['REAL_OBJECT_ID']) && !empty($data['FILE_ID']) && $data['REAL_OBJECT_ID'] != $data['ID']) { $field = static::getEntity()->getField('FILE_ID'); $result->addError(new FieldError($field, Loc::getMessage("DISK_OBJECT_ENTITY_ERROR_LINK_FILE_ID", array("#FIELD#" => $field->getTitle())))); } if (isset($data['FILE_ID']) && empty($data['REAL_OBJECT_ID']) && empty($data['FILE_ID'])) { $field = static::getEntity()->getField('FILE_ID'); $result->addError(new FieldError($field, Loc::getMessage("DISK_OBJECT_ENTITY_ERROR_REQUIRED_FILE_ID", array("#FIELD#" => $field->getTitle())))); } } parent::checkFields($result, $primary, $data); }
/** * @param Order $order * @param Payment $payment * @param $operation * @return Result * @throws Exception */ public static function createOperation(Order &$order, Payment &$payment, $operation) { $result = new Result(); $paymentSum = $payment->getSum(); if ($operation == self::OPERATION_DEBIT) { $userBudget = UserBudgetPool::getUserBudgetByOrder($order); if ($userBudget >= $paymentSum) { UserBudgetPool::addPoolItem($order, $paymentSum * -1, UserBudgetPool::BUDGET_TYPE_ORDER_PAY, $payment); // $payment->setField('PAID', 'Y'); } else { $result->addError(new EntityError(Loc::getMessage('ORDER_PS_INNER_ERROR_INSUFFICIENT_MONEY'))); } } elseif ($operation == self::OPERATION_CREDIT) { UserBudgetPool::addPoolItem($order, $paymentSum, UserBudgetPool::BUDGET_TYPE_ORDER_UNPAY, $payment); // $payment->setField('PAID', 'N'); } elseif ($operation == self::OPERATION_RETURN) { $sumPaid = $order->getSumPaid(); $sumTrans = UserBudgetPool::getUserBudgetTransForOrder($order); $finalSumPaid = $paymentSum + $sumTrans; if ($finalSumPaid > 0) { $paymentSum = $paymentSum - $finalSumPaid; } // InternalsUserBudgetPool::addPoolItem($order->getUserId(), ( $paymentSum ), UserBudgetPool::BUDGET_TYPE_CANCEL_RETURN, $order, $payment); // $payment->setField('PAID', 'N'); $payment->setField('IS_RETURN', 'Y'); } else { throw new InvalidOperationException('Wrong operation type!'); } return $result; }
public static function checkFields(Result $result, $primary, array $data) { if ($result instanceof DeleteResult) { if (!ObjectPathTable::isLeaf($primary)) { $result->addError(new EntityError(Loc::getMessage("DISK_OBJECT_ENTITY_ERROR_DELETE_NODE"))); } } parent::checkFields($result, $primary, $data); }
public function validateValue($value, $primary, $row, Result $result) { $validators = $this->getValidators(); foreach ($validators as $validator) { if (is_object($validator)) { $vResult = $validator->validate($value, $primary, $row, $this); } else { $vResult = call_user_func_array($validator, array($value, $primary, $row, $this)); } if ($vResult !== true) { $result->addError(new FieldError($this, $vResult, FieldError::INVALID_VALUE)); break; } } }
/** * Check data for create one or more coupons. * * @param array $data Coupon data. * @param bool $newDiscount New discount flag. * @return MainEntityResult */ public static function checkPacket(array $data, $newDiscount = false) { $result = new MainEntityResult(); $newDiscount = $newDiscount === true; if (empty($data) || !is_array($data)) { $result->addError(new MainEntityEntityError(Loc::getMessage('DISCOUNT_COUPON_PACKET_EMPTY'), 'COUPON_PACKET')); } else { if (empty($data['TYPE']) || !in_array((int) $data['TYPE'], self::getCouponTypes(false))) { $result->addError(new MainEntityEntityError(Loc::getMessage('DISCOUNT_COUPON_VALIDATOR_TYPE'), 'COUPON_PACKET')); } if (!$newDiscount && empty($data['DISCOUNT_ID'])) { $result->addError(new MainEntityEntityError(Loc::getMessage('DISCOUNT_COUPON_VALIDATOR_DISCOUNT_ID'), 'COUPON_PACKET')); } if (isset($data['ACTIVE_FROM']) && !$data['ACTIVE_FROM'] instanceof MainTypeDateTime || isset($data['ACTIVE_TO']) && !$data['ACTIVE_TO'] instanceof MainTypeDateTime) { $result->addError(new MainEntityEntityError(Loc::getMessage('DISCOUNT_COUPON_VALIDATOR_PERIOD'), 'COUPON_PACKET')); } } return $result; }
/** * Checks data fields before saving to DB. Result stores in $result object * * @param Result $result * @param array $data * @param null $id * @throws Exception */ public static function checkFields(Result $result, $primary, array $data) { //checks required fields foreach (static::getEntity()->getFields() as $field) { if ($field instanceof ScalarField && $field->isRequired()) { $fieldName = $field->getName(); if ($id === null && (!isset($data[$fieldName]) || $data[$fieldName] == '') || $id !== null && isset($data[$fieldName]) && $data[$fieldName] == '') { $result->addError(new FieldError($field, getMessage("MAIN_ENTITY_FIELD_REQUIRED", array("#FIELD#" => $field->getTitle())), FieldError::EMPTY_REQUIRED)); } } } // checks data - fieldname & type & strlen etc. foreach ($data as $k => $v) { if (static::getEntity()->hasField($k) && static::getEntity()->getField($k) instanceof ScalarField) { $field = static::getEntity()->getField($k); } elseif (static::getEntity()->hasUField($k)) { // should be continue // checking is inside uf manager $field = static::getEntity()->getUField($k); } else { throw new Exception(sprintf('Field `%s` not found in entity when trying to query %s row.', $k, static::getEntity()->getName())); } $field->validateValue($v, $primary, $data, $result); } }
public static function checkFieldsChain(BitrixMainEntityResult $result, $primary = null, array $fields) { $id = $primary; $errorList = array(); $errorCurrentNumber = 0; foreach ($fields as $item) { $errorCurrentNumber++; $chainFields = array('MAILING_ID' => $id ? $id : 1, 'ID' => $item['ID'], 'REITERATE' => 'Y', 'IS_TRIGGER' => 'Y', 'EMAIL_FROM' => $item['EMAIL_FROM'], 'SUBJECT' => $item['SUBJECT'], 'MESSAGE' => $item['MESSAGE'], 'TIME_SHIFT' => intval($item['TIME_SHIFT'])); $chainId = 0; if (!empty($item['ID'])) { $chainId = $item['ID']; } if ($chainId > 0) { $chain = BitrixSenderMailingChainTable::getRowById(array('ID' => $chainId)); if ($chain && $chain['STATUS'] != BitrixSenderMailingChainTable::STATUS_WAIT) { $chainFields['STATUS'] = $chain['STATUS']; } } if (empty($chainFields['STATUS'])) { $chainFields['STATUS'] = BitrixSenderMailingChainTable::STATUS_WAIT; } $chainFields['ID'] = $chainId; $resultItem = new BitrixMainEntityResult(); BitrixSenderMailingChainTable::checkFields($resultItem, null, $chainFields); if ($resultItem->isSuccess()) { } else { $errorList[$errorCurrentNumber] = $resultItem->getErrors(); } } $delimiter = ''; foreach ($errorList as $number => $errors) { /* @var BitrixMainEntityFieldError[] $errors*/ foreach ($errors as $error) { $result->addError(new EntityFieldError($error->getField(), $delimiter . Loc::getMessage('SENDER_ENTITY_MAILING_CHAIN_ITEM_NUMBER') . $number . ': ' . $error->getMessage(), $error->getCode())); $delimiter = ''; } $delimiter = "n"; } return $result; }
public static function checkFields(Result $result, $primary, array $data) { if ($result instanceof EntityUpdateResult) { if (isset($data['STORAGE_ID'])) { $field = static::getEntity()->getField('STORAGE_ID'); $result->addError(new EntityFieldError($field, Loc::getMessage("DISK_OBJECT_ENTITY_ERROR_UPDATE_STORAGE_ID", array("#FIELD#" => $field->getTitle())))); } if (isset($data['PARENT_ID'])) { $field = static::getEntity()->getField('PARENT_ID'); $result->addError(new EntityFieldError($field, Loc::getMessage("DISK_OBJECT_ENTITY_ERROR_UPDATE_PARENT_ID", array("#FIELD#" => $field->getTitle())))); } } if (!empty($data['NAME']) && (!IOPath::validateFilename($data['NAME']) || strpos($data['NAME'], '%') !== false)) { $field = static::getEntity()->getField('NAME'); $result->addError(new EntityFieldError($field, Loc::getMessage("DISK_OBJECT_ENTITY_ERROR_FIELD_NAME_HAS_INVALID_CHARS", array("#FIELD#" => $field->getTitle())))); } parent::checkFields($result, $primary, $data); }
/** * @param $value * @param $primary * @param $row * @param Result $result * * @return Result * @throws SystemException */ public function validateValue($value, $primary, $row, Result $result) { if ($value instanceof SqlExpression) { return $result; } $validators = $this->getValidators(); foreach ($validators as $validator) { if ($validator instanceof IValidator) { $vResult = $validator->validate($value, $primary, $row, $this); } else { $vResult = call_user_func_array($validator, array($value, $primary, $row, $this)); } if ($vResult !== true) { if ($vResult instanceof EntityError) { $result->addError($vResult); } else { $result->addError(new FieldError($this, $vResult, FieldError::INVALID_VALUE)); } } } return $result; }
public static function checkFields(EntityResult $result, $primary, array $data) { // check data - fieldname & type & strlen etc. foreach ($data as $k => $v) { if (!(static::getEntity()->hasField($k) && static::getEntity()->getField($k) instanceof BitrixMainEntityScalarField)) { throw new Exception(sprintf('Field `%s` not found in entity when trying to query %s row.', $k, static::getEntity()->getName())); } } // check by uf manager $entityName = static::getEntity()->getName(); $hlblock = HighloadBlockTable::getList(array('select' => array('ID'), 'filter' => array('=NAME' => $entityName)))->fetch(); $fields = $GLOBALS['USER_FIELD_MANAGER']->getUserFields('HLBLOCK_' . $hlblock['ID']); // dear uf manager, please go fuck yourself and don't touch unchanged files foreach ($data as $k => $v) { // hide them from him $arUserField = $fields[$k]; if ($arUserField["USER_TYPE"]["BASE_TYPE"] == 'file' && !is_array($v)) { //unset($data[$k]); } } if (!$GLOBALS["USER_FIELD_MANAGER"]->checkFields('HLBLOCK_' . $hlblock['ID'], null, $data)) { if (is_object($GLOBALS['APPLICATION']) && $GLOBALS['APPLICATION']->getException()) { $e = $GLOBALS['APPLICATION']->getException(); $result->addError(new EntityEntityError($e->getString())); $GLOBALS['APPLICATION']->resetException(); } else { $result->addError(new EntityEntityError("Unknown error.")); } } }
/** * Извлечение данных для связей */ protected function collectReferencesData() { $result = new EntityResult(); $references = $this->getReferences(); // Извлечение данных управляемых связей foreach ($references as $fieldName => $reference) { if (array_key_exists($fieldName, $this->data)) { if (!is_array($this->data[$fieldName])) { $result->addError(new EntityEntityError('Связь должна быть множественным полем')); return $result; } // Извлечение данных для связи $this->referencesData[$fieldName] = $this->data[$fieldName]; unset($this->data[$fieldName]); } } return $result; }
public static function saveProfileData($profileId, Order $order, array $formData) { $result = new Result(); $errors = array(); if (!($profileName = $order->getPropertyCollection()->getProfileName())) { $result->addError(new EntityError(Loc::getMessage("SALE_ORDEREDIT_PROFILE_ERROR_NAME"))); return $result; } $res = CSaleOrderUserProps::DoSaveUserProfile($order->getUserId(), $profileId, $profileName->getValue(), $order->getPersonTypeId(), $propCollection = $formData["PROPERTIES"], $errors); if ($res === false) { if (!empty($errors)) { foreach ($errors as $error) { $result->addError(new EntityError($error . "<br>n")); } } else { $result->addError(new EntityError(Loc::getMessage("SALE_ORDEREDIT_PROFILE_ERROR_SAVE"))); } } return $result; }