Операции с сущностями и события

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

Основные операции

Для модификации данных в БД можно использовать три ключевых метода: add, update, delete. Рассмотрим их на примера класса BookTable.

Метод add

Метод добавляет запись, принимая массив значений. Ключами массива являются имена полей сущности.

namespace SomePartner\MyBooksCatalog; // Пространство имен, в котором находится код
        
        use Bitrix\Main\Type; // Подключаем класс Type из пространства имен Bitrix\Main
        
        $result = BookTable::add([
            'ISBN' => '978-0321127426',
            'TITLE' => 'Patterns of Enterprise Application Architecture',
            'PUBLISH_DATE' => new Type\Date('2002-11-16', 'Y-m-d') // Указываем дату публикации книги
        ]);
        
        if ($result->isSuccess())
        { // Проверяем, успешно ли добавлена запись
            $id = $result->getId(); // Получаем ID новой записи для дальнейших операций
        }
        

Метод add возвращает объект Entity\AddResult. Этот объект содержит ID добавленной записи и позволяет проверить, успешно ли была добавлена запись в базу данных.

Для полей типа DateField и DateTimeField, а также пользовательских полей «Дата» и «Дата со временем» используйте объекты Bitrix\Main\Type\Date и Bitrix\Main\Type\DateTime. По умолчанию в конструктор передается строковая дата в формате сайта, но можно указать формат явно.

Метод update

Метод обновляет запись, добавляя к массиву значений первичный ключ.

$result = BookTable::update($id, [
            'PUBLISH_DATE' => new Type\Date('2002-11-15', 'Y-m-d') // Изменить дату
        ]);
        

Метод update в качестве результата возвращает объект Entity\UpdateResult, у которого есть проверочный метод isSuccess(). Проверочный метод устанавливает, были ли в запросе ошибки.

Чтобы узнать, была ли запись фактически обновлена, используйте метод getAffectedRowsCount().

Метод delete

Для удаления записи укажите ее первичный ключ.

$result = BookTable::delete($id);
        

Чтобы удалить запись с составным ключом, передайте массив со всеми значениями ключа.

BookTable::delete([
            'key1' => value1,
            'key2' => value2
        ]);
        

Результаты операции

Если в ходе операции возникли ошибки, метод вернет массив сообщений об ошибках.

$result = BookTable::update(...); // Выполняем обновление записи в таблице BookTable
        
        if (!$result->isSuccess())
        { // Проверяем, успешно ли выполнено обновление
            $errors = $result->getErrorMessages(); // Если обновление не удалось, получаем сообщения об ошибках
        }
        

События

Чтобы хранить в БД только важные данные, используйте обработчик события.

Доступные события:

Событие

Когда срабатывает

Описание параметров

OnBeforeAdd

Перед добавлением новой записи в базу данных

fields — данные, которые будут добавлены

OnAdd

В момент добавления записи в базу данных

fields — данные, которые были добавлены

OnAfterAdd

После успешного добавления записи

primary — первичный ключ записи
fields — данные добавленной записи

OnBeforeUpdate

Перед обновлением существующей записи

primary — первичный ключ записи
fields — данные для обновления

OnUpdate

В момент обновления записи

primary — первичный ключ записи
fields — данные, которые были обновлены

OnAfterUpdate

После успешного обновления записи

primary — первичный ключ записи
fields — обновленные данные

OnBeforeDelete

Перед удалением записи из базы данных

primary — первичный ключ записи, которую планируется удалить

OnDelete

В момент удаления записи

primary — первичный ключ удаляемой записи

OnAfterDelete

После успешного удаления записи

primary — первичный ключ удаленной записи

Чтобы подписаться на событие в любом месте скрипта, используйте менеджер событий. Это позволяет выполнять определенные действия при наступлении событий в системе.

$em = \Bitrix\Main\ORM\EventManager::getInstance();
        
        $em->addEventHandler(
            BookTable::class, // Класс сущности, для которого регистрируется обработчик
            \Bitrix\Main\ORM\Data\DataManager::EVENT_ON_BEFORE_ADD, // Код события, которое будет обрабатываться
            function ()
            { // Ваша callback-функция
                var_dump('handle entity event'); // Действие, выполняемое при срабатывании события
            }
        );
        
  • $em = \Bitrix\Main\ORM\EventManager::getInstance(); создает объект менеджера событий, который управляет подписками на события.

  • $em->addEventHandler(...) добавляет обработчик для события. Укажите класс сущности и код события, которое будет обрабатываться.

  • function () { ... } — анонимная callback-функция, которая будет выполнена при срабатывании события.

Как изменить данные с помощью события

Система распознает метод onBeforeAdd как обработчик события «перед добавлением». В нем можно изменить данные или провести дополнительные проверки.

В примере с валидаторами для поля ISBN проверяли наличие 13 цифр. Но в поле ISBN могут быть еще и дефисы, которые не нужно хранить в БД. Изменим поле ISBN с помощью метода modifyFields.

