Создание сущностей ORM [book rental]

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

Говоря слово «сущность», я подразумеваю некий объект с определенным набором свойств, связей и событий. Я предпочитаю не использовать инфоблоки и смарт процессы для решения подобного класса задач, так как считаю этот функционал немного избыточным и не всегда уместным. Меня вполне устраивают обычные таблицы и DataManager, позволяющий выполнять основные операции.

Приступаем к работе.

Шаг 1 Поля сущности

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

Получаем схему следующего вида:

НазваниеКодТипОбязательноПримечание
ИдентификаторIDчислодапервичный ключ
НазваниеNAMEстрокада
Год изданияPUBLISHING_YEARчислода
ИздательствоPUBLISHING_HOUSEстрокада
АвторAUTHORстрокада
ЖанрGENREстроканет
ОписаниеDESCRIPTIONтекстнет
ОбложкаBOOK_COVER_IDчислонетидентификатор файла

Сразу можно обратить внимание на то, что некоторые поля являются справочными, а некоторые можно вынести в отдельные сущности. Например, Издательство и авторы вполне могут содержать какую-то дополнительную информацию, а жанры — это самый что ни на есть справочник. Всё это мы обязательно разделим и сделаем, но позже. Сейчас я показываю как создать сущность на простеньком примере.

Шаг 2 Описание сущности

В минимальном представлении, описание сущности состоит из двух методов, которые возвращают название таблицы в базе данных

    public static function getTableName()
    {
        return 'table_name';
    }

и список полей сущности

    public static function getMap()
    {
        return [
            // ... поля ...
        ];
    }

Каждое поле является экземпляром класса конкретного типа, наследуемого от базового класса Bitrix\Main\Entity\Field. У объектов данных типов есть методы, которые устанавливают аттрибуты полей

  • configurePrimary() — первичный ключ
  • configureAutocomplete() — автоинкремент
  • configureRequired() — обязательность
  • и другие (со всеми методами можно ознакомиться в классе Bitrix\Main\ORM\Fields\ScalarField)

Класс самой сущности должен заканчиваться суффиксом Table и является наследником датаменеджера

class EditionTable extends \Bitrix\Main\Entity\DataManager
{
	//...
}

Для начала этих знаний хватит чтобы собрать класс нашей сущности. Где и как я предпочитаю размещать классы я рассказывал в этой статье.

<?php
  
namespace Aclips\BookRental\Internal;

use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields\IntegerField;
use Bitrix\Main\ORM\Fields\StringField;
use Bitrix\Main\ORM\Fields\TextField;

class EditionTable extends DataManager
{
    public static function getTableName()
    {
        return "a_bookrental_editions";
    }

    public static function getMap()
    {
        return [
            (new IntegerField('ID'))
                ->configurePrimary()
                ->configureAutocomplete(),

            (new StringField('NAME'))
                ->configureRequired(),

            (new IntegerField('PUBLISHING_YEAR'))
                ->configureRequired(),

            (new StringField('PUBLISHING_HOUSE'))
                ->configureRequired(),

            (new StringField('AUTHOR'))
                ->configureRequired(),

            (new StringField('GENRE')),

            (new TextField('DESCRIPTION')),

            (new IntegerField('BOOK_COVER_ID')),
        ];
    }
}   

Шаг 3 Создание таблицы в базе данных

Здесь всё просто. Для создания таблицы в бд нужно вызвать метод createDBTable на объекте сущности.

$entity = \Aclips\BookRental\Internal\EditionTable::getEntity();
$entity->createDBTable();

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

<?php
  
/**
 * Консольная утилита для проверки существования и создание таблиц сущностей в бд
 *
 * Пример запуска:
 *      /usr/bin/php -f ~/www/local/php_interface/install/db_tables.php
 **/

if (php_sapi_name() != 'cli') {
    die();
}

// Абсолютный путь к корневой директории проекта
$_SERVER['DOCUMENT_ROOT'] = '/home/bitrix/www';

// Отключение ненужных проверок и действий
define("NO_KEEP_STATISTIC", true);
define("NOT_CHECK_PERMISSIONS", true);
define("NEED_AUTH", true);

require_once($_SERVER['DOCUMENT_ROOT'] . "/bitrix/modules/main/include/prolog_before.php");

// Классы сущностей проекта для создания таблиц в бд
$dataManagerClasses = [
    \Aclips\BookRental\Internal\EditionTable::class,
    // ...
];



foreach ($dataManagerClasses as $dataManagerClass) {
    try {
        // Получение объекта сущности
        $entity = $dataManagerClass::getEntity();

        // Получение названия таблицы бд
        $tableName = $entity->getDBTableName();

        // Проверка на отсутствие таблицы в бд
        if (!$connection->isTableExists($tableName)) {
            // Создание бд
            $result = $entity->createDBTable();
            // log: Table $tableName created successfully
        } else {
            // log: Table $tableName already exists
        }
    } catch (\Throwable $e) {
        // log: Exception $e->getMessage()
    }
}

Запуск скрипта

/usr/bin/php -f ~/www/local/php_interface/install/db_tables.php

При желании можно добавить логирование результата выполнения или отображение в консоли

fwrite(STDOUT, 'Вывод сообщения в консоль'.PHP_EOL);

Бонус

Для ускорения описания полей сущностей я сделал небольшой (надеюсь) интуитивно понятный сервис. На момент написания статьи в нём нельзя указать реляции и доступны только базовые типы полей, тем не менее позволяет экономить время. Да, bitrix24 позволяет генерировать классы сущностей из коробки, но меня это не устраивает по двум причинам, так как для этого в базе данных уже должна существовать таблица, а создавать её sql запросом или через другие инструменты я не хочу.

Бонус временно недоступен 🙂

Думаю, что для начала этой информации достаточно для понимания общей концепции и принципа работы. В следующей статье я продолжу работу над проектом, создам недостающие сущности и расскажу про работу с сущностями через EntityManager.

2 комментария

  1. Алексей:

    Привет!
    А почем вы именно таким способом создаете таблицы в БД, а не упаковываете в модуль — как предписывает битрикс?
    Как быть в том случае — если понадобиться удалить таблицу из БД?
    В случае использования модуля — в файле local/modules/vendor.module_name/install/index.php — используются специальные методы.

    1. Привет, Алексей.

      У меня в основном речь идёт про разработку проекта. Вероятность того, что вы будете удалять таблицу из согласованного проекта очень мала. Если говорить про модуль, то вы правы — следует предусматривать возможность удаления таблиц (разумеется, при подтверждении этого действия пользователем).

      Что касается использования специального метода, не совсем понятно о каком именно идёт речь:
      Если вы про InstallDB, то это просто «интерфейс», который может быть реализован как угодно (хоть прямым запросом в БД, хоть описанным в статье методом).

      Например в некоторых модулях происходит следующее:
      Отдельно кладётся файл с sql запросом и далее вызывается метод RunSQLBatch на экземпляре подключения к базе данных. Чем мне нравится такой подход? Собрали SQL запрос и забыли. Чем мне не нравится такой подход? Он не гарантирует описание модели (от DataManager), т.е. нам придётся сперва получить запрос, который точно будет «совместим» с нашей моделью, сохранить его в файл и только потом его выполнить.

Добавить комментарий