Операции над сущностями через EntityManager [book rental]

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

Стандартный датаменеджер предоставляет исчерпывающий функционал для работы с сущностями, но мне не нравится, что объект сущности не является таковым в классическом смысле.

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

Шаг 1 создание сущностей

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

Рассмотрим структуру файлов компонента с которой будем работать:

- local/php_interface/classes/Aclips/BookRental/
-- Entity
---- Element.php
---- ...
- Internal
---- ElementTable.php
---- ...

В директории Entity хранятся классы сущностей;
В Internal — классы, описывающие таблицы сущностей.

Файлам из директории Internal была посвящена предыдущая статья, а вот содержимое Entity я хочу показать на примере сущности «издание»

<?php

namespace Aclips\BookRental\Entity;

/**
 * Class Edition
 * Сущность Издание
 * @package Aclips\BookRental\Entity
 */
class Edition
{
    /**
     * Идентификатор издания
     * @var int|null
     */
    public ?int $id = null;

    /**
     * Название издания
     * @var string|null
     */
    public ?string $name = null;

    /**
     * Год публикации
     * @var int|null
     */
    public ?integer $publishingYear = null;

    /**
     * Идентификатор издательства
     * @var int|null
     */
    public ?integer $publishingHouseId = null;

    /**
     * Идентификатор жанра
     * @var int|null
     */
    public ?int $genreId = null;

    /**
     * Идентификатор файла обложки
     * @var int|null
     */
    public ?int $bookCoverId = null;

    /**
     * Описание издания
     * @var string|null
     */
    public ?string $description = null;

}

Обновлённое описание EditionTable.php

<?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 IntegerField('PUBLISHING_HOUSE_ID'))
                ->configureRequired(),

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

            (new IntegerField('BOOK_COVER_ID')),

            (new TextField('DESCRIPTION')),
        ];
    }
}

Шаг 2 установка bitrix-entity-manager

В качестве менеджера управления сущностями я использую библиотеку enoffspb/bitrix-entity-manager, разработанную совместно с коллегой по цеху.

На момент написания статьи библиотеку еще не добавили в composer. Установка из github репозитория через composer выглядит следующим образом:

Файл local/php_interface/composer.json

{
    "name": "aclips/bookRental",
    "type": "project",
	"repositories": [
		{
			"type": "vcs",
			"url": "https://github.com/enoffspb/bitrix-entity-manager"
		}
	],
	"require": {
		"enoffspb/bitrix-entity-manager": "dev-main"
	},
    "require-dev": {
        "phpunit/phpunit": "^9.5"
    }
}

UPD библиотека добавлена в composer

{
    "name": "aclips/bookRental",
    "type": "project",
	"require": {
		"enoffspb/bitrix-entity-manager": "dev-main"
	},
    "require-dev": {
        "phpunit/phpunit": "^9.5"
    }
}

Устанавливаем пакеты в директории local/php_interface

composer install

или

php ./composer.phar install

в зависимости от того, как был установлен composer.

Установка через composer: пакет enoffspb/bitrix-entity-manager (версия dev-main)

Шаг 3 конфигурирование проекта

Сущности готовы, репозитории созданы, EntityManager установлен. Теперь нужно все это собрать воедино.

Чтобы не собирать конфигурационный массив и не создавать объект EntityManager в коде всякий раз, когда нам потребуется выполнить какую-либо операцию над сущностью я создам функцию, я оберну весь процесс в функцию и буду обращаться к ней.

use enoffspb\BitrixEntityManager\EntityManagerInterface;
use enoffspb\BitrixEntityManager\BitrixEntityManager;

/**
 * Получение EntityManager для работы с сущностями BookRental
 * @return BitrixEntityManager
 */
function getlEntityManager(): EntityManagerInterface
{
    $entitiesConfig = [
        \Aclips\BookRental\Entity\Edition::class => [
            'tableClass' => \Aclips\BookRental\Internal\EditionTable::class,
        ],
    ];

    $config['entitiesConfig'] = $entitiesConfig;

    return new BitrixEntityManager($config);
}