class BookTable extends Entity\DataManager
        {
        	...
        	
            public static function onBeforeAdd(Entity\Event $event)
            {
                $result = new Entity\EventResult;
                $data = $event->getParameter("fields");
        
                if (isset($data['ISBN']))
                {
                    $cleanIsbn = str_replace('-', '', $data['ISBN']); // Удаляем дефисы из ISBN
                    $result->modifyFields(['ISBN' => $cleanIsbn]); // Модифицируем поле ISBN
                }
        
                return $result;
            }
        }
        
// до преобразования
        978-0321127426
        978-1-449-31428-6
        9780201485677
        // после преобразования
        9780321127426
        9781449314286
        9780201485677
        

После преобразования в значении остались только цифры, поэтому можно использовать стандартный валидатор RegExp — проверку по регулярному выражению.

'validation' => function()
        {
            return [
                // function ($value)
                // {
                //     $clean = str_replace('-', '', $value);
                //
                //     if (preg_match('/^\d{13}$/', $clean))
                //     {
                //         return true;
                //     }
                //     else
                //     {
                //         return 'Код ISBN должен содержать 13 цифр.';
                //     }
                // },
                new Entity\Validator\RegExp('/\d{13}/'), // Валидатор, проверяющий, что значение содержит 13 цифр подряд
                ...
            ];
        }
        

Как запретить обновление данных

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

  • Удалите ISBN из данных для обновления.
public static function onBeforeUpdate(Entity\Event $event)
        {
            $result = new Entity\EventResult;
            $data = $event->getParameter("fields");
        
            if (isset($data['ISBN']))
            {
                $result->unsetFields(['ISBN']); // Удаляет поле ISBN из данных для обновления
            }
        
            return $result;
        }
        
  • Сгенерируйте ошибку при обновлении.
public static function onBeforeUpdate(Entity\Event $event)
        {
            $result = new Entity\EventResult;
            $data = $event->getParameter("fields");
        
            if (isset($data['ISBN']))
            {
            // Получает объект поля ISBN и выдает сообщение об ошибке
                $result->addError(new Entity\FieldError( 
                    $event->getEntity()->getField('ISBN'), 
                    'Запрещено менять ISBN код у существующих книг' 
                ));
            }
        
            return $result;
        }
        

Чтобы узнать, в каком поле произошла ошибка, используйте объект Entity\FieldError. Если ошибка касается нескольких полей или всей записи, используйте Entity\EntityError.

public static function onBeforeUpdate(Entity\Event $event)
        {
            $result = new Entity\EventResult;
            $data = $event->getParameter("fields");
        
            if (...)
            { // Здесь должна быть ваша логика комплексной проверки данных
                $result->addError(new Entity\EntityError(
                    'Невозможно обновить запись' 
                ));
            }
        
            return $result;
        }
        

Форматирование значений

Иногда нужно хранить данные в одном формате, а работать с ними — в другом. Часто это касается массивов, которые сериализуются перед сохранением в БД, то есть преобразуются в строку. Для этого есть параметры поля save_data_modification и fetch_data_modification, которые задаются через callback.

Пример каталога книг, где поле EDITIONS_ISBN будет хранить коды ISBN других изданий книги.

new Entity\TextField('EDITIONS_ISBN', [
            'save_data_modification' => function ()
            {
                return [
                    function ($value)
                    {
                        return serialize($value); // Преобразует значение в сериализованную строку перед сохранением
                    }
                ];
            },
            'fetch_data_modification' => function ()
            {
                return [
                    function ($value)
                    {
                        return unserialize($value); // Преобразует сериализованную строку обратно в значение при извлечении
                    }
                ];
            }
        ])
        

Для сериализации используйте параметр serialized.

new Entity\TextField('EDITIONS_ISBN', [
            'serialized' => true // Автоматически сериализует и десериализует данные
        ])
        

Вычисляемые значения

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

Для безопасного обновления данных в базе данных используйте плейсхолдеры. Они помогают избежать SQL-инъекций, экранируя значения и идентификаторы. Список доступных плейсхолдеров:

  • ? или ?s — значение экранируется и заключается в кавычки ' ,

  • ?# — значение экранируется как идентификатор,

  • ?i — значение приводится к integer,

  • ?f — значение приводится к float.

Пример использования вычисляемых значений

Чтобы увеличить количество читателей READERS_COUNT на 1, можно обновить соответствующее поле в базе данных.

BookTable::update($id, [ // Обновление записи в таблице BookTable
            'READERS_COUNT' => new DB\SqlEx * pression('?# + 1', 'READERS_COUNT') // Увеличение значения поля READERS_COUNT на 1
        ]);
        

В этом примере плейсхолдер ?# указывает на идентификатор базы данных, который будет экранирован.

Если число читателей переменное, лучше описать выражение так.

