Bitrix entity error

См. определение в файле entityerror.php строка 11

Открытые члены

  __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()

getCustomData ( )
inherited
Возвращает
mixed|null

См. определение в файле error.php строка 59

◆ getMessage()

Returns the message of the error.

Возвращает
string

См. определение в файле error.php строка 51

◆ jsonSerialize()

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

  1. Главная
  2. Сниппеты Битрикс 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
  • BookTable::update
  • BookTable::delete
  • Validators
  • Events
  • Value formatting
  • Calculated values
  • Error warnings
  • 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 (создание, чтение, обновление и удаление данных).

  • Концепция сущностей
  • Типизация полей
  • Primary & autoincrement & required
  • Маппинг имени колонки
  • Выражения ExpressionField
  • Пользовательские поля
  • Пример
  • Концепция сущностей

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

    Например, сущность Пользователь — это множество пользователей с набором полей:

    • 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;
     }
    <?php /** * Bitrix Framework * @package bitrix * @subpackage main * @copyright 2001-2016 Bitrix */ namespace BitrixMainORM; use BitrixMain; use BitrixMainORMDataDataManager; use BitrixMainORMFieldsExpressionField; use BitrixMainORMFieldsField; use BitrixMainORMFieldsRelationsReference; use BitrixMainORMFieldsScalarField; use BitrixMainORMObjectifyEntityObject; use BitrixMainORMObjectifyCollection; use BitrixMainORMQueryQuery; use BitrixMainTextStringHelper; /** * Base entity */ class Entity { /** @var DataManager */ protected $className; protected $module, $name, $connectionName, $dbTableName, $primary, $autoIncrement; protected $uf_id, $isUts, $isUtm; /** @var Field[] */ protected $fields; protected $fieldsMap; /** @var UField[] */ protected $u_fields; /** @var string Unique code */ protected $code; protected $references; /** @var static[] */ protected static $instances; /** @var bool */ protected $isClone = false; const DEFAULT_OBJECT_PREFIX = ‘EO_’; /** * Returns entity object * * @param $entityName * * @return Entity * @throws MainArgumentException * @throws MainSystemException */ public static function get($entityName) { return static::getInstance($entityName); } /** * Checks if entity exists * * @param $entityName * * @return bool */ public static function has($entityName) { $entityClass = static::normalizeEntityClass($entityName); return class_exists($entityClass); } /** * @static * * @param string $entityName * * @return Entity * @throws MainArgumentException * @throws MainSystemException */ public static function getInstance($entityName) { $entityName = static::normalizeEntityClass($entityName); return self::getInstanceDirect($entityName); } /** * @param DataManager|string $className * * @return mixed * @throws MainArgumentException * @throws MainSystemException */ protected static function getInstanceDirect($className) { if (empty(self::$instances[$className])) { /** @var Entity $entity */ $entityClass = $className::getEntityClass(); // in case of calling Table class was not ended with entity initialization if (empty(self::$instances[$className])) { $entity = new $entityClass; $entity->initialize($className); $entity->postInitialize(); // call user-defined postInitialize $className::postInitialize($entity); self::$instances[$className] = $entity; } } return self::$instances[$className]; } /** * Fields factory * * @param string $fieldName * @param array|Field $fieldInfo * * @return Field * @throws MainArgumentException * @throws MainSystemException */ public function initializeField($fieldName, $fieldInfo) { if ($fieldInfo instanceof Field) { $field = $fieldInfo; // rewrite name if (!empty($fieldName) && !is_numeric($fieldName)) { $field->setName($fieldName); } } elseif (is_array($fieldInfo)) { if (!empty($fieldInfo[‘reference’])) { if (is_string($fieldInfo[‘data_type’]) && strpos($fieldInfo[‘data_type’], ‘\’) === false) { // if reference has no namespace, then it’is in the same namespace $fieldInfo[‘data_type’] = $this->getNamespace().$fieldInfo[‘data_type’]; } //$refEntity = Base::getInstance($fieldInfo[‘data_type’].»Table»); $field = new Reference($fieldName, $fieldInfo[‘data_type’], $fieldInfo[‘reference’], $fieldInfo); } elseif (!empty($fieldInfo[‘expression’])) { $expression = array_shift($fieldInfo[‘expression’]); $buildFrom = $fieldInfo[‘expression’]; $field = new ExpressionField($fieldName, $expression, $buildFrom, $fieldInfo); } elseif (!empty($fieldInfo[‘USER_TYPE_ID’])) { $field = new UField($fieldInfo); } else { $fieldClass = StringHelper::snake2camel($fieldInfo[‘data_type’]) . ‘Field’; $fieldClass = ‘\Bitrix\Main\Entity\’.$fieldClass; if (strlen($fieldInfo[‘data_type’]) && class_exists($fieldClass)) { $field = new $fieldClass($fieldName, $fieldInfo); } elseif (strlen($fieldInfo[‘data_type’]) && class_exists($fieldInfo[‘data_type’])) { $fieldClass = $fieldInfo[‘data_type’]; $field = new $fieldClass($fieldName, $fieldInfo); } else { throw new MainArgumentException(sprintf( ‘Unknown data type «%s» found for `%s` field in %s Entity.’, $fieldInfo[‘data_type’], $fieldName, $this->getName() )); } } } else { throw new MainArgumentException(sprintf(‘Unknown field type `%s`’, is_object($fieldInfo) ? get_class($fieldInfo) : gettype($fieldInfo) )); } $field->setEntity($this); $field->postInitialize(); return $field; } public function initialize($className) { /** @var $className DataManager */ $this->className = $className; /** @var DataManager $className */ $this->connectionName = $className::getConnectionName(); $this->dbTableName = $className::getTableName(); $this->fieldsMap = $className::getMap(); $this->uf_id = $className::getUfId(); $this->isUts = $className::isUts(); $this->isUtm = $className::isUtm(); // object & collection classes // Loader::registerObjectClass($className::getObjectClass(), $className); // Loader::registerCollectionClass($className::getCollectionClass(), $className); } /** * @throws MainArgumentException * @throws MainSystemException */ public function postInitialize() { // basic properties $classPath = explode(‘\’, ltrim($this->className, ‘\’)); $this->name = substr(end($classPath), 0, —5); // default db table name if (is_null($this->dbTableName)) { $_classPath = array_slice($classPath, 0, —1); $this->dbTableName = ‘b_’; foreach ($_classPath as $i => $_pathElem) { if ($i == 0 && $_pathElem == ‘Bitrix’) { // skip bitrix namespace continue; } if ($i == 1 && $_pathElem == ‘Main’) { // also skip Main module continue; } $this->dbTableName .= strtolower($_pathElem).‘_’; } // add class if ($this->name !== end($_classPath)) { $this->dbTableName .= StringHelper::camel2snake($this->name); } else { $this->dbTableName = substr($this->dbTableName, 0, —1); } } $this->primary = array(); $this->references = array(); // attributes foreach ($this->fieldsMap as $fieldName => &$fieldInfo) { $this->addField($fieldInfo, $fieldName); } if (!empty($this->fieldsMap) && empty($this->primary)) { throw new MainSystemException(sprintf(‘Primary not found for %s Entity’, $this->name)); } // attach userfields if (empty($this->uf_id)) { // try to find ENTITY_ID by map $userTypeManager = MainApplication::getUserTypeManager(); if($userTypeManager) { $entityList = $userTypeManager->getEntityList(); $ufId = is_array($entityList) ? array_search($this->className, $entityList) : false; if ($ufId !== false) { $this->uf_id = $ufId; } } } if (!empty($this->uf_id)) { MainUserFieldTable::attachFields($this, $this->uf_id); } } /** * Returns class of Object for current entity. * * @return EntityObject|string */ public function getObjectClass() { $dataManagerClass = $this->className; return static::normalizeName($dataManagerClass::getObjectClass()); } /** * Returns class name of Object for current entity. * * @return EntityObject|string */ public function getObjectClassName() { $dataManagerClass = $this->className; return $dataManagerClass::getObjectClassName(); } public static function getDefaultObjectClassName($entityName) { $className = $entityName; if ($className == ») { // entity without name $className = ‘NNM_Object’; } $className = static::DEFAULT_OBJECT_PREFIX.$className; return $className; } /** * @return Collection|string */ public function getCollectionClass() { $dataClass = $this->getDataClass(); return static::normalizeName($dataClass::getCollectionClass()); } /** * @return Collection|string */ public function getCollectionClassName() { $dataClass = $this->getDataClass(); return $dataClass::getCollectionClassName(); } public static function getDefaultCollectionClassName($entityName) { $className = static::DEFAULT_OBJECT_PREFIX.$entityName.‘_Collection’; return $className; } /** * @param bool $setDefaultValues * * @return null Actual type should be annotated by orm:annotate */ public function createObject($setDefaultValues = true) { $objectClass = $this->getObjectClass(); return new $objectClass($setDefaultValues); } /** * @return null Actual type should be annotated by orm:annotate */ public function createCollection() { $collectionClass = $this->getCollectionClass(); return new $collectionClass($this); } /** * @see EntityObject::wakeUp() * * @param $row * * @return null Actual type should be annotated by orm:annotate * @throws MainArgumentException * @throws MainSystemException */ public function wakeUpObject($row) { $objectClass = $this->getObjectClass(); return $objectClass::wakeUp($row); } /** * @see Collection::wakeUp() * * @param $rows * * @return null Actual type should be annotated by orm:annotate * @throws MainArgumentException * @throws MainSystemException */ public function wakeUpCollection($rows) { $collectionClass = $this->getCollectionClass(); return $collectionClass::wakeUp($rows); } /** * @param Field $field * * @return bool * @throws MainArgumentException * @throws MainSystemException */ protected function appendField(Field $field) { if (isset($this->fields[StringHelper::strtoupper($field->getName())]) && !$this->isClone) { trigger_error(sprintf( ‘Entity `%s` already has Field with name `%s`.’, $this->getFullName(), $field->getName() ), E_USER_WARNING); return false; } if ($field instanceof Reference) { // references cache $this->references[$field->getRefEntityName()][] = $field; } $this->fields[StringHelper::strtoupper($field->getName())] = $field; if ($field instanceof ScalarField && $field->isPrimary()) { $this->primary[] = $field->getName(); if($field->isAutocomplete()) { $this->autoIncrement = $field->getName(); } } // add reference field for UField iblock_section if ($field instanceof UField && $field->getTypeId() == ‘iblock_section’) { $refFieldName = $field->getName().‘_BY’; if ($field->isMultiple()) { $localFieldName = $field->getValueFieldName(); } else { $localFieldName = $field->getName(); } $newFieldInfo = array( ‘data_type’ => ‘BitrixIblockSection’, ‘reference’ => array($localFieldName, ‘ID’) ); $newRefField = new Reference($refFieldName, $newFieldInfo[‘data_type’], $newFieldInfo[‘reference’][0], $newFieldInfo[‘reference’][1]); $newRefField->setEntity($this); $this->fields[StringHelper::strtoupper($refFieldName)] = $newRefField; } return true; } /** * @param array|Field $fieldInfo * @param null|string $fieldName * * @return Field|false * @throws MainArgumentException * @throws MainSystemException */ public function addField($fieldInfo, $fieldName = null) { $field = $this->initializeField($fieldName, $fieldInfo); return $this->appendField($field) ? $field : false; } public function getReferencesCountTo($refEntityName) { if (array_key_exists($key = strtolower($refEntityName), $this->references)) { return count($this->references[$key]); } return 0; } public function getReferencesTo($refEntityName) { if (array_key_exists($key = strtolower($refEntityName), $this->references)) { return $this->references[$key]; } return array(); } // getters public function getFields() { return $this->fields; } /** * @param $name * * @return Field|ScalarField * @throws MainArgumentException */ public function getField($name) { if ($this->hasField($name)) { return $this->fields[StringHelper::strtoupper($name)]; } throw new MainArgumentException(sprintf( ‘%s Entity has no `%s` field.’, $this->getName(), $name )); } public function hasField($name) { return isset($this->fields[StringHelper::strtoupper($name)]); } /** * @return ScalarField[] */ public function getScalarFields() { $scalarFields = array(); foreach ($this->getFields() as $field) { if ($field instanceof ScalarField) { $scalarFields[$field->getName()] = $field; } } return $scalarFields; } /** * @deprecated * * @param $name * * @return UField * @throws MainArgumentException * @throws MainSystemException */ public function getUField($name) { if ($this->hasUField($name)) { return $this->u_fields[$name]; } throw new MainArgumentException(sprintf( ‘%s Entity has no `%s` userfield.’, $this->getName(), $name )); } /** * @deprecated * * @param $name * * @return bool * @throws MainSystemException */ public function hasUField($name) { if (is_null($this->u_fields)) { $this->u_fields = array(); if($this->uf_id <> ») { /** @var CUserTypeManager $USER_FIELD_MANAGER */ global $USER_FIELD_MANAGER; foreach($USER_FIELD_MANAGER->getUserFields($this->uf_id) as $info) { $this->u_fields[$info[‘FIELD_NAME’]] = new UField($info); $this->u_fields[$info[‘FIELD_NAME’]]->setEntity($this); // add references for ufield (UF_DEPARTMENT_BY) if($info[‘USER_TYPE_ID’] == ‘iblock_section’) { $info[‘FIELD_NAME’] .= ‘_BY’; $this->u_fields[$info[‘FIELD_NAME’]] = new UField($info); $this->u_fields[$info[‘FIELD_NAME’]]->setEntity($this); } } } } return isset($this->u_fields[$name]); } public function getName() { return $this->name; } public function getFullName() { return substr($this->className, 0, —5); } public function getNamespace() { return substr($this->className, 0, strrpos($this->className, ‘\’) + 1); } public function getModule() { if($this->module === null) { // BitrixMainSite -> «main» // PartnerModuleThing -> «partner.module» // Thing -> «» $parts = explode(«\», $this->className); if($parts[1] == «Bitrix«) $this->module = strtolower($parts[2]); elseif(!empty($parts[1]) && isset($parts[2])) $this->module = strtolower($parts[1].».«.$parts[2]); else $this->module = «»; } return $this->module; } /** * @return DataManager */ public function getDataClass() { return $this->className; } /** * @return MainDBConnection * @throws MainSystemException */ public function getConnection() { /** @var MainDBConnection $conn */ $conn = MainApplication::getInstance()->getConnectionPool()->getConnection($this->connectionName); return $conn; } public function getDBTableName() { return $this->dbTableName; } public function getPrimary() { return count($this->primary) == 1 ? $this->primary[0] : $this->primary; } public function getPrimaryArray() { return $this->primary; } public function getAutoIncrement() { return $this->autoIncrement; } public function isUts() { return $this->isUts; } public function isUtm() { return $this->isUtm; } public function getUfId() { return $this->uf_id; } /** * @param Query $query * * @return Query */ public function setDefaultScope($query) { $dataClass = $this->className; return $dataClass::setDefaultScope($query); } public static function isExists($name) { return class_exists(static::normalizeEntityClass($name)); } /** * @param $entityName * * @return string|DataManager */ public static function normalizeEntityClass($entityName) { if (strtolower(substr($entityName, —5)) !== ‘table’) { $entityName .= ‘Table’; } if (substr($entityName, 0, 1) !== ‘\’) { $entityName = ‘\’.$entityName; } return $entityName; } public static function getEntityClassParts($class) { $class = static::normalizeEntityClass($class); $lastPos = strrpos($class, ‘\’); if($lastPos === 0) { //global namespace $namespace = «»; } else { $namespace = substr($class, 1, $lastPos1); } $name = substr($class, $lastPos + 1, —5); return compact(‘namespace’, ‘name’); } public function getCode() { if ($this->code === null) { $this->code = »; // get absolute path to class $class_path = explode(‘\’, strtoupper(ltrim($this->className, ‘\’))); // cut class name to leave namespace only $class_path = array_slice($class_path, 0, —1); // cut Bitrix namespace if (count($class_path) && $class_path[0] === ‘BITRIX’) { $class_path = array_slice($class_path, 1); } // glue module name if (count($class_path)) { $this->code = join(‘_’, $class_path).‘_’; } // glue entity name $this->code .= strtoupper(StringHelper::camel2snake($this->getName())); } return $this->code; } public function getLangCode() { return $this->getCode().‘_ENTITY’; } public function getTitle() { $dataClass = $this->getDataClass(); $title = $dataClass::getTitle(); if ($title === null) { $title = MainLocalizationLoc::getMessage($this->getLangCode()); } return $title; } /** * @deprecated Use BitrixStringHelper::camel2snake instead * * @param $str * * @return string */ public static function camel2snake($str) { return StringHelper::camel2snake($str); } /** * @deprecated Use BitrixStringHelper::snake2camel instead * * @param $str * * @return mixed */ public static function snake2camel($str) { return StringHelper::snake2camel($str); } public static function normalizeName($entityName) { if (substr($entityName, 0, 1) !== ‘\’) { $entityName = ‘\’.$entityName; } if (strtolower(substr($entityName, —5)) === ‘table’) { $entityName = substr($entityName, 0, —5); } return $entityName; } public function __clone() { $this->isClone = true; } /** * @param Query $query * @param null $entity_name * * @return Entity * @throws MainArgumentException * @throws MainSystemException */ public static function getInstanceByQuery(Query $query, &$entity_name = null) { if ($entity_name === null) { $entity_name = ‘Tmp’.randString().‘x’; } elseif (!preg_match(‘/^[a-z0-9_]+$/i’, $entity_name)) { throw new MainArgumentException(sprintf( ‘Invalid entity name `%s`.’, $entity_name )); } $query_string = ‘(‘.$query->getQuery().‘)’; $query_chains = $query->getChains(); $replaced_aliases = array_flip($query->getReplacedAliases()); // generate fieldsMap $fieldsMap = array(); foreach ($query->getSelect() as $k => $v) { // convert expressions to regular field, clone in case of regular scalar field if (is_array($v)) { // expression $fieldsMap[$k] = array(‘data_type’ => $v[‘data_type’]); } else { if ($v instanceof ExpressionField) { $fieldDefinition = $v->getName(); // better to initialize fields as objects after entity is created $dataType = Field::getOldDataTypeByField($query_chains[$fieldDefinition]->getLastElement()->getValue()); $fieldsMap[$fieldDefinition] = array(‘data_type’ => $dataType); } else { $fieldDefinition = is_numeric($k) ? $v : $k; /** @var Field $field */ $field = $query_chains[$fieldDefinition]->getLastElement()->getValue(); if ($field instanceof ExpressionField) { $dataType = Field::getOldDataTypeByField($query_chains[$fieldDefinition]->getLastElement()->getValue()); $fieldsMap[$fieldDefinition] = array(‘data_type’ => $dataType); } else { /** @var ScalarField[] $fieldsMap */ $fieldsMap[$fieldDefinition] = clone $field; $fieldsMap[$fieldDefinition]->setName($fieldDefinition); $fieldsMap[$fieldDefinition]->setColumnName($fieldDefinition); $fieldsMap[$fieldDefinition]->resetEntity(); } } } if (isset($replaced_aliases[$k])) { if (is_array($fieldsMap[$k])) { $fieldsMap[$k][‘column_name’] = $replaced_aliases[$k]; } elseif ($fieldsMap[$k] instanceof ScalarField) { /** @var ScalarField[] $fieldsMap */ $fieldsMap[$k]->setColumnName($replaced_aliases[$k]); } } } // generate class content $eval = ‘class ‘.$entity_name.‘Table extends ‘.DataManager::class.‘ {‘.PHP_EOL; $eval .= ‘public static function getMap() {‘.PHP_EOL; $eval .= ‘return ‘.var_export([‘TMP_ID’ => [‘data_type’ => ‘integer’, ‘primary’ => true, ‘auto_generated’ => true]], true).‘;’.PHP_EOL; $eval .= ‘}’; $eval .= ‘public static function getTableName() {‘.PHP_EOL; $eval .= ‘return ‘.var_export($query_string, true).‘;’.PHP_EOL; $eval .= ‘}’; $eval .= ‘}’; eval($eval); $entity = self::getInstance($entity_name); foreach ($fieldsMap as $k => $v) { $entity->addField($v, $k); } return $entity; } /** * @param string $entityName * @param null|array[]|Field[] $fields * @param array $parameters [namespace, table_name, uf_id, parent, parent_map, default_scope] * * @return Entity * * @throws MainArgumentException * @throws MainSystemException */ public static function compileEntity($entityName, $fields = null, $parameters = array()) { $classCode = »; $classCodeEnd = »; if (strtolower(substr($entityName, —5)) !== ‘table’) { $entityName .= ‘Table’; } // validation if (!preg_match(‘/^[a-z0-9_]+$/i’, $entityName)) { throw new MainArgumentException(sprintf( ‘Invalid entity className `%s`.’, $entityName )); } /** @var DataManager $fullEntityName */ $fullEntityName = $entityName; // namespace configuration if (!empty($parameters[‘namespace’]) && $parameters[‘namespace’] !== ‘\’) { $namespace = $parameters[‘namespace’]; if (!preg_match(‘/^[a-z0-9_\\]+$/i’, $namespace)) { throw new MainArgumentException(sprintf( ‘Invalid namespace name `%s`’, $namespace )); } $classCode = $classCodenamespace {$namespace} «.»{«; $classCodeEnd = ‘}’.$classCodeEnd; $fullEntityName = ‘\’.$namespace.‘\’.$fullEntityName; } $parentClass = !empty($parameters[‘parent’]) ? $parameters[‘parent’] : DataManager::class; // build entity code $classCode = $classCodeclass {$entityName} extends \».$parentClass {«; $classCodeEnd = ‘}’.$classCodeEnd; if (!empty($parameters[‘table_name’])) { $classCode .= ‘public static function getTableName(){return ‘.var_export($parameters[‘table_name’], true).‘;}’; } if (!empty($parameters[‘uf_id’])) { $classCode .= ‘public static function getUfId(){return ‘.var_export($parameters[‘uf_id’], true).‘;}’; } if (!empty($parameters[‘default_scope’])) { $classCode .= ‘public static function setDefaultScope($query){‘.$parameters[‘default_scope’].‘}’; } if (isset($parameters[‘parent_map’]) && $parameters[‘parent_map’] == false) { $classCode .= ‘public static function getMap(){return [];}’; } if(isset($parameters[‘object_parent’]) && is_a($parameters[‘object_parent’], EntityObject::class, true)) { $classCode .= ‘public static function getObjectParentClass(){return ‘.var_export($parameters[‘object_parent’], true).‘;}’; } // create entity eval($classCode.$classCodeEnd); $entity = $fullEntityName::getEntity(); // add fields if (!empty($fields)) { foreach ($fields as $fieldName => $field) { $entity->addField($field, $fieldName); } } return $entity; } /** * @return string[] Array of SQL queries * @throws MainSystemException */ public function compileDbTableStructureDump() { $fields = $this->getScalarFields(); /** @var MainDBMysqlCommonConnection $connection */ $connection = $this->getConnection(); $autocomplete = []; $unique = []; foreach ($fields as $field) { if ($field->isAutocomplete()) { $autocomplete[] = $field->getName(); } if ($field->isUnique()) { $unique[] = $field->getName(); } } // start collecting queries $connection->disableQueryExecuting(); // create table $connection->createTable($this->getDBTableName(), $fields, $this->getPrimaryArray(), $autocomplete); // create indexes foreach ($unique as $fieldName) { $connection->createIndex($this->getDBTableName(), $fieldName, [$fieldName], null, MainDBMysqlCommonConnection::INDEX_UNIQUE); } // stop collecting queries $connection->enableQueryExecuting(); return $connection->getDisabledQueryExecutingDump(); } /** * @param $dataClass * * @return EntityObject|string */ public static function compileObjectClass($dataClass) { $dataClass = static::normalizeEntityClass($dataClass); $classParts = static::getEntityClassParts($dataClass); if (class_exists($dataClass::getObjectClass(), false) && is_subclass_of($dataClass::getObjectClass(), EntityObject::class)) { // class is already defined return $dataClass::getObjectClass(); } $baseObjectClass = ‘\’.$dataClass::getObjectParentClass(); $objectClassName = static::getDefaultObjectClassName($classParts[‘name’]); $eval = «»; if($classParts[‘namespace’] <> ») { $eval .= «namespace {$classParts[‘namespace’]} {«; } $eval .= «class {$objectClassName} extends {$baseObjectClass} {«; $eval .= «static public $dataClass = ‘{$dataClass}‘;«; $eval .= «}«; // end class if($classParts[‘namespace’] <> ») { $eval .= «}«; // end namespace } eval($eval); return $dataClass::getObjectClass(); } /** * @param $dataClass * * @return Collection|string */ public static function compileCollectionClass($dataClass) { $dataClass = static::normalizeEntityClass($dataClass); $classParts = static::getEntityClassParts($dataClass); if (class_exists($dataClass::getCollectionClass(), false) && is_subclass_of($dataClass::getCollectionClass(), Collection::class)) { // class is already defined return $dataClass::getCollectionClass(); } $baseCollectionClass = ‘\’.$dataClass::getCollectionParentClass(); $collectionClassName = static::getDefaultCollectionClassName($classParts[‘name’]); $eval = «»; if($classParts[‘namespace’] <> ») { $eval .= «namespace {$classParts[‘namespace’]} {«; } $eval .= «class {$collectionClassName} extends {$baseCollectionClass} {«; $eval .= «static public $dataClass = ‘{$dataClass}‘;«; $eval .= «}«; // end class if($classParts[‘namespace’] <> ») { $eval .= «}«; // end namespace } eval($eval); return $dataClass::getCollectionClass(); } /** * Creates table according to Fields collection * * @return void * @throws MainSystemException */ public function createDbTable() { foreach ($this->compileDbTableStructureDump() as $sqlQuery) { $this->getConnection()->query($sqlQuery); } } /** * @param Entity|string $entity * * @return bool */ public static function destroy($entity) { if ($entity instanceof Entity) { $entityName = $entity->getDataClass(); } else { $entityName = static::normalizeEntityClass($entity); } if (isset(self::$instances[$entityName])) { unset(self::$instances[$entityName]); DataManager::unsetEntity($entityName); return true; } return false; } /** * Reads data from cache. * * @param int $ttl TTL. * @param string $cacheId The cache ID. * @param bool $countTotal Whether to read total count from the cache. * * @return MainDBArrayResult|null * @throws MainSystemException */ public function readFromCache($ttl, $cacheId, $countTotal = false) { if($ttl > 0) { $cache = MainApplication::getInstance()->getManagedCache(); $cacheDir = $this->getCacheDir(); $count = null; if($countTotal && $cache->read($ttl, $cacheId.total«, $cacheDir)) { $count = $cache->get($cacheId.total«); } if($cache->read($ttl, $cacheId, $cacheDir)) { $result = new MainDBArrayResult($cache->get($cacheId)); if($count !== null) { $result->setCount($count); } return $result; } } return null; } /** * @param MainDBResult $result A query result to cache. * @param string $cacheId The cache ID. * @param bool $countTotal Whether to write total count to the cache. * * @return MainDBArrayResult * @throws MainObjectPropertyException * @throws MainSystemException */ public function writeToCache(MainDBResult $result, $cacheId, $countTotal = false) { $rows = $result->fetchAll(); $arrayResult = new MainDBArrayResult($rows); $cache = MainApplication::getInstance()->getManagedCache(); $cache->set($cacheId, $rows); if($countTotal) { $count = $result->getCount(); $cache->set($cacheId.total«, $count); $arrayResult->setCount($count); } return $arrayResult; } /** * Returns cache TTL for the entity, possibly limited by the .settings.php: * ‘cache_flags’ => array(‘value’=> array( * «b_group_max_ttl» => 200, * «b_group_min_ttl» => 100, * )) * Maximum is a higher-priority. * @param int $ttl Preferable TTL * @return int Calculated TTL */ public function getCacheTtl($ttl) { $table = $this->getDBTableName(); $cacheFlags = MainConfigConfiguration::getValuecache_flags«); if(isset($cacheFlags[$table_min_ttl«])) { $ttl = (int)max($ttl, $cacheFlags[$table_min_ttl«]); } if(isset($cacheFlags[$table_max_ttl«])) { $ttl = (int)min($ttl, $cacheFlags[$table_max_ttl«]); } return $ttl; } protected function getCacheDir() { return «orm_«.$this->getDBTableName(); } /** * Cleans all cache entries for the entity. */ public function cleanCache() { if($this->getCacheTtl(100) > 0) { //cache might be disabled in .settings.php via *_max_ttl = 0 option $cache = MainApplication::getInstance()->getManagedCache(); $cache->cleanDir($this->getCacheDir()); } } /** * Sets a flag indicating full text index support for a field. * * @deprecated Does nothing, mysql 5.6 has fulltext always enabled. * @param string $field * @param bool $mode */ public function enableFullTextIndex($field, $mode = true) { } /** * Returns true if full text index is enabled for a field. * * @deprecated Always returns true, mysql 5.6 has fulltext always enabled. * @param string $field * @return bool */ public function fullTextIndexEnabled($field) { return true; } }

    Понравилась статья? Поделить с друзьями:
  • Bitrix catalog 404 ошибка
  • Bitrix 410 error processing file
  • Bitmos oxy 6000 o2 error
  • Bitmap manager error file could not be found 3ds max
  • Bitlocker driver ошибка 24620 windows 10