Custom ORM Model (bookTable) - uniqcle/Bitrix GitHub Wiki

Entity Intro

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

Type's Fields
DataField       // Дата
 DatetimeField  // Дата и время
BooleanField    // true/false
IntegerField    // Целое число
FloatField      // Число
EnumField	// Значение из списка
StringField     // Строка
 TextField      // Текст

SqlExpression

$result = BookTable::update(1, array(
            'NAME' => 'Книга для теста измененная1',
            //'WRITE_COUNT' => new \Bitrix\Main\DB\SqlExpression('?# +1', 'WRITE_COUNT')
            // 'WRITE_COUNT' => new \Bitrix\Main\DB\SqlExpression( 'NOW()' )

        ));
1. Creating Entity Class (BookTable)

local/modules/uniqcle.d7/lib/book.php

namespace Uniqcle\D7;

use \Bitrix\Main\Entity;
use \Bitrix\Main\Type;

class BookTable extends Entity\DataManager
{
    public static function getTableName(){
        return 'b_book';
    }

    // Возвращает имя подключения к БД
    /*public static function getConnectionName(){
        return 'localhost';
    }*/

    public static function getMap(){
        return array(
            //ID
            new Entity\IntegerField('ID', array(
                'primary' => true,
                'autocomplete' => true
            )),
            // Название
            new Entity\StringField('NAME', array(
                'required' => true
            )),
            // Год выхода
            new Entity\IntegerField('RELEASED', array(
                'required' => true
            )),
            // ISBN
            new Entity\StringField('ISBN', array(
                'required' => true,
                'column_name' => 'ISBNCODE',
                'validation' => function() {
                    return array(
                        new Entity\Validator\Unique,

                        function ($value, $primary, $row, $field) {
                            // value - значение поля
                            // primary - массив с первичным ключом, в данном случае [ID => 1]
                            // row - весь массив данных, переданный в ::add или ::update
                            // field - объект валидируемого поля - Entity\StringField('ISBN', ...)

                            $clean = str_replace(array('-',' '), '', $value);

                            if (preg_match('/^\d{1,13}$/', $clean)){
                                return true;
                            } else {
                                return 'Код ISBN должен содержать не более 13 цифр, разделенных дефисом или пробелами';
                            }
                        }
                    );
                }
            )),

            // ФИО Автора
            new Entity\StringField('AUTHOR'),
            // Дата и время поступления книги в магазин
            new Entity\DatetimeField('TIME_ARRIVAL', array(
                'required' => true,
                'default_value' => new Type\DateTime
            )),
            // Описание книги
            new Entity\TextField('DESCRIPTION'),
            // Сколько лет книги
            new Entity\ExpressionField('AGE_YEAR',
                'YEAR(CURDATE())-%s', array('RELEASED')
            )
        );
    }

    // Регистрация обработчика при установке модуля
    public static function onBeforeUpdate(Entity\Event $event)
    {
        $result = new Entity\EventResult;
        $data = $event->getParameter("fields");

        if (isset($data['ISBN'])) {
            $result->addError(new Entity\FieldError(
                $event->getEntity()->getField('ISBN'),
                'Запрещено менять ISBN код у существующих книг'
            ));
        }

        return $result;
    }
}
2. Creating DB Table/ Drop Table

local/modules/uniqcle.d7/install/index.php

...
    function InstallDB($install_wizard = true){
        Loader::includeModule($this->MODULE_ID);

        //Проверяем существует ли таблица в БД
        if(!Application::getConnection()->                                     // В качестве параметра может быть \Academy\D7\BookTable::getConnectionName()
            isTableExists( Base::getInstance('\Uniqcle\D7\BookTable')->getDBTableName() )){

            Base::getInstance('\Uniqcle\D7\BookTable')->createDbTable(); // При описании ORM-сущности, создает таблицу в БД
            }
    }

    function UnInstallDB($arParams = Array()){

        Loader::includeModule($this->MODULE_ID);

        Application::getConnection()->   // В качестве параметра \Academy\D7\BookTable::getConnectionName()
        queryExecute('drop table if exists '.Base::getInstance('\Uniqcle\D7\BookTable')->getDBTableName());

        Option::delete($this->MODULE_ID);
    }