// правильно
        BookTable::update($id, [ // Обновление записи в таблице BookTable
            'READERS_COUNT' => new DB\SqlEx * pression('?# + ?i', 'READERS_COUNT', $readersCount) // Увеличение значения поля READERS_COUNT на значение переменной $readersCount
            // '?#' - плейсхолдер для имени поля, заменяется на 'READERS_COUNT'
            // '?i' - плейсхолдер для целочисленного значения, заменяется на $readersCount
        ]);
        
        // неправильно
        BookTable::update($id, [ // Обновление записи в таблице BookTable
            'READERS_COUNT' => new DB\SqlEx * pression('?# + '.$readersCount, 'READERS_COUNT') // Небезопасное увеличение значения поля READERS_COUNT
            // Отсутствие плейсхолдера для значения, что может привести к SQL-инъекциям
        ]);
        

Здесь ?# заменяется на имя поля READERS_COUNT, а ?i — на значение переменной $readersCount.

Предупреждения об ошибках

Вызывать методы можно как с проверкой успешности выполнения запроса, так и без проверки.

В режиме агента рекомендуем проверять результат выполнения операций с помощью $result->isSuccess() и логировать ошибки. Если запрос не прошел из-за валидации и не была вызвана проверка isSuccess(), система сгенерирует E_USER_WARNING. В сообщении будут перечислены все ошибки.

// Вызов с проверкой успешности выполнения запроса
        $result = BookTable::update(...); // Выполнение обновления и сохранение результата
        if (!$result->isSuccess())
        { // Проверка успешности выполнения
            // обработка ошибки
            // Здесь можно добавить код для обработки ошибок, например, логирование или уведомление пользователя
        }
        
        // Вызов без проверки успешности выполнения запроса
        BookTable::update(...); // Обновление записи без проверки результата
        

Пример создания сущности

Создадим класс BookTable, который представляет таблицу для хранения информации о книгах. В классе зададим поля таблицы, их типы и правила валидации. С помощью событий настроим обработку данных перед их добавлением в базу.

namespace SomePartner\MyBooksCatalog; // Определение пространства имен для организации кода
        use Bitrix\Main\Entity; // Импорт класса Entity для работы с ORM
        use Bitrix\Main\Type; // Импорт класса Type для работы с типами данных
        
        class BookTable extends Entity\DataManager // Класс BookTable наследует Entity\DataManager для работы с данными
        {
            // Метод для получения имени таблицы
            public static function getTableName()
            {
                return 'my_book'; // Имя таблицы в базе данных
            }
        
            // Метод для получения уникального идентификатора пользовательских полей
            public static function getUfId()
            {
                return 'MY_BOOK'; // Уникальный идентификатор
            }
        
            // Метод для определения карты полей таблицы
            public static function getMap()
            {
                return array(
                    new Entity\IntegerField('ID', array( // Поле ID
                        'primary' => true, // Указание, что это первичный ключ
                        'autocomplete' => true // Автоинкремент для поля
                    )),
                    new Entity\StringField('ISBN', array( // Поле ISBN
                        'required' => true, // Поле обязательно для заполнения
                        'column_name' => 'ISBNCODE', // Имя столбца в базе данных
                        'validation' => function()
                        { // Валидация поля
                            return array(
                                new Entity\Validator\RegExp('/\d{13}/'), // Проверка на 13 цифр
                                function ($value, $primary, $row, $field)
                                { // Дополнительная проверка
                                    // Проверка контрольной цифры ISBN
                                    return new Entity\FieldError(
                                        $field, 'Контрольная цифра ISBN не сошлась', 'MY_ISBN_CHECKSUM'
                                    ); // Возврат ошибки, если проверка не пройдена
                                }
                            );
                        }
                    )),
                    new Entity\StringField('TITLE'), // Поле для названия книги
                    new Entity\DateField('PUBLISH_DATE', array( // Поле для даты публикации
                        'default_value' => function ()
                        { // Установка значения по умолчанию
                            $lastFriday = date('Y-m-d', strtotime('last friday')); // Вычисление даты последней пятницы
                            return new Type\Date($lastFriday, 'Y-m-d'); // Возврат даты в нужном формате
                        }
                    )),
                    new Entity\TextField('EDITIONS_ISBN', array( // Поле для хранения сериализованных данных
                        'serialized' => true // Автоматическая сериализация и десериализация
                    )),
                    new Entity\IntegerField('READERS_COUNT') // Поле для количества читателей
                );
            }
        
            // Событие, вызываемое перед добавлением новой записи
            public static function onBeforeAdd(Entity\Event $event)
            {
                $result = new Entity\EventResult; // Создание объекта для результата события
                $data = $event->getParameter("fields"); // Получение данных полей из события
                if (isset($data['ISBN'])) // Проверка наличия поля ISBN
                {
                    $cleanIsbn = str_replace('-', '', $data['ISBN']); // Удаление дефисов из ISBN
                    $result->modifyFields(array('ISBN' => $cleanIsbn)); // Модификация поля ISBN
                }
                return $result; // Возврат результата события
            }
        }
        
Предыдущая
Следующая