ORM в новом ядре
Реализация ORM в ядре D7 призвана абстрагировать разработчика от механики работы с таблицами на уровне запросов к БД, введя понятие сущности
и поля сущности
.
Cущность
в терминах битрикса, это таблицаПоля сущности
столбцы или ссылки на другие сущностиДатаменеджер
система управления данными
Нужно понимать, работа в ORM возможна только если есть класс описывающий таблицу, с которой нужно работать. Более подробно про сущность ORM в Битрикс можно посмотреть в статье. Класс может быть сгенерирован и находиться в ядре, а может и отсутствовать. Всё зависит от таблицы данных с которой нужно работать. Работу можно разделить на следующие этапы:
- В папочке
bitrix
пытаемся найти класс, если класс удалось найти значит продолжаем работать с файлом в котором он описан - Если в папочке
bitrix
не удалось найти класс, значит его нужно сгенерировать, как это сделать автоматически написано ниже в статье - Нужно убедиться что система видит класс, если класс не подключен его нужно подключить, есть три варианта:
через автозагрузку классов
через Composer
через модуль - Пишем необходимые запросы используя
API bitrix
и обрабатываем их
Из чего состоит класс
В классе обязательно должны быть два метода:
getMap()
перечисляются все поля таблицыgetTableName()
указывается название базы данных
Пример класса через который можно работать с таблицей b_iblock
:
/bitrix/modules/iblock/lib/element.php<?
namespace Bitrix\Iblock;
use Bitrix\Main\Localization\Loc,
Bitrix\Main\ORM\Data\DataManager,
Bitrix\Main\ORM\Fields,
Bitrix\Main\Type;
Loc::loadMessages(__FILE__);
/**
* Class IblockTable
*
* Fields:
* <ul>
* <li> ID int mandatory
* <li> TIMESTAMP_X datetime optional default current datetime
* <li> IBLOCK_TYPE_ID string(50) mandatory
* <li> LID string(2) mandatory
* <li> CODE string(50) optional
* <li> API_CODE string(50) optional
* <li> REST_ON bool ('N', 'Y') optional default 'N'
* <li> NAME string(255) mandatory
* <li> ACTIVE bool ('N', 'Y') optional default 'Y'
* <li> SORT int optional default 500
* <li> LIST_PAGE_URL string(255) optional
* <li> DETAIL_PAGE_URL string(255) optional
* <li> SECTION_PAGE_URL string(255) optional
* <li> CANONICAL_PAGE_URL string(255) optional
* <li> PICTURE int optional
* <li> DESCRIPTION text optional
* <li> DESCRIPTION_TYPE enum ('text', 'html') optional default 'text'
* <li> RSS_TTL int optional default 24
* <li> RSS_ACTIVE bool ('N', 'Y') optional default 'Y'
* <li> RSS_FILE_ACTIVE bool ('N', 'Y') optional default 'N'
* <li> RSS_FILE_LIMIT int optional
* <li> RSS_FILE_DAYS int optional
* <li> RSS_YANDEX_ACTIVE bool ('N', 'Y') optional default 'N'
* <li> XML_ID string(255) optional
* <li> TMP_ID string(40) optional
* <li> INDEX_ELEMENT bool ('N', 'Y') optional default 'Y'
* <li> INDEX_SECTION bool ('N', 'Y') optional default 'N'
* <li> WORKFLOW bool ('N', 'Y') optional default 'Y'
* <li> BIZPROC bool ('N', 'Y') optional default 'N'
* <li> SECTION_CHOOSER string(1) optional
* <li> LIST_MODE string(1) optional
* <li> RIGHTS_MODE string(1) optional
* <li> SECTION_PROPERTY string(1) optional
* <li> PROPERTY_INDEX string(1) optional
* <li> VERSION int optional default 1
* <li> LAST_CONV_ELEMENT int optional default 0
* <li> SOCNET_GROUP_ID int optional
* <li> EDIT_FILE_BEFORE string(255) optional
* <li> EDIT_FILE_AFTER string(255) optional
* <li> SECTIONS_NAME string(100) optional
* <li> SECTION_NAME string(100) optional
* <li> ELEMENTS_NAME string(100) optional
* <li> ELEMENT_NAME string(100) optional
* <li> PICTURE reference to {@link \Bitrix\File\FileTable}
* <li> IBLOCK_TYPE_ID reference to {@link \Bitrix\Iblock\IblockTypeTable}
* <li> LID reference to {@link \Bitrix\Lang\LangTable}
* <li> SOCNET_GROUP_ID reference to {@link \Bitrix\Sonet\SonetGroupTable}
* </ul>
*
* @package Bitrix\Iblock
**/
class IblockTable extends DataManager
{
/**
* Returns DB table name for entity.
*
* @return string
*/
public static function getTableName()
{
return 'b_iblock';
}
/**
* Returns entity map definition.
*
* @return array
*/
public static function getMap()
{
return [
new Fields\IntegerField(
'ID',
[
'primary' => true,
'autocomplete' => true,
'title' => Loc::getMessage('IBLOCK_ENTITY_ID_FIELD')
]
),
new Fields\DatetimeField(
'TIMESTAMP_X',
[
'default' => function()
{
return new Type\DateTime();
},
'title' => Loc::getMessage('IBLOCK_ENTITY_TIMESTAMP_X_FIELD')
]
),
new Fields\StringField(
'IBLOCK_TYPE_ID',
[
'required' => true,
'validation' => [__CLASS__, 'validateIblockTypeId'],
'title' => Loc::getMessage('IBLOCK_ENTITY_IBLOCK_TYPE_ID_FIELD')
]
),
new Fields\StringField(
'LID',
[
'required' => true,
'validation' => [__CLASS__, 'validateLid'],
'title' => Loc::getMessage('IBLOCK_ENTITY_LID_FIELD')
]
),
new Fields\StringField(
'CODE',
[
'validation' => [__CLASS__, 'validateCode'],
'title' => Loc::getMessage('IBLOCK_ENTITY_CODE_FIELD')
]
),
new Fields\StringField(
'API_CODE',
[
'validation' => [__CLASS__, 'validateApiCode'],
'title' => Loc::getMessage('IBLOCK_ENTITY_API_CODE_FIELD')
]
),
new Fields\BooleanField(
'REST_ON',
[
'values' => array('N', 'Y'),
'default' => 'N',
'title' => Loc::getMessage('IBLOCK_ENTITY_REST_ON_FIELD')
]
),
new Fields\StringField(
'NAME',
[
'required' => true,
'validation' => [__CLASS__, 'validateName'],
'title' => Loc::getMessage('IBLOCK_ENTITY_NAME_FIELD')
]
),
new Fields\BooleanField(
'ACTIVE',
[
'values' => array('N', 'Y'),
'default' => 'Y',
'title' => Loc::getMessage('IBLOCK_ENTITY_ACTIVE_FIELD')
]
),
new Fields\IntegerField(
'SORT',
[
'default' => 500,
'title' => Loc::getMessage('IBLOCK_ENTITY_SORT_FIELD')
]
),
new Fields\StringField(
'LIST_PAGE_URL',
[
'validation' => [__CLASS__, 'validateListPageUrl'],
'title' => Loc::getMessage('IBLOCK_ENTITY_LIST_PAGE_URL_FIELD')
]
),
new Fields\StringField(
'DETAIL_PAGE_URL',
[
'validation' => [__CLASS__, 'validateDetailPageUrl'],
'title' => Loc::getMessage('IBLOCK_ENTITY_DETAIL_PAGE_URL_FIELD')
]
),
new Fields\StringField(
'SECTION_PAGE_URL',
[
'validation' => [__CLASS__, 'validateSectionPageUrl'],
'title' => Loc::getMessage('IBLOCK_ENTITY_SECTION_PAGE_URL_FIELD')
]
),
new Fields\StringField(
'CANONICAL_PAGE_URL',
[
'validation' => [__CLASS__, 'validateCanonicalPageUrl'],
'title' => Loc::getMessage('IBLOCK_ENTITY_CANONICAL_PAGE_URL_FIELD')
]
),
new Fields\IntegerField(
'PICTURE',
[
'title' => Loc::getMessage('IBLOCK_ENTITY_PICTURE_FIELD')
]
),
new Fields\TextField(
'DESCRIPTION',
[
'title' => Loc::getMessage('IBLOCK_ENTITY_DESCRIPTION_FIELD')
]
),
new Fields\StringField(
'DESCRIPTION_TYPE',
[
'values' => array('text', 'html'),
'default' => 'text',
'title' => Loc::getMessage('IBLOCK_ENTITY_DESCRIPTION_TYPE_FIELD')
]
),
new Fields\IntegerField(
'RSS_TTL',
[
'default' => 24,
'title' => Loc::getMessage('IBLOCK_ENTITY_RSS_TTL_FIELD')
]
),
new Fields\BooleanField(
'RSS_ACTIVE',
[
'values' => array('N', 'Y'),
'default' => 'Y',
'title' => Loc::getMessage('IBLOCK_ENTITY_RSS_ACTIVE_FIELD')
]
),
new Fields\BooleanField(
'RSS_FILE_ACTIVE',
[
'values' => array('N', 'Y'),
'default' => 'N',
'title' => Loc::getMessage('IBLOCK_ENTITY_RSS_FILE_ACTIVE_FIELD')
]
),
new Fields\IntegerField(
'RSS_FILE_LIMIT',
[
'title' => Loc::getMessage('IBLOCK_ENTITY_RSS_FILE_LIMIT_FIELD')
]
),
new Fields\IntegerField(
'RSS_FILE_DAYS',
[
'title' => Loc::getMessage('IBLOCK_ENTITY_RSS_FILE_DAYS_FIELD')
]
),
new Fields\BooleanField(
'RSS_YANDEX_ACTIVE',
[
'values' => array('N', 'Y'),
'default' => 'N',
'title' => Loc::getMessage('IBLOCK_ENTITY_RSS_YANDEX_ACTIVE_FIELD')
]
),
new Fields\StringField(
'XML_ID',
[
'validation' => [__CLASS__, 'validateXmlId'],
'title' => Loc::getMessage('IBLOCK_ENTITY_XML_ID_FIELD')
]
),
new Fields\StringField(
'TMP_ID',
[
'validation' => [__CLASS__, 'validateTmpId'],
'title' => Loc::getMessage('IBLOCK_ENTITY_TMP_ID_FIELD')
]
),
new Fields\BooleanField(
'INDEX_ELEMENT',
[
'values' => array('N', 'Y'),
'default' => 'Y',
'title' => Loc::getMessage('IBLOCK_ENTITY_INDEX_ELEMENT_FIELD')
]
),
new Fields\BooleanField(
'INDEX_SECTION',
[
'values' => array('N', 'Y'),
'default' => 'N',
'title' => Loc::getMessage('IBLOCK_ENTITY_INDEX_SECTION_FIELD')
]
),
new Fields\BooleanField(
'WORKFLOW',
[
'values' => array('N', 'Y'),
'default' => 'Y',
'title' => Loc::getMessage('IBLOCK_ENTITY_WORKFLOW_FIELD')
]
),
new Fields\BooleanField(
'BIZPROC',
[
'values' => array('N', 'Y'),
'default' => 'N',
'title' => Loc::getMessage('IBLOCK_ENTITY_BIZPROC_FIELD')
]
),
new Fields\StringField(
'SECTION_CHOOSER',
[
'validation' => [__CLASS__, 'validateSectionChooser'],
'title' => Loc::getMessage('IBLOCK_ENTITY_SECTION_CHOOSER_FIELD')
]
),
new Fields\StringField(
'LIST_MODE',
[
'validation' => [__CLASS__, 'validateListMode'],
'title' => Loc::getMessage('IBLOCK_ENTITY_LIST_MODE_FIELD')
]
),
new Fields\StringField(
'RIGHTS_MODE',
[
'validation' => [__CLASS__, 'validateRightsMode'],
'title' => Loc::getMessage('IBLOCK_ENTITY_RIGHTS_MODE_FIELD')
]
),
new Fields\StringField(
'SECTION_PROPERTY',
[
'validation' => [__CLASS__, 'validateSectionProperty'],
'title' => Loc::getMessage('IBLOCK_ENTITY_SECTION_PROPERTY_FIELD')
]
),
new Fields\StringField(
'PROPERTY_INDEX',
[
'validation' => [__CLASS__, 'validatePropertyIndex'],
'title' => Loc::getMessage('IBLOCK_ENTITY_PROPERTY_INDEX_FIELD')
]
),
new Fields\IntegerField(
'VERSION',
[
'default' => 1,
'title' => Loc::getMessage('IBLOCK_ENTITY_VERSION_FIELD')
]
),
new Fields\IntegerField(
'LAST_CONV_ELEMENT',
[
'default' => 0,
'title' => Loc::getMessage('IBLOCK_ENTITY_LAST_CONV_ELEMENT_FIELD')
]
),
new Fields\IntegerField(
'SOCNET_GROUP_ID',
[
'title' => Loc::getMessage('IBLOCK_ENTITY_SOCNET_GROUP_ID_FIELD')
]
),
new Fields\StringField(
'EDIT_FILE_BEFORE',
[
'validation' => [__CLASS__, 'validateEditFileBefore'],
'title' => Loc::getMessage('IBLOCK_ENTITY_EDIT_FILE_BEFORE_FIELD')
]
),
new Fields\StringField(
'EDIT_FILE_AFTER',
[
'validation' => [__CLASS__, 'validateEditFileAfter'],
'title' => Loc::getMessage('IBLOCK_ENTITY_EDIT_FILE_AFTER_FIELD')
]
),
new Fields\StringField(
'SECTIONS_NAME',
[
'validation' => [__CLASS__, 'validateSectionsName'],
'title' => Loc::getMessage('IBLOCK_ENTITY_SECTIONS_NAME_FIELD')
]
),
new Fields\StringField(
'SECTION_NAME',
[
'validation' => [__CLASS__, 'validateSectionName'],
'title' => Loc::getMessage('IBLOCK_ENTITY_SECTION_NAME_FIELD')
]
),
new Fields\StringField(
'ELEMENTS_NAME',
[
'validation' => [__CLASS__, 'validateElementsName'],
'title' => Loc::getMessage('IBLOCK_ENTITY_ELEMENTS_NAME_FIELD')
]
),
new Fields\StringField(
'ELEMENT_NAME',
[
'validation' => [__CLASS__, 'validateElementName'],
'title' => Loc::getMessage('IBLOCK_ENTITY_ELEMENT_NAME_FIELD')
]
),
new Fields\Relations\Reference(
'FILE',
'\Bitrix\File\File',
['=this.PICTURE' => 'ref.ID'],
['join_type' => 'LEFT']
),
new Fields\Relations\Reference(
'IBLOCK_TYPE',
'\Bitrix\Iblock\IblockType',
['=this.IBLOCK_TYPE_ID' => 'ref.ID'],
['join_type' => 'LEFT']
),
new Fields\Relations\Reference(
'LANG',
'\Bitrix\Lang\Lang',
['=this.LID' => 'ref.LID'],
['join_type' => 'LEFT']
),
new Fields\Relations\Reference(
'SOCNET_GROUP',
'\Bitrix\Sonet\SonetGroup',
['=this.SOCNET_GROUP_ID' => 'ref.ID'],
['join_type' => 'LEFT']
),
];
}
/**
* Returns validators for IBLOCK_TYPE_ID field.
*
* @return array
*/
public static function validateIblockTypeId()
{
return [
new Fields\Validators\LengthValidator(null, 50),
];
}
/**
* Returns validators for LID field.
*
* @return array
*/
public static function validateLid()
{
return [
new Fields\Validators\LengthValidator(null, 2),
];
}
/**
* Returns validators for CODE field.
*
* @return array
*/
public static function validateCode()
{
return [
new Fields\Validators\LengthValidator(null, 50),
];
}
/**
* Returns validators for API_CODE field.
*
* @return array
*/
public static function validateApiCode()
{
return [
new Fields\Validators\LengthValidator(null, 50),
];
}
/**
* Returns validators for NAME field.
*
* @return array
*/
public static function validateName()
{
return [
new Fields\Validators\LengthValidator(null, 255),
];
}
/**
* Returns validators for LIST_PAGE_URL field.
*
* @return array
*/
public static function validateListPageUrl()
{
return [
new Fields\Validators\LengthValidator(null, 255),
];
}
/**
* Returns validators for DETAIL_PAGE_URL field.
*
* @return array
*/
public static function validateDetailPageUrl()
{
return [
new Fields\Validators\LengthValidator(null, 255),
];
}
/**
* Returns validators for SECTION_PAGE_URL field.
*
* @return array
*/
public static function validateSectionPageUrl()
{
return [
new Fields\Validators\LengthValidator(null, 255),
];
}
/**
* Returns validators for CANONICAL_PAGE_URL field.
*
* @return array
*/
public static function validateCanonicalPageUrl()
{
return [
new Fields\Validators\LengthValidator(null, 255),
];
}
/**
* Returns validators for XML_ID field.
*
* @return array
*/
public static function validateXmlId()
{
return [
new Fields\Validators\LengthValidator(null, 255),
];
}
/**
* Returns validators for TMP_ID field.
*
* @return array
*/
public static function validateTmpId()
{
return [
new Fields\Validators\LengthValidator(null, 40),
];
}
/**
* Returns validators for SECTION_CHOOSER field.
*
* @return array
*/
public static function validateSectionChooser()
{
return [
new Fields\Validators\LengthValidator(null, 1),
];
}
/**
* Returns validators for LIST_MODE field.
*
* @return array
*/
public static function validateListMode()
{
return [
new Fields\Validators\LengthValidator(null, 1),
];
}
/**
* Returns validators for RIGHTS_MODE field.
*
* @return array
*/
public static function validateRightsMode()
{
return [
new Fields\Validators\LengthValidator(null, 1),
];
}
/**
* Returns validators for SECTION_PROPERTY field.
*
* @return array
*/
public static function validateSectionProperty()
{
return [
new Fields\Validators\LengthValidator(null, 1),
];
}
/**
* Returns validators for PROPERTY_INDEX field.
*
* @return array
*/
public static function validatePropertyIndex()
{
return [
new Fields\Validators\LengthValidator(null, 1),
];
}
/**
* Returns validators for EDIT_FILE_BEFORE field.
*
* @return array
*/
public static function validateEditFileBefore()
{
return [
new Fields\Validators\LengthValidator(null, 255),
];
}
/**
* Returns validators for EDIT_FILE_AFTER field.
*
* @return array
*/
public static function validateEditFileAfter()
{
return [
new Fields\Validators\LengthValidator(null, 255),
];
}
/**
* Returns validators for SECTIONS_NAME field.
*
* @return array
*/
public static function validateSectionsName()
{
return [
new Fields\Validators\LengthValidator(null, 100),
];
}
/**
* Returns validators for SECTION_NAME field.
*
* @return array
*/
public static function validateSectionName()
{
return [
new Fields\Validators\LengthValidator(null, 100),
];
}
/**
* Returns validators for ELEMENTS_NAME field.
*
* @return array
*/
public static function validateElementsName()
{
return [
new Fields\Validators\LengthValidator(null, 100),
];
}
/**
* Returns validators for ELEMENT_NAME field.
*
* @return array
*/
public static function validateElementName()
{
return [
new Fields\Validators\LengthValidator(null, 100),
];
}
}
Автоматическая генерация класса
Генерировать класс, нужно в основном для новых таблиц, для дефолтных таблиц классы уже существуют.
Для использования генератора ORM классов перейдите на страницу Настройки -> Настройки продукта -> Настройки модулей -> Монитор производительности
модуль Монитор производительности
должен быть установлен. На вкладке Генератор таблетов
отметьте поле Разрешить генерацию таблетов для ORM
.
Автоматически сгенерировать класс с описанием любой таблицы можно на странице Настройки -> Производительность -> Таблицы
, в меню действий доступен пункт ORM
Операции с сущностями
Для работы с таблицей стандартными методами D7 доступны следующие методы:
getList
выполняет запрос и возвращает отобранные по параметрам запроса данные, подробнееadd
добавляет новый элемент, подробнееupdate
обновляет строку в таблице, подробнееdelete
удаляет строку в таблице сущности по первичному ключу, подробнее
GetList
Пример запроса:
// подключаем модуль, через модуль подключается класс IblockTable
\Bitrix\Main\Loader::IncludeModule("iblock");
// делаем запрос на выборку данных getList
$res = \Bitrix\Iblock\IblockTable::getList(array(
'select' => array('ID')
));
// перебираем массив и выводим на экран
while ($arr = $res->fetch()) {
pp($arr);
}
Операторы:
IblockTable::getList(array(
// имена полей, которые необходимо получить
'select' => array('ISBN', 'TITLE', 'PUBLISH_DATE'),
// описание фильтра для WHERE и HAVING
'filter' => array('=ID' => 1),
// явное указание полей, по которым нужно группировать результат
'group' => array('PUBLISH_DATE'),
// параметры сортировки
'order' => array('PUBLISH_DATE' => 'DESC', 'TITLE' => 'ASC'),
// количество записей
'limit' => 10,
// смещение для limit
'offset' => 80,
// динамически определенные поля
'runtime' => 'runtime' => array(new Entity\ExpressionField('CNT', 'COUNT(*)'))
));
select
Параметр select
задается как массив с именами полей сущности:
array(
'select' => array('ISBN', 'TITLE', 'PUBLISH_DATE')
)
// SELECT ISBN, TITLE, PUBLISH_DATE FROM my_book
Чтобы изменить названия полей, используйте алиасы. Алиасы позволяют переименовывать поля в результирующем наборе данных для удобства. Чтобы поле PUBLISH_DATE
отображалось как PUBLICATION
:
array(
'select' => array('ISBN', 'TITLE', 'PUBLICATION' => 'PUBLISH_DATE')
)
// SELECT ISBN, TITLE, PUBLISH_DATE AS PUBLICATION FROM my_book
Для выбора всех полей используйте символ *
:
array(
'select' => array('*')
)
// SELECT *
При этом выберутся только скалярные поля ScalarField
. Скалярные поля, это обычные поля таблицы. Вычисляемые поля ExpressionField
и связи с другими сущностями нужно указывать в запросе. Они создаются на основе выражений и не являются частью таблицы по умолчанию.
filter
Параметр filter
задает условия для выборки данных в виде ассоциативного массива. Ключи массива это условия, а значения это параметры для поиска.
array(
'filter' => array('=ID' => 1)
)
// WHERE ID = 1
array(
'filter' => array('%=TITLE' => 'Patterns%')
)
// WHERE TITLE LIKE 'Patterns%'
Фильтр может быть многоуровневым с использованием AND
или OR
, что позволяет комбинировать условия:
array(
'filter' => array(
'=ID' => 1,
'=ISBN' => '9780321127426'
)
)
// WHERE ID = 1 AND ISBN = '9780321127426'
array(
'filter' => array(
// по умолчанию элементы склеиваются через AND
//'LOGIC' => 'AND',
'LOGIC' => 'OR',
array(
'=ID' => 1,
'=ISBN' => '9780321127426'
),
array(
'=ID' => 2,
'=ISBN' => '9781449314286'
)
)
)
// WHERE (ID=1 AND ISBN='9780321127426') OR (ID=2 AND ISBN='9781449314286')
Операторы сравнения позволяют задавать условия фильтрации:
=
равно (работает и с массивами)%
подстрока>
больше<
меньше@ IN (EXPR)
в качестве значения передается массив или объект!@ NOT IN (EXPR)
в качестве значения передается массив или объект!=
не равно!%
не подстрока><
между, в качестве значения передается массив array (MIN, MAX)>=
больше или равно<=
меньше или равно=%
ищет строки, которые начинаются с указанного значения. Аналог оператора LIKE в SQL%=
ищет строки, которые заканчиваются указанным значением. Аналог оператора LIKE в SQL
Префиксы %=
и =%
эквивалентны и используются для поиска подстрок:
'%=NAME' => 'тест'
аналог LIKE, не подстрока'%=NAME' => 'тест%'
содержит «тест» в начале'%=NAME' => '%тест'
содержит «тест» в конце'%=NAME' => '%тест%'
содержит подстроку «тест»
==
булевое выражение для ExpressionField (например, для EXISTS () или NOT EXISTS ())!><
не между, в качестве значения передается массив array (MIN, MAX)!=%
NOT LIKE!%=
NOT LIKE'==ID' => null
условие, что поле ID равно NULL (в sql-запросе будет преобразовано в ID IS NULL)'!==NAME' => null
условие, что поле NAME не равно NULL (в sql-запросе будет преобразовано в NAME IS NOT NULL)
group
Параметр group
задает поля для группировки. Это позволяет объединять записи с одинаковыми значениями в указанных полях.
В параметре group
перечисляются поля для группировки:
array(
'group' => array('PUBLISH_DATE')
)
Чаще всего группировку указывать не нужно — система сделает это автоматически.
order
Параметр позволяет указать порядок сортировки:
array(
'order' => array('PUBLISH_DATE' => 'DESC', 'TITLE' => 'ASC')
)
array(
'order' => array('ID') // направление по умолчанию - ASC
)
ASC
по возрастаниюDESC
по убыванию
offset/limit
Параметры помогут ограничить количество выбираемых записей или реализовать постраничную выборку:
array(
'order' => array('ID' => 'DESC')
'limit' => 10
)
// 10 последних записей
array(
'order' => array('ID')
'limit' => 20,
'offset' => 80
)
// 5-я страница с записями, по 20 на страницу
Runtime
Параметр runtime
добавляет временные поля, которые вычисляются во время выполнения запроса.
Для подсчета количества записей используйте ExpressionField
. Здесь вычисляемое поле реализует SQL-выражение с функцией COUNT
:
BookTable::getList([
'select' => ['CNT'],
'runtime' => [
new ORM\Fields\ExpressionField('CNT', 'COUNT(*)')
]
]);
// посчитать общее количество записей в таблице
// SELECT COUNT(*) AS CNT FROM my_book
После того, как добавили вычисляемое поле, его можно использовать в фильтрах, система автоматически группирует по PUBLISH_DATE
:
BookTable::getList([
'select' => ['PUBLISH_DATE'],
'filter' => ['>CNT' => 5],
'runtime' => [
new ORM\Fields\ExpressionField('CNT', 'COUNT(*)')
]
]);
// посчитать общее количество записей в таблице
// выбрать те дни, когда выпущено более 5 книг
// SELECT PUBLISH_DATE, COUNT(*) AS CNT FROM my_book GROUP BY PUBLISH_DATE HAVING COUNT(*) > 5
В runtime
можно регистрировать не только Expression
поля, но и другие типы. Механизм runtime
добавляет поле к сущности, как если бы оно было описано в getMap
. Однако такое поле доступно только в рамках одного запроса и требует повторной регистрации в следующем вызове getList
.
Вычисляемые поля без runtime
Если вычисляемое поле нужно только в select
, runtime
можно не использовать. Система поддерживает вложенные выражения, которые разворачиваются в финальном SQL
:
BookTable::getList([
'select' => [
new ORM\Fields\ExpressionField('MAX_AGE', 'MAX(%s)', ['AGE_DAYS'])
]
]);
// SELECT MAX(DATEDIFF(NOW(), PUBLISH_DATE)) AS MAX_AGE FROM my_book
Создается вычисляемое поле MAX_AGE
через ExpressionField
.
Используется вложенное выражение MAX(%s)
, где %s
заменяется на AGE_DAYS
.
AGE_DAYS
автоматически разворачивается в DATEDIFF(NOW(), PUBLISH_DATE)
Count_total
Чтобы получить список всех элементов без постраничного вывода, используйте параметр count_total
со значением true
:
$res = MyTable::getList([
/* ваши параметры запроса */
'count_total' => true,
]);
$res = MyTable::getList([
/* ваши параметры запроса */
'count_total' => true,
]);
Это позволяет получить весь список в один запрос:
// все элементы без пагинации
$res->getCount();
Add
Метод добавляет запись, принимая массив значений. Ключами массива являются имена полей сущности.
// Пространство имен, в котором находится код
namespace SomePartner\MyBooksCatalog;
// Подключаем класс Type из пространства имен Bitrix\Main
use Bitrix\Main\Type;
$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 новой записи для дальнейших операций
$id = $result->getId();
}
Метод 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
]);
Проверка корректности выполнения операции
Если в ходе операции возникли ошибки, метод вернет массив сообщений об ошибках.
// Выполняем обновление записи в таблице BookTable
$result = BookTable::update(...);
// Проверяем, успешно ли выполнено обновление
if (!$result->isSuccess()) {
// Если обновление не удалось, получаем сообщения об ошибках
$errors = $result->getErrorMessages();
}
События
Чтобы подписаться на событие в любом месте скрипта, используйте менеджер событий. Это позволяет выполнять определенные действия при наступлении событий в системе.
$em = \Bitrix\Main\ORM\EventManager::getInstance();
$em->addEventHandler(
// Класс сущности, для которого регистрируется обработчик
BookTable::class,
// Код события, которое будет обрабатываться
\Bitrix\Main\ORM\Data\DataManager::EVENT_ON_BEFORE_ADD,
// Ваш callback-функция
function () {
// Действие, выполняемое при срабатывании события
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 цифр.';
// }
// },
// Валидатор, проверяющий, что значение содержит 13 цифр подряд
new Entity\Validator\RegExp('/\d{13}/'),
];
}
Как запретить обновление данных
В обработчике события можно удалять данные или прерывать операцию. Например, чтобы запретить обновление ISBN для созданных книг, используйте событие onBeforeUpdate
одним из способов.
Удаление ISBN из данных для обновления
public static function onBeforeUpdate(Entity\Event $event)
{
$result = new Entity\EventResult;
$data = $event->getParameter("fields");
if (isset($data['ISBN'])) {
// Удаляет поле ISBN из данных для обновления
$result->unsetFields(['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 на 1
'READERS_COUNT' => new DB\SqlEx * pression('?# + 1', 'READERS_COUNT')
]);
В этом примере плейсхолдер ?#
указывает на идентификатор базы данных, который будет экранирован.
Если число читателей переменное, лучше описать выражение так.
// правильно
// Обновление записи в таблице BookTable
BookTable::update($id, [
// Увеличение значения поля READERS_COUNT на значение переменной $readersCount
'READERS_COUNT' => new DB\SqlEx * pression('?# + ?i', 'READERS_COUNT', $readersCount)
// '?#' - плейсхолдер для имени поля, заменяется на 'READERS_COUNT'
// '?i' - плейсхолдер для целочисленного значения, заменяется на $readersCount
]);
// неправильно
// Обновление записи в таблице BookTable
BookTable::update($id, [
// Небезопасное увеличение значения поля READERS_COUNT
'READERS_COUNT' => new DB\SqlEx * pression('?# + '.$readersCount, '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;
// Импорт класса Entity для работы с ORM
use Bitrix\Main\Entity;
// Импорт класса Type для работы с типами данных
use Bitrix\Main\Type;
// Класс BookTable наследует Entity\DataManager для работы с данными
class BookTable extends Entity\DataManager
{
// Метод для получения имени таблицы
public static function getTableName()
{
// Имя таблицы в базе данных
return 'my_book';
}
// Метод для получения уникального идентификатора пользовательских полей
public static function getUfId()
{
// Уникальный идентификатор
return 'MY_BOOK';
}
// Метод для определения карты полей таблицы
public static function getMap()
{
return array(
// Поле ID
new Entity\IntegerField('ID', array(
// Указание, что это первичный ключ
'primary' => true,
// Автоинкремент для поля
'autocomplete' => true
)),
// Поле ISBN
new Entity\StringField('ISBN', array(
// Поле обязательно для заполнения
'required' => true,
// Имя столбца в базе данных
'column_name' => 'ISBNCODE',
// Валидация поля
'validation' => function() {
return array(
// Проверка на 13 цифр
new Entity\Validator\RegExp('/\d{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");
// Проверка наличия поля ISBN
if (isset($data['ISBN']))
{
// Удаление дефисов из ISBN
$cleanIsbn = str_replace('-', '', $data['ISBN']);
// Модификация поля ISBN
$result->modifyFields(array('ISBN' => $cleanIsbn));
}
// Возврат результата события
return $result;
}
}