Перейти к содержанию

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

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

Для модификации данных в БД можно использовать три ключевых метода: 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 \Bitrix\Main\DB\SqlExpression('?# + 1', 'READERS_COUNT') // Увеличение значения поля READERS_COUNT на 1
]);

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

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

// правильно
BookTable::update($id, [ // Обновление записи в таблице BookTable
    'READERS_COUNT' => new \Bitrix\Main\DB\SqlExpression('?# + ?i', 'READERS_COUNT', $readersCount) // Увеличение значения поля READERS_COUNT на значение переменной $readersCount
    // '?#' - плейсхолдер для имени поля, заменяется на 'READERS_COUNT'
    // '?i' - плейсхолдер для целочисленного значения, заменяется на $readersCount
]);

// неправильно
BookTable::update($id, [ // Обновление записи в таблице BookTable
    'READERS_COUNT' => new \Bitrix\Main\DB\SqlExpression('?# + '.$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; // Возврат результата события
    }
}