Начиная с версии 20.5.400 в bitrix24 появился сервис локатор, который идеально подходит для инициализации сервисов, в т.ч. менеджера управления сущностями.

В статье «Как я настраиваю проект после установки» я упоминал про регистрацию служб в сервис локаторе при рассмотрении структуры директории php_interface.

В нашем случае файл kernel.php примет следующий вид:

<?php

use Bitrix\Main\DI\ServiceLocator;

if (class_exists('\Bitrix\Main\DI\ServiceLocator')) {
    $serviceLocator = ServiceLocator::getInstance();

    $serviceLocator->addInstanceLazy('aclips.bookrental.entityManager', [
        'constructor' => static function () use ($serviceLocator) {

            $entitiesConfig = [
                \Aclips\BookRental\Entity\Edition::class => [
                    'tableClass' => \Aclips\BookRental\Internal\EditionTable::class,
                ],
            ];

            $config['entitiesConfig'] = $entitiesConfig;

            return new \enoffspb\BitrixEntityManager\BitrixEntityManager($config);

        }
    ]);
}

Проверим, что все в порядке на примере создания нового элемента

use Bitrix\Main\DI\ServiceLocator;
use Aclips\BookRental\Entity\Edition;

$serviceLocator = ServiceLocator::getInstance();

// Получение сервиса
$entityManager = $serviceLocator->get('aclips.bookrental.entityManager');

// Создание объекта "издание"
$edition = new Edition();

// Заполняем свойства объекта
$edition->name = 'Котенок Шмяк и большая тыква';
$edition->publishingYear = 2020;
$edition->publishingHouseId = 1;
$edition->genreId = 3;
$edition->description = 'Приключения котёнка Шмяка...';

// Сохранение в базу
if($entityManager->save($edition)) {
	// Получаем объект из репозитория
    $processRepository = $entityManager->getRepository(Edition::class);
	$smyakEdition = $processRepository->getById($edition->id);
  
  	print "Название публикации: ".$smyakEdition->name;
  	// Название публикации: Котенок Шмяк и большая тыква

}

Порядок!

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

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

  1. Алексей:

    Привет!
    Никита, а почему получается вот такая ситуация:
    Если при создании элемента в таблице использовать Сервис, как вы указываете, тогда
    У вас для имени:
    (new IntegerField(‘NAME’))
    ->configureRequired(),

    Запись имени:
    $edition->name = ‘Котенок Шмяк и большая тыква’;

    Т.е. в числовое поле пишем строку — и самое интересное — строка и записывается )))

    НО!

    Если при создании нового элемента использовать фабрику сущностей, т.е. сделать так:
    $newBook = new \Aclips\BookRental\Internal\EditionTable::createObject();
    $newBook->setTitle(‘Котенок Шмяк и большая тыква’);
    ….
    ….
    $newBook->save();

    В таком случае числовое поле запишется 0 — что верно, так как поле у нас числовое.
    Почему так?

    1. Привет.

      Почему так?

      На самом деле всё куда проще. Это опечатка в коде, конечно же должно быть (new StringField(‘NAME’)), спасибо за бдительность:)

      PS случись такая ситуация взаправду, в бд был бы сохранён 0, как и в приведённом вами примере с сохранением сущности.

  2. Алексей:

    «случись такая ситуация взаправду, в бд был бы сохранён 0, как и в приведённом вами примере с сохранением сущности.»
    К сожалению это не так.
    В БД, в случае использования BitrixEntityManager — сохраняется не 0, а строка.
    Проверьте сами.

    1. Если мы создаём таблицу в БД на основании модели, в которой указано (new IntegerField(‘NAME’)), то поле NAME будет иметь тип int(11), что не допускает сохранения текстового значения в это поле.

      Describe

      $elementWithIntName = new Edition();
      $elementWithIntName->name = 123;

      $entityManager->save($elementWithIntName);

      $elementWithStringName = new Edition();
      $elementWithStringName->name = 'Котёнок Шмяк и большая тыква';

      $entityManager->save($elementWithStringName);

      select

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