...

add, update, delete operations

add()

function var1()
    {
        $result = BookTable::add(array(
            'NAME' => 'Название книги',
            'RELEASED' => '2020',
            'ISBN' => '978-0321127426',
            'AUTHOR' => 'ФИО',
            'TIME_ARRIVAL' => new Type\DateTime('04.09.2015 00:00:00'),
            'DESCRIPTION' => 'Описание книги'
        ));

        return $result;
    }
\Uniqcle\D7\BookTable::add()
use \Bitrix\Main;
use \Bitrix\Main\Localization\Loc;
use \Bitrix\Main\Type;
use \Uniqcle\D7\BookTable;

class d7OrmAdd extends CBitrixComponent
{

    /**
     * проверяет подключение необходиимых модулей
     * @throws LoaderException
     */
    protected function checkModules()
    {
        if (!Main\Loader::includeModule('uniqcle.d7'))
            throw new Main\LoaderException(Loc::getMessage('UNIQCLE_D7_MODULE_NOT_INSTALLED'));
    }

    //Корректное добавление записи
    function var1()
    {
        $result = BookTable::add(array(
            'NAME' => 'Книга для теста',
            'RELEASED' => '2002',
            'ISBN' => '978-0321127426',
            'AUTHOR' => 'Сергей Покоев',
            'TIME_ARRIVAL' => new Type\DateTime('04.09.2015 00:00:00'),
            'DESCRIPTION' => 'тестовый текст
            вторая строчка'
        ));

        return $result;
    }

    //Добавление записи без обязательного поля "Название".
    function var2()
    {
        $result = BookTable::add(array(
            'RELEASED' => '2002',
            'ISBN' => '978-0321127426',
            'AUTHOR' => 'Сергей Покоев',
            'TIME_ARRIVAL' => new Type\DateTime('04.09.2015 00:00:00'),
            'DESCRIPTION' => 'тестовый текст
            вторая строчка'
        ));

        return $result;
    }

    //Добавление записи без указания поля, для которого установлено значение по умолчанию
    function var3()
    {
        $result = BookTable::add(array(
            'NAME' => 'Книга для теста',
            'RELEASED' => '2002',
            'ISBN' => '978-0321127426',
            'AUTHOR' => 'Сергей Покоев',
            'DESCRIPTION' => 'тестовый текст
            вторая строчка'
        ));

        return $result;
    }

    public function executeComponent()
    {
        $this -> includeComponentLang('class.php');

        $this -> checkModules();

        //все верно
        $result = $this->var1();

        //Не указал обязательное поле: название
        //$result = $this->var2();

        //Добавление используя поле по умолчанию.
        //$result = $this->var3();

        if ($result->isSuccess())
        {
            $id = $result->getId();
            $this->arResult='Запись добавлена с id: '.$id;
        }
        else
        {
            $error=$result->getErrorMessages();
            $this->arResult='Произошла ошибка при добавлении: <pre>'.var_export($error,true).'</pre>';
        }

        $this->includeComponentTemplate();
    }
}

update()

  //Обновление записи. Обновляется только название. (нужно указать верный id)
  $result = BookTable::update(1, array(
     'NAME' => 'Книга для теста измененная',
  ));
\Uniqcle\D7\BookTable::update()
use \Bitrix\Main;
use \Bitrix\Main\Localization\Loc;
use \Bitrix\Main\Type;
use \Uniqcle\D7\BookTable;

class d7OrmUpdate extends CBitrixComponent
{

    /**
     * проверяет подключение необходиимых модулей
     * @throws LoaderException
     */
    protected function checkModules()
    {
        if (!Main\Loader::includeModule('uniqcle.d7'))
            throw new Main\LoaderException(Loc::getMessage('UNIQCLE_D7_MODULE_NOT_INSTALLED'));
    }

    function var1()
    {
        //Обновление записи. Обновляется только название. (нужно указать верный id)
        $result = BookTable::update(1, array(
            'NAME' => 'Книга для теста измененная',
        ));

        return $result;
    }


