, (*1)
Данный пакет больше активно не поддерживается
Причина - мы больше не используем Битрикс в своих проектах.
Если вам интересен этот проект и вы хотите заняться его поддержкой - форкните его и создайте Issue в данном репозитории чтобы мы поместили здесь ссылку на форк., (*2)
Форки:
- https://github.com/Ge1i0N/bitrix-models, (*3)
Bitrix models
Вступление
Данный пакет привносит Model Layer в Битрикс.
Этот слой логически состоит из двух частей:, (*4)
- Модели для сущностей битрикса (в дальнейшем будем называть их "Битрикс-модели") работающие внутри через API Битрикса (
CIBlockElement
и т д) и представляющие собой надстройку над ним.
С внешней же стороны эта надстройка напоминает Eloquent
.
- Модели для произвольных таблицы работающие через
illuminate/database
в целом и Eloquent
в частности.
Установка
composer require arrilot/bitrix-models
- Регистрируем пакет в
init.php
- Arrilot\BitrixModels\ServiceProvider::register();
Использование моделей Bitrix
Для наследования доступны следующие модели:, (*5)
Arrilot\BitrixModels\Models\ElementModel
Arrilot\BitrixModels\Models\SectionModel
Arrilot\BitrixModels\Models\UserModel
Arrilot\BitrixModels\Models\D7Model
Для пример далее везде будем рассматривать модель для элемента инфоблока (ElementModel).
Для других сущностей API практически идентичен., (*6)
ElementModel полноценно поддерживает только инфоблоки второй версии (та что хранит свойства в отдельных таблицах).
С первой версией некоторые моменты могут не работать должным образом из-за особенностей работы CIBlockElement::GetList().
Самая большая проблема: Если в инфоблоке есть множественные свойства, то запросы с limit(), take(), и first() будут отрабатывать неверно и получать меньше элементов чем нужно и не с полным набором множественных свойств.
Если вы всё же собираетесь использовать ElementModel с инфоблоками первой версии, то обязательно выставите const IBLOCK_VERSION = 1; внутри класса модели., (*7)
Создадим, модель для инфоблока Товары., (*8)
<?php
use Arrilot\BitrixModels\Models\ElementModel;
class Product extends ElementModel
{
/**
* Corresponding iblock id.
*
* @return int
*/
const IBLOCK_ID = 1;
}
Для работы модели необходимо лишь задать ID инфоблока в константе.
Для юзеров не требуется и этого., (*9)
Если вы не хотите привязываться к ID инфоблоков, можно переопределить в моделе метод public static iblockId()
и получать в нём ID инфоблока например по коду. Это дает большую гибкость, но скорее всего вам потребуются дополнительные запросы в БД., (*10)
В дальнейшем мы будем использовать наш класс Product
как в статическом, так и в динамическом контексте., (*11)
Добавление продукта
// $fields - массив, аналогичный передаваемому в CIblockElement::Add(), но IBLOCK_ID в нём можно не указывать.
$product = Product::create($fields);
Заметка:
В случае если поля $product в дальнейшем как-то используются, рекомендуется сразу же обновить объект новым запросом в БД .
Нужно это из-за того, что форматы полей в CIblockElement::Add() и CIblockElement::GetList() не совпадают на 100%
Делается это так:, (*12)
$product = Product::create($fields)
$product->refresh();
Обновление
// вариант 1
$product['NAME'] = 'Новое имя продукта';
$product->save();
// вариант 2
$product->update(['NAME' => 'Новое имя продукта']);
Инстанцирование модели без запросов к базе.
Для ряда операций нет необходимости в получении информации из БД, достаточно лишь ID объекта.
В этом случае достаточно инстанцировать объект модели, передав в конструктор идентификатор., (*13)
$product = new Product($id);
//теперь есть возможно работать с моделью, допустим
$product->deactivate();
Получение полей объекта из базы
$product = new Product($id);
// метод `load` обращается к базе, только если информация еще не была получена.
$product->load();
// Если мы хотим принудительно обновить информацию из базы даже если она уже была получена ранее
$product->refresh();
// После любого из этих методов, мы можем работать с полученными полями (`echo $product['CODE'];`)
//Для текущего пользователья есть отдельный хэлпер
$user = User::current();
// В итоге мы получаем инстанс User с заполненными полями.
// Сколько бы раз мы не вызывали `User::current()` в рамках работы скрипта, запрос в базу происходит только один раз - первый.
// `User::freshCurrent()` - то же самое, но получает данные из базы каждый раз.
Описанные методы сохраняют данные из БД внутри экземпляра класса-модели.
Объекты-модели реализуют ArrayAccess, поэтому с ними можно во многом работать как с массивами., (*14)
$product->load();
if ($product['CODE'] === 'test') {
$product->deactivate();
}
Преобразование моделей в массив/json.
$array = $product->toArray();
$json = $product->toJson();
По умолчанию, все поля модели становятся доступными в массиве, что не всегда желательно.
У моделей есть специальные свойства protected $visible = [];
и protected $hidden = [];
,
при помощи которых можно составить белый/черный список полей для преобразования модели в массив/json., (*15)
Получение информации из базы
Наиболее распостраненный сценарий работы с моделями - получение элементов/списков из БД.
Для построение запроса используется "Fluent API", который использует в недрах себя стандартный Битриксовый API., (*16)
Для начала построения запроса используется статический метод ::query()
.
Данный метод возвращает объект-построитель запроса (ElementQuery
, SectionQuery
или UserQuery
), через который уже строится цепочка запроса., (*17)
Простейший пример:, (*18)
$products = Product::query()->select('ID')->getList();
На самом деле данная форма приведена больше для понимания, есть более удобный вид, который использует __callStatic
для передачи управления в объект-запрос., (*19)
$products = Product::select('ID')->getList();
Любая цепочка запросов должна заканчиваться одним из следующих методов:, (*20)
-
->getList()
- получение коллекции (см. http://laravel.com/docs/master/collections) объектов. По умолчанию ключом каждого элемента является его ID.
-
->getById($id)
- получение объекта по его ID.
-
->first()
- получение одного (самого первого) объекта удовлетворяющего параметрам запроса.
-
->count()
- получение количества объектов.
-
->paginate() или ->simplePaginate()
- получение спагинированного списка с мета-данными (см. http://laravel.com/docs/master/pagination)
- Методы для отдельных сущностей:
->getByLogin($login)
и ->getByEmail($email)
- получение первого попавшегося юзера с данным логином/email.
->getByCode($code)
и ->getByExternalId($id)
- получение первого попавшегося элемента или раздела ИБ по CODE/EXTERNAL_ID
Управление выборкой
-
->sort($array)
- аналог $arSort
(первого параметра CIBlockElement::GetList
), (*21)
Примеры:, (*22)
->sort(['NAME' => 'ASC', 'ID => 'DESC'])
, (*23)
->sort('NAME', 'DESC') // = ->sort(['NAME' => 'DESC'])
, (*24)
->sort('NAME') // = ->sort(['NAME' => 'ASC'])
, (*25)
-
->filter($array)
- аналог $arFilter
, (*26)
->navigation($array)
-
->select(...)
- аналог $arSelect
, (*27)
Примеры:, (*28)
->select(['ID', 'NAME'])
, (*29)
->select('ID', 'NAME')
, (*30)
select()
поддерживает два дополнительных значения - 'FIELDS'
(выбрать все поля), 'PROPS'
(выбрать все свойства).
Для пользователей также можно указать 'GROUPS'
(добавить группы пользователя в выборку).
Значение по-умолчанию для ElementModel
- ['FIELDS', 'PROPS']
, (*31)
-
->limit($int)
, ->take($int)
, ->page($int)
, ->forPage($page, $perPage)
- для навигации, (*32)
Fetch и GetNext
По-умолчанию, внутри моделей для итерации по полученным из базы элементам/разделам/юзерам используется производительный метод ->Fetch()
В отличии от ->GetNext()
, он не приводит данные в html безопасный вид и не преобразует DETAIL_PAGE_URL, SECTION_PAGE_URL в реальные урлы элементов и категорий.
Если в результате выборки вам нужны эти преобразования, то можно переключиться на этот метод., (*33)
-
Можно переключить сразу всю модель задав ей свойство, (*34)
public static $fetchUsing = 'GetNext';
// полная форма, если нужно менять параметры.
public static $fetchUsing = [
'method' => 'GetNext',
'params' => [true, true],
];
-
Можно переключить для одного единственного запроса., (*35)
Products::query()->filter(['ACTIVE' => 'Y'])->fetchUsing('GetNext')->getList()`
// вместо строки `'GetNext'` можно как и в первом случае использовать массив.
Некоторые дополнительные моменты:
- Для ограничения выборки добавлены алиасы
limit($value)
(соответсвует nPageSize
) и page($num)
(соответсвует iNumPage
)
- В некоторых местах API более дружелюбный чем в самом Битриксе. Допустим в фильтре по пользователям не обязательно использовать
'GROUP_IDS'
. При передаче 'GROUP_ID'
(именно такой ключ требует Битрикс допустим при создании пользователя) или 'GROUPS'
результат будет аналогичен.
- При создании или изменении элементов и разделов инфоблоков Битрикс перестраивает поисковый индекс и позволяет пропустить это перестаривание для конкретного вызова
Add/Update
для увеличения производиельности.
В моделях вы можете добиться того же эффекта либо сразу одним махом установив непосредственно в классе-модели protected static $updateSearch = false;
, либо уже непосредственно перед добавлением/обновлением вызвав отдельный статический метод Product::setUpdateSearch(false)
.
- Флагами $bWorkFlow и $bResizePictures для CIBlockElement::Add/Update можно управлять аналогичным образом.
Query Scopes
Построитель запросов можно расширять добавляя "query scopes" в модель.
Для этого необходимо создать публичный метод начинаюзищися со scope
., (*36)
Пример "query scope"-a уже присутсвующего в пакете., (*37)
/**
* Scope to get only active items.
*
* @param BaseQuery $query
*
* @return BaseQuery
*/
public function scopeActive($query)
{
$query->filter['ACTIVE'] = 'Y';
return $query;
}
...
$products = Product::filter(['SECTION_ID' => $secId])
->active()
->getList();
В "query scopes" можно также передавать дополнительные параметры., (*38)
/**
* @param ElementQuery $query
* @param string|array $category
*
* @return ElementQuery
*/
public function scopeFromSectionWithCode($query, $category)
{
$query->filter['SECTION_CODE'] = $category;
return $query;
}
...
$users = Product::fromSectionWithCode('sale')->getList();
Данные скоупы уже присутсвуют в пакете, ими можно пользоваться., (*39)
Остановка действия
Иногда требуется остановить выборку из базы из query scope.
Для этого достаточно вернуть false.
Пример:, (*40)
public function scopeFromCategory($query, $category)
{
if (!$category) {
return false;
}
$query->filter['SECTION_CODE'] = $category;
return $query;
}
...
В результате запрос в базу не будет сделан - getList()
вернёт пустую коллекцию, getById()
- false, а count()
- 0., (*41)
Того же самого эффекта можно добиться вызвав вручную метод ->stopQuery()
, (*42)
Кэширование запросов
Для всех вышеупомнянутых Битрикс-моделей есть простой встроенный механизм кэширования.
Достаточно лишь добавитьв цепочку вызовов ->cache($minutes)->
и результат выборки из базы будет закэширован на указанное количество минут.
Пример: $products = Products::query()->cache(30)->filter(['ACTIVE' => 'Y'])->getList()
Под капотом кэширование происходит используя стандартный механизм из d7 Битрикса. Клоюч кэширования зависит от модели и всех параметров запроса., (*43)
Accessors
Временами возникает потребность как то модифицировать данные между выборкой их из базы и получением их из модели.
Для этого используются акссессоры.
Также как и для "query scopes", для добавления аксессора необходимо добавить метод в соответсвующую модель., (*44)
Правило именования метода - $methodName = "get".camelCase($field)."Attribute"
.
Пример:, (*45)
public function getXmlIdAttribute($value)
{
return (int) $value;
}
// теперь в $product['XML_ID'] всегда будет целочисленное значение
Этим надо пользоваться с осторожностью, потому оригинальное значение становится недоступным., (*46)
Аксессоры можно создавать также и для несуществущих (виртуальных) полей, например:, (*47)
public function getFullNameAttribute()
{
return $this['NAME']." ".$this['LAST_NAME'];
}
...
echo $user['NAME']; // John
echo $user['LAST_NAME']; // Doe
echo $user['FULL_NAME']; // John Doe
Для того чтобы такие виртуальные аксессоры отображались в toArray() и toJson(), их необходимо явно указать в поле $appends модели., (*48)
protected $appends = ['FULL_NAME'];
Языковые аксессоры
Для многоязычных сайтов типичным является подход, когда для каждого языка создается своё свойство, например, UF_TITLE_RU, UF_TITLE_BY
В этом случае для каждого такого поля можно создать аксессор:, (*49)
// используем далее $section['UF_TITLE'];
public function getUfTitleAttribute()
{
return $this['UF_TITLE_' . strtoupper(LANGUAGE_ID)];
}
// используем далее $element['PROPERTY_TITLE'];
public function getPropertyTitleAttribute()
{
return $this['PROPERTY_TITLE_' . strtoupper(LANGUAGE_ID) . '_VALUE'];
}
Так как эти аксессоры однотипны и имеют неприятную особенность засорять модели, то для них можно использовать специальный краткий синтаксис, (*50)
class Product extends ElementModel
{
protected $languageAccessors = [
'PROPERTY_TITLE',
'PROPERTY_FOO'
];
}
События моделей (Model Events)
События позволяют вклиниваться в различные точки жизненного цикла модели и выполнять в них произвольный код.
Например, автоматически проставлять символьный код при создании элемента.
События моделей не используют событийную модель Битрикса (ни старого ядра, ни D7) и касаются лишь того, что происходит внутри моделей.
Использование Битриксовых событий покрывает больше кейсов., (*51)
Обработчик события задается переопределением соответсвующего метода в классе-модели., (*52)
class News extends ElementModel
{
/**
* Hook into before item create or update.
*
* @return mixed
*/
protected function onBeforeSave()
{
$this['CODE'] = CUtil::translit($this['NAME'], "ru");
}
/**
* Hook into after item create or update.
*
* @param bool $result
*
* @return void
*/
protected function onAfterSave($result)
{
//
}
}
Сигнатуры обработчиков других событий совпадают с приведенными выше., (*53)
Список доступных эвентов:, (*54)
-
onBeforeCreate
- перед добавлением записи
-
onAfterCreate(bool $result)
- после добавления записи
-
onBeforeUpdate
- перед обновлением записи
-
onAfterUpdate(bool $result)
- после обновления записи
-
onBeforeSave
- перед добавлением или обновлением записи
-
onAfterSave(bool $result)
- после добавления или обновления записи
-
onBeforeDelete
- перед удалением записи
-
onAfterDelete(bool $result)
- после удаления записи
Если сделать return false;
из обработчика onBefore...()
то последующее действие будет отменено.
В обработчиках можно получить дополнительную информацию используя свойства объекта текущей модели.
Например, в onBefore...()
обработчиках доступны все поля через $this->fields
Во всех onAfter...()
доступен массив ошибок через $this->eventErrors
;
В onBeforeUpdate()
и onBeforeSave()
доступен массив $this->fieldsSelectedForSave
, в котором содержатся ключи полей которые мы собираемся обновлять., (*55)
D7 Model
Немного особняком стоит D7Model
В отличии от предыдущих моделей она вместо старых GetList-ов и т д использует в качестве бэкэнда D7
Через неё можно работать как с обычными сущностями D7, так и с хайлоадблоками, (*56)
Пример для хайлоадблока:, (*57)
class Subscriber extends D7Model
{
public static function tableClass()
{
$hlBlock = HighloadBlockTable::getRowById(1);
return HighloadBlockTable::compileEntity($hlBlock)->getDataClass();
}
}
Понятно, что логика получения класса хайлоадблока может быть любой, но важно не забыть скомпилировать его, иначе он не будет работать.
Пожалуй самый удобный вариант - использовать вспомогательный пакет https://github.com/arrilot/bitrix-iblock-helper/
С ним мы получаем следующее:, (*58)
class Subscriber extends D7Model
{
public static function tableClass()
{
return highloadblock_class('app_subscribers');
}
}
Если мы работаем не с хайлоадблоком, а с полноценной сущностью D7 ORM, то просто возвращаем в этом методе полное название класса этой сущности.
Цепочки вызовов и названия методов для D7Model такие же как и для предыдущих моделей. Всё что мы передаем в эти методы будет передано далее в D7., (*59)
Пример получения всех подписчиков с именем John и с кэшированием на 5 минут:, (*60)
$subscribers = Subscriber::query()->cache(5)->filter(['=NAME'=>'John])->getList();
Полный список методов следующий, (*61)
/**
* static int count()
*
* D7Query methods
* @method static D7Query runtime(array|\Bitrix\Main\Entity\ExpressionField $fields)
* @method static D7Query enableDataDoubling()
* @method static D7Query disableDataDoubling()
* @method static D7Query cacheJoins(bool $value)
*
* BaseQuery methods
* @method static Collection getList()
* @method static D7Model first()
* @method static D7Model getById(int $id)
* @method static D7Query sort(string|array $by, string $order='ASC')
* @method static D7Query order(string|array $by, string $order='ASC') // same as sort()
* @method static D7Query filter(array $filter)
* @method static D7Query addFilter(array $filters)
* @method static D7Query resetFilter()
* @method static D7Query navigation(array $filter)
* @method static D7Query select($value)
* @method static D7Query keyBy(string $value)
* @method static D7Query limit(int $value)
* @method static D7Query offset(int $value)
* @method static D7Query page(int $num)
* @method static D7Query take(int $value) // same as limit()
* @method static D7Query forPage(int $page, int $perPage=15)
* @method static \Illuminate\Pagination\LengthAwarePaginator paginate(int $perPage = 15, string $pageName = 'page')
* @method static \Illuminate\Pagination\Paginator simplePaginate(int $perPage = 15, string $pageName = 'page')
* @method static D7Query stopQuery()
* @method static D7Query cache(float|int $minutes)
*/
За подробностями смотрите vendor/arrilot/bitrix-models/src/Models/D7Model.php
и vendor/arrilot/bitrix-models/src/Queries/D7Query.php
, (*62)
Связи между моделями (Relations)
Помимо работы с отдельными Битрикс-моделями, также имеется возможность и строить связи между моделями, что делает их легко-доступными для получения через основные объекты данных. Например, товар связан с вопросами о товарах. С помощью объявления этой связи вы можете получить объекты модели вопроса с помощью выражения $product->questions, которое возвращает информацию о вопросах в виде колелкции объектов класса Question (дочерний класс BaseBitrixModel)., (*63)
Объявление связей
/**
* Class Product
*
* @property Brand $brand
* @property ProductQuestion $questions
* @property Storage $storages
*/
class Product extends ElementModel
{
...
/**
* ID Brand записан в текущую модель в свойтво PROPERTY_BRAND_VALUE (не множественное)
* (у товара может быть только один бренд, но у бренда много товаров)
*/
public function brand()
{
return $this->hasOne(Brand::class, 'ID', 'PROPERTY_BRAND_VALUE');
}
/**
* У ProductQuestion в свойтве PROPERTY_PRODUCT_VALUE записан ID текущей модели
* (у товара может быть много вопросов, но вопрос относится только к одному товару)
*
* Но это будет так же работать, если PROPERTY_PRODUCT_VALUE будет множественным
*/
public function questions()
{
return $this->hasMany(ProductQuestion::class, 'PROPERTY_PRODUCT_VALUE', 'ID');
}
/**
* ID Storage записан в текущую модель в свойтво PROPERTY_STORAGE_VALUE (множественное)
* (у товара может быть много складов, на складе может быть много товаров)
*/
public function storages()
{
return $this->hasMany(Storage::class, 'ID', 'PROPERTY_STORAGE_VALUE');
}
}
Имена связей чувствительны к регистру., (*64)
При объявлении связи, вы должны указать следующую информацию:, (*65)
- кратность связи: указывается с помощью вызова метода hasMany() или метода hasOne().
- название связного класса: указывается в качестве первого параметра для метода hasMany() / hasOne().
- связь между двумя типами данных: второй аргумент - поле во внешней модели, третий - поле во внутренней (по-умолчанию ID).
- в отличии от других ORM множественные связи не используют промежуточные (pivot) таблицы. Вместо этого используются множественные свойства Битрикса.
Доступ к связным данным
После объявления связей вы можете получать доступ к связным данным с помощью имён связей.
Доступ осуществляется по свойствам объекта. Название свойства = название метода связи (но без скобочек метода конечно), (*66)
$product = Product::getById(1);
// В этот момент используются магические методы и выполняются sql запросы в БД за данными.
$product->brand; // Объект класса Brand
$product->questions; // Collection объектов класса ProductQuestion
// Запросы в базу выполняются лишь один раз. При повторном обращении к переменной возвращаются данные получененные при первом запросе.
$product->brand;
$product->questions;
Отложеная и жадная загрузка
В примерах выше используется отложенная загрузка (данные загружаются при первом обращении к ним).
Когда идет работа с массивами данных, получаем запросы в цикле, проблему n + 1., (*67)
$products = Product::getList();
foreach($products as $product) {
// Выполняется запрос
$product->brand;
}
Чтобы избежать это необходимо использовать жадную загрузку:, (*68)
// Выполняется один дополнительный запрос который получит все бренды для всех полученных продуктов.
$products = Product::query()->with('brand')->getList();
foreach($products as $product) {
// Запрос не выполняется
$product->brand;
}
- Метод with можно вызывать несколько раз.
- В качестве параметра принимает строку - название связи или несколько строк/массив таких строк
->with('brand', 'questions')
/->with(['brand', 'questions'])
.
- Вы можете указать вложенные связи
->with('questions.answers')
(в таком случае загрузится сразу и список вопрос и для каждого вопроса список ответов. Всего 3 запроса - на товары, на вопросы, на ответы).
- Для модификации запросов можно использовать колбеки. Например чтобы загрузить только активные вопросы, и для них загрузить ответы:
->with([
'questions' => function ($query) {
$query->filter(['ACTIVE' => 'Y'])
},
'questions.answers'
])
Использование моделей Eloquent
Вторая часть пакета - интеграция ORM Eloquent для пользовательских таблиц в Битриксе, то есть созданных при разработке вручную, а не поставляемых вместе системой.
По сути это альтернатива прямым запросам в базу, D7 ORM и моделям D7Model из этого пакета., (*69)
Через Eloquent
можно работать не только с пользовательскими таблицами, но и с Highload-блоками, что очень удобно.
При этом мы работаем с таблицей Highload-блока минуя какое-бы то ни было API Битрикса., (*70)
Стоит учитывать, что в отличии от Битрикса, Eloquent
использует в качестве расширения PHP для доступа к mysql не mysql/mysqli, а PDO.
А это значит что:
1. необходимо чтобы PDO был установлен и настроен
2. будут создаваться два подключения к базе на запрос., (*71)
Заметка:
Вопрос: Зачем в одном пакете Eloquent если уже есть D7Model? Что лучше выбрать?
Ответ: Выбор между ними зависит от условий проекта и личных предпочтений.
Eloquent удобнее и функциональнее чем всё что есть в Битриксе и в D7Model
Например там есть полноценные связи между моделями через промежуточные таблицы и т д
С другой стороны это большая внешняя зависимость со своими требованиями, (*72)
Недостатки, (*73)
Установка
Первым делом нужно поставить еще одну зависимость - composer require illuminate/database
После этого добавляем в init.php
еще одну строку - Arrilot\BitrixModels\ServiceProvider::registerEloquent();
Теперь уже можно создавать Eloquent модели наследуясь от EloquentModel
, (*74)
<?php
use Arrilot\BitrixModels\Models\EloquentModel;
class Product extends EloquentModel
{
protected $table = 'app_products';
}
Если таблица называется products
(множественная форма названия класса), то protected $table = 'products';
можно опустить - это стандартное для Eloquent поведение.
Из нестандартного
1. Первичным ключом является ID
, а не id
2. Поля для времени создания и обновления записи называются UF_CREATED_AT
и UF_UPDATED_AT
, а не created_at
и updated_at
, (*75)
Если вы решили не добавлять в таблицу поля UF_CREATED_AT и UF_UPDATED_AT, то в модели нужно задать public $timestamps = false;
, (*76)
Работа с хайлоадблоком через Eloquent
Представим что мы создали Highload-блок для брендов Brands
, задали для него таблицу brands
и добавили в него свойство UF_NAME
.
Тогда класс-модель для него будет выглядеть так:, (*77)
<?php
use Arrilot\BitrixModels\Models\EloquentModel;
class Brand extends EloquentModel
{
public $timestamps = false;
}
А для добавления новой записи в него достаточно следующего кода:, (*78)
$brand = new Brand();
$brand['UF_NAME'] = 'Nike';
$brand->save();
// либо даже такого если настроены $fillable поля.
$brand = Brand::create(['UF_NAME' => 'Nike']);
Для полноценной работой с Eloquent-моделями важно ознакомиться с официальной документацией данной ORM (ссылка еще раз), (*79)
В заключение обращу внимание на то что, несмотря на то что API моделей Битрикса и моделей Eloquent очень похожи (во многом из-за того что bitrix-models разрабатывались под влияением Eloquent)
это всё же разные вещи и внутри они совершенно независимые. Нельзя допустим сделать связь (relation) Eloquent модели и Битриксовой модели., (*80)
Множественные свойства highload-блока и Eloquent модели
Множественные свойства в хайлоадблоках реализованы немного хитро.
Данные хранятся сразу в 2х местах:
1. непосредственно в таблице хайлоадблока в сериализованном виде.
2. в дополнительной таблице для этого свойства.
К счастью пакет умеет довольно неплохо решает эту проблему.
При добавлении множественного свойства достаточно добавить в модель код этого свойства в поле-массив $multipleHighloadBlockFields
модели
Например public $multipleHighloadBlockFields = ['UF_SOME_MULTIPLE_FIELD'];
После этого:
1. $model['UF_SOME_MULTIPLE_FIELD']
будет возвращать десериализованный массив
2. Для добавления/обновления значения поля, тоже достаточно положить в $model['UF_SOME_MULTIPLE_FIELD']
массив, вручную сериализовать его не нужно.
3. При добавлении/обновлении значения поля изменения будут автоматически применены к вспомогательной таблице. Вручную в ней ничего менять не требуется.
Чтобы последний пункт работал требуется установить дополнительную зависимость - composer require illuminate/events
.
Без этой зависимости вспомогательные таблицы обновляться не будут. Немного подробнее про неё написано в следующем абзаце., (*81)
События в Eloquent моделях
В Eloquent есть так называемые События моделей / Model events, которые позволяют вклиниться в какой-то момент работы модели.
В целом довольно похожая на Битриксовые события OnBeforeIblockElementUpdate
и т д вещь.
Если они вам нужны, то вместе с illuminate/database
вам нужно поставить еще и зависимость illuminate/events
:, (*82)
composer require illuminate/events
, (*83)
Query Builder
При подключении Eloquent мы бесплатно получаем и Query Builder от Laravel https://laravel.com/docs/master/queries, который очень полезен если необходима прямая работа с БД минуя уровень абстракции моделей.
Он удобнее и несравнимо безопаснее чем $DB->Query()
и прочее., (*84)
Работа с билдером проводится через глобально доступный класс DB
.
Например добавление элемента бренда в HL-блок будет выглядеть так:, (*85)
DB::table('brands')->insert(['UF_NAME' => 'Nike']);
И Битрикс-модели и Eloquent-модели поддерживают ->paginate()
и ->simplePaginate()
(см. http://laravel.com/docs/master/pagination)
Для того чтобы затем отобразить постраничную навигацию через ->links()
нужно, (*86)
- Установить https://github.com/arrilot/bitrix-blade/
- Скопировать дефолтные вьюшки из https://github.com/laravel/framework/tree/master/src/Illuminate/Pagination/resources/views в
local/views/pagination
После этого вьюшки можно модифицировать или создавать новые., (*87)
Подключение к нескольким БД, детальная настройка подключения.
По умолчанию, для подключения к БД, используется стандартная конфигурация, с которой работает Битрикс.
Выглядеть она может, например, вот так (файл .settings.php.
):, (*88)
'connections' => [
'value' => [
'default' => [
'className' => '\\Bitrix\\Main\\DB\\MysqliConnection',
'host' => 'localhost',
'database' => 'db',
'login' => 'login',
'password' => 'password',
'options' => 2,
],
],
'readonly' => true,
]
Для того, чтобы иметь возможность задействовать весь функционал Eloquent, необходимо добавить в файл .settings.php
массив bitrix-models.illuminate-database
с описанием подключения:, (*89)
[
...
'bitrix-models.illuminate-database' => [
'value' => [
'default' => 'mysql',
'connections' => [
'mysql' => [
'driver' => 'mysql',
'url' => getenv('DB_MYSQL_URL'),
'host' => getenv('DB_MYSQL_HOST'),
'port' => getenv('DB_MYSQL_PORT'),
'database' => getenv('DB_MYSQL_DATABASE'),
'username' => getenv('DB_MYSQL_USERNAME'),
'password' => getenv('DB_MYSQL_PASSWORD'),
'unix_socket' => getenv('DB_MYSQL_SOCKET'),
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => false,
'engine' => null,
'options' => [],
],
'mysql_2' => [
...
],
],
],
'readonly' => true,
],
]
Важно! Если задан массив bitrix-models.illuminate-database
, то все настройки подключения к БД для Eloquent берутся только из него, а стандартные - игнорируются., (*90)
Больше примеров настройки подключения (connections): https://github.com/laravel/laravel/blob/5.7/config/database.php, (*91)
Активность элементов в D7Model/EloquentModel
В инфоблоках битрикса есть поле ACTIVE = 'Y'/'N', фильтрация по которому очень часто используется.
В хайлоадблоках и кастомных таблицах такого поля по-умолчанию нет, однако пакет предоставляет trait который помогает в создании подобной функциональности.
Как это работает:
1. Добавляем поле UF_DEACTIVATED_AT типа datetime в таблицу/хайлоадблок.
2. Добавляем в D7Model/EloquentModel трейт use \Arrilot\BitrixModels\Models\Traits\DeactivationTrait;
3. Теперь в модели доступны следуюющие методы:, (*92)
3.1. `$model->deactivate()` и `$model->activate()` - деактивирует или активирует элемент в БД.
3.2. `$model->markForDeactivation()` и `$model->markForActivation()` - тоже самое, но только меняет php переменную, не выполняет ->save(). Полезно если вместе с активацией нужно сделать и другие изменения в таблице и не хочется делать дополнительный запрос в БД.
3.3. Скоупы `->active()` и `->deactivated()`. Например `SomeD7Model::query()->active()->getList()`.