    public function executeComponent()
    {
        $this -> includeComponentLang('class.php');

        $this -> checkModules();

        $result = $this->var1();

        if ($result->isSuccess())
        {
            $id = $result->getId();
            $this->arResult='Запись изменена с id: '.$id;
        }
        else
        {
            $error=$result->getErrorMessages();
            $this->arResult='Произошла ошибка при изменении: <pre>'.var_export($error,true).'</pre>';
        }

        $this->includeComponentTemplate();
    }
}

delete()

  //Удаление записи (нужно указать верный id)
  return BookTable::delete(1);
\Uniqcle\D7\BookTable::delete()
use \Bitrix\Main;
use \Bitrix\Main\Localization\Loc;
use \Bitrix\Main\Type;
use \Uniqcle\D7\BookTable;

class d7OrmDelete extends CBitrixComponent
{

    /**
     * проверяет подключение необходиимых модулей
     * @throws LoaderException
     */
    protected function checkModules()
    {
        if (!Main\Loader::includeModule('uniqcle.d7'))
            throw new Main\LoaderException(Loc::getMessage('UNIQCLE_D7_MODULE_NOT_INSTALLED'));
    }

    function var1()
    {
        //Удаление записи (нужно указать верный id)
        $result = BookTable::delete(1);

        return $result;
    }


    public function executeComponent()
    {
        $this -> includeComponentLang('class.php');

        $this -> checkModules();

        $result = $this->var1();

        if ($result->isSuccess())
        {
            // Не можем узнать id удаленного элемента
            $this->arResult='Запись была удалена';
        }
        else
        {
            $error=$result->getErrorMessages();
            $this->arResult='Произошла ошибка при удалении: <pre>'.var_export($error,true).'</pre>';
        }

        $this->includeComponentTemplate();
    }
}

getList

BookTable::getList(array(
    'select'  => ... // имена полей, которые необходимо получить в результате
    'filter'  => ... // описание фильтра для WHERE и HAVING
    'group'   => ... // явное указание полей, по которым нужно группировать результат
    'order'   => ... // параметры сортировки
    'limit'   => ... // количество записей
    'offset'  => ... // смещение для limit
    'runtime' => ... // динамически определенные поля
));
Выборка getList из BookTable
use \Bitrix\Main;
use \Bitrix\Main\Localization\Loc;
use \Bitrix\Main\Type;
use \Uniqcle\D7\BookTable;

class d7OrmGetlist extends CBitrixComponent
{

    /**
     * проверяет подключение необходиимых модулей
     * @throws LoaderException
     */
    protected function checkModules()
    {
        if (!Main\Loader::includeModule('uniqcle.d7'))
            throw new Main\LoaderException(Loc::getMessage('ACADEMY_D7_MODULE_NOT_INSTALLED'));
    }

    function var1()
    {
        $result = BookTable::getList(array(
            'select'  => array('ID','NAME_BOOK' =>'NAME', 'AUTHOR'), // имена полей, которые необходимо получить в результате
            'filter'  => array(), // описание фильтра для WHERE и HAVING
            //'group'   => array(), // явное указание полей, по которым нужно группировать результат
            'order'   => array('ID'=>'DESC'), // параметры сортировки
            'limit'   => 3, // количество записей
            'offset'  => 2, // смещение для limit
        ));

        return $result;
    }


    public function executeComponent()
    {
        $this -> includeComponentLang('class.php');

        $this -> checkModules();

        $result = $this->var1();

        //Вариант 1 получения данных
        /*while ($row = $result->fetch())
        {
            $this -> arResult[] = $row;
        }*/

        //Вариант 2 получения данных
        $this -> arResult = $result->fetchAll();

        $this->includeComponentTemplate();
    }
}

runtime динамически определяемое поле

Выборка с добавлением runtime
use \Bitrix\Main;
use \Bitrix\Main\Localization\Loc;
use \Bitrix\Main\Type;
use \Uniqcle\D7\BookTable;

class d7OrmGetlistExpression extends CBitrixComponent
{

    /**
     * проверяет подключение необходиимых модулей
     * @throws LoaderException
     */
    protected function checkModules()
    {
        if (!Main\Loader::includeModule('uniqcle.d7'))
            throw new Main\LoaderException(Loc::getMessage('UNIQCLE_D7_MODULE_NOT_INSTALLED'));
    }

    // Динамический подсчет элементов и занесение в CNT
    function var1()
    {
        $result = BookTable::getList(array(
            'select' => array('CNT'),
            'runtime' => array(
                new Main\Entity\ExpressionField('CNT', 'COUNT(*)')
            ),
        ));

        return $result->fetch();
    }
    // Подсчет кол-ва элементов. Более короткая запись
    function var2()
    {
        $result = BookTable::getList(array(
            'select' => array(
                new Main\Entity\ExpressionField('CNT', 'COUNT(*)')
            ),
        ));

        return $result->fetch();
    }
    // К сущености добавляется поле, как будтно оно было описано изначально в getMap().
    // Добавить нужно в БД.
    function var3()
    {
        $result = BookTable::getList(array(
            'select' => array(
                'ID','NAME', 'ACTIVITY'
            ),
            'filter'  => array('ACTIVITY' => 1),
            'runtime' => array(
                new Main\Entity\IntegerField('ACTIVITY'),
            )
        ));

        return $result->fetchAll();
    }

    public function executeComponent()
    {
        $this -> includeComponentLang('class.php');

        $this -> checkModules();

        //$this -> arResult = $this->var1();

        //$this -> arResult = $this->var2();

        $this -> arResult = $this->var3();

        $this->includeComponentTemplate();
    }
}

еще пример

$resOrder = Bitrix\Sale\OrderTable::getList(
    array(
        'filter' => array('=USER_ID' => 1),
        'group' => array('PAYED'),
        'runtime' => array(          
            new Bitrix\Main\Entity\ExpressionField('IDS', 'GROUP_CONCAT(%s)', array('ID')),
        ),
    )
);

Standart Tables/Methods

Standart Methods

$dbItems->fetch();                 // или $dbItems->fetchRaw() получение одной записи, можно перебрать в цикле while ($arItem = $dbItems->fetch())
$dbItems->fetchAll();             // получение всех записей
$dbItems->getCount();             // кол-во найденных записей без учета limit, доступно если при запросе было указано count_total = 1
$dbItems->getSelectedRowsCount(); // кол-во полученных записей с учетом limit

Short Record

getById($id) выборка по первичному ключу

return BookTable::getById(7);

// или так
$result = BookTable::getList(array(
    'filter' => array('=ID' => $id)
));

$row = $result->fetch();
getById($id)
use \Bitrix\Main;
use \Bitrix\Main\Localization\Loc;
use \Bitrix\Main\Type;
use \Uniqcle\D7\BookTable;

class d7OrmGetbyid extends CBitrixComponent
{

    /**
     * проверяет подключение необходиимых модулей
     * @throws LoaderException
     */
    protected function checkModules()
    {
        if (!Main\Loader::includeModule('uniqcle.d7'))
            throw new Main\LoaderException(Loc::getMessage('ACADEMY_D7_MODULE_NOT_INSTALLED'));
    }

    function var1()
    {
        // Выборка записи по id
        return BookTable::getById(7);
    }

    public function executeComponent()
    {
        $this -> includeComponentLang('class.php');

        $this -> checkModules();

        $this -> arResult = $this->var1()->fetch();

        $this->includeComponentTemplate();
    }
}

getByPrimary(array('ID' => $id)) в обоих методах мы можем как передать id в виде числа, так и явно указать какой элемент является ключом, передав массив. Массив необходимо использовать, если у вас есть несколько primary полей. Если вы передаете в массиве элемент, который не является первичным ключом, то это будет ошибкой.

// Аналогичен выборке по id, но может выбирать динамические поля
return BookTable::getByPrimary(array('ID' => 7));

// аналогичны следующему вызову getList:
BookTable::getList(array(
    'filter' => array('=ID' => 7)
));
getByPrimary($id)
use \Bitrix\Main;
use \Bitrix\Main\Localization\Loc;
use \Bitrix\Main\Type;
use \Uniqcle\D7\BookTable;

class d7OrmGetbyid extends CBitrixComponent
{

    /**
     * проверяет подключение необходиимых модулей
     * @throws LoaderException
     */
    protected function checkModules()
    {
        if (!Main\Loader::includeModule('uniqcle.d7'))
            throw new Main\LoaderException(Loc::getMessage('ACADEMY_D7_MODULE_NOT_INSTALLED'));
    }

    function var1()
    {
        // Аналогичен выборке по id, но может выбирать динамические поля
        return BookTable::getByPrimary(array('ID' => 7));
    }

    public function executeComponent()
    {
        $this -> includeComponentLang('class.php');

        $this -> checkModules();

        $this -> arResult = $this->var1()->fetch();

        $this->includeComponentTemplate();
    }
}

getRowById($id) Возвращает массив

$row = BookTable::getRowById($id);

// аналогичный результат можно получить так:
$result = BookTable::getById($id);
$row = $result->fetch();

// или так
$result = BookTable::getList(array(
    'filter' => array('=ID' => $id)
));

$row = $result->fetch();
getRowById($id)
use \Bitrix\Main;
use \Bitrix\Main\Localization\Loc;
use \Bitrix\Main\Type;
use \Uniqcle\D7\BookTable;

class d7OrmGetbyid extends CBitrixComponent
{

    /**
     * проверяет подключение необходиимых модулей
     * @throws LoaderException
     */
    protected function checkModules()
    {
        if (!Main\Loader::includeModule('uniqcle.d7'))
            throw new Main\LoaderException(Loc::getMessage('ACADEMY_D7_MODULE_NOT_INSTALLED'));
    }

    function var1()
    {
        // Выборка записи по id, возвращаем массив. fetch можно не использовать
        return BookTable::getRowById(7);
    }

    public function executeComponent()
    {
        $this -> includeComponentLang('class.php');

        $this -> checkModules();

        $this -> arResult = $this->var1();

        $this->includeComponentTemplate();
    }
}

getRow(); производит выборку не по первичному ключу, а по каким-то другим параметрам. При этом возвращается только одна запись.

return BookTable::getRow(array(
            'filter' => array('%=ISBN' => '978-032112261'),
            'order' => array('ID')
        ));

// аналогичный результат можно получить так:
$result = BookTable::getList(array(
    'filter' => array('%=TITLE' => 'Patterns%'),
    'order' => array('ID')
    'limit' => 1
));
$row = $result->fetch();
getRow()
use \Bitrix\Main;
use \Bitrix\Main\Localization\Loc;
use \Bitrix\Main\Type;
use \Uniqcle\D7\BookTable;

class d7OrmGetbyid extends CBitrixComponent
{

    /**
     * проверяет подключение необходиимых модулей
     * @throws LoaderException
     */
    protected function checkModules()
    {
        if (!Main\Loader::includeModule('uniqcle.d7'))
            throw new Main\LoaderException(Loc::getMessage('ACADEMY_D7_MODULE_NOT_INSTALLED'));
    }

    function var1()
    {
        // При этом возвращается только одна запись- массив
        return BookTable::getRow(array(
            'filter' => array('%=ISBN' => '978-032112261'),
            'order' => array('ID')
        ));
    }

    public function executeComponent()
    {
        $this -> includeComponentLang('class.php');

        $this -> checkModules();

        $this -> arResult = $this->var1();

        $this->includeComponentTemplate();
    }
}

Query

Удобно если необходимо собрать запрос из разных частей.

Список методов Bitrix\Main\Entity\Query
setSelect(), setGroup() — устанавливает массив с именами полей
addSelect(), addGroup() — добавляет имя поля
getSelect(), getGroup() — возвращает массив с именами полей

setFilter() — устанавливает одно- или многомерный массив с описанием фильтра
addFilter() — добавляет один параметр фильтра со значением
getFilter() — возвращает текущее описание фильтра

setOrder() — устанавливает массив с именами полей и порядком сортировки
addOrder() — добавляет одно поле с порядком сортировки
getOrder() — возвращает текущее описание сортировки

setLimit(), setOffset() — устанавливает значение
getLimit(), getOffset() — возвращает текущее значение
registerRuntimeField() — регистрирует новое временное поле для исходной сущности
        $q = new Main\Entity\Query(BookTable::getEntity()); // Экземпляр класса query. В конструктор эзекмпляр класса ORM сущности

        $q->setSelect(array(                                // поля выборки
            'ID',
            'NAME_BOOK' =>'NAME',
            'AGE_YEAR',
            'WRITE_COUNT')
        );

        $q->setFilter(array(                               // описание фильтра для WHERE и HAVING
            'WRITE_COUNT' => 0
            )
        );

        $q->setOrder(array('ID'=>'DESC')); // параметры сортировки
        $q->setLimit(3);                   // количество записей
        $q->setOffset(2);                  // смещение для limit
        $result = $q->exec();

        return $result->fetchAll();
Main\Entity\Query
use \Bitrix\Main;
use \Bitrix\Main\Localization\Loc;
use \Bitrix\Main\Type;
use \Uniqcle\D7\BookTable;

class d7OrmQuery extends CBitrixComponent
{

    /**
     * проверяет подключение необходиимых модулей
     * @throws LoaderException
     */
    protected function checkModules()
    {
        if (!Main\Loader::includeModule('uniqcle.d7'))
            throw new Main\LoaderException(Loc::getMessage('ACADEMY_D7_MODULE_NOT_INSTALLED'));
    }

    function var1()
    {
        $q = new Main\Entity\Query(BookTable::getEntity()); // Экземпляр класса query. В конструктор эзекмпляр класса ORM сущности

        $q->setSelect(array(                                // поля выборки
            'ID',
            'NAME_BOOK' =>'NAME',
            'WRITE_COUNT')
        );

        $q->setFilter(array(                               // описание фильтра для WHERE и HAVING
            'WRITE_COUNT' => 0
            )
        );

        $q->setOrder(array('ID'=>'DESC')); // параметры сортировки
        $q->setLimit(3);                   // количество записей
        $q->setOffset(2);                  // смещение для limit
        $result = $q->exec();

        return $result->fetchAll();
    }


    public function executeComponent()
    {
        $this -> includeComponentLang('class.php');

        $this -> checkModules();

        $this -> arResult = $this->var1();

        $this->includeComponentTemplate();
    }
}

Validators

В методе getMap() при создании сущности, добавляем параметр validation

standard validators

Entity\Validator\RegExp – check by regular expression,

Entity\Validator\Length – check the minimum/maximum line length,

Entity\Validator\Range – check the minimum/maximum number value,

Entity\Validator\Unique – check the uniqueness of a value.

   // ISBN
   new Entity\StringField('ISBN', array(
     'required' => true,
     'column_name' => 'ISBNCODE',
     'validation' => function() {
           return array(
              new Entity\Validator\Unique,
           );
      }
   )),

И при добавлении элемента выдает ошибку

string(203) "Произошла ошибка при добавлении: 
array (
  0 => 'Запись со значением "ISBN", равным "000000001", уже есть в базе данных',
)
Свой валидатор с возвращаемой callback-функцией
     ...
     // ISBN
     new Entity\StringField('ISBN', array(
                'required' => true,
                'column_name' => 'ISBNCODE',
                'validation' => function() {
                    return array(
                        new Entity\Validator\Unique,

                        function ($value, $primary, $row, $field) {
                            // value - значение поля
                            // primary - массив с первичным ключом, в данном случае [ID => 1]
                            // row - весь массив данных, переданный в ::add или ::update
                            // field - объект валидируемого поля - Entity\StringField('ISBN', ...)

                            $clean = str_replace(array('-',' '), '', $value);

                            if (preg_match('/^\d{1,13}$/', $clean)){
                                return true;
                            } else {
                                return 'Код ISBN должен содержать не более 13 цифр, разделенных дефисом или пробелами';
                            }
                        }
                    );
                }
            )),
...
⚠️ **GitHub.com Fallback** ⚠️