Download the PHP package enjame/bitrix-models without Composer
On this page you can find all versions of the php package enjame/bitrix-models. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Informations about the package bitrix-models
Bitrix models
Вступление
Данный пакет привносит Model Layer в Битрикс. Этот слой логически состоит из двух частей:
- Модели для сущностей битрикса (в дальнейшем будем называть их "Битрикс-модели") работающие внутри через API Битрикса (
CIBlockElement
и т д) и представляющие собой надстройку над ним. С внешней же стороны эта надстройка напоминаетEloquent
. - Модели для произвольных таблицы работающие через
illuminate/database
в целом иEloquent
в частности.
Установка
- Регистрируем пакет в
init.php
-Arrilot\BitrixModels\ServiceProvider::register();
Использование моделей Bitrix
Для наследования доступны следующие модели:
Для пример далее везде будем рассматривать модель для элемента инфоблока (ElementModel). Для других сущностей API практически идентичен.
ElementModel полноценно поддерживает только инфоблоки второй версии (та что хранит свойства в отдельных таблицах). С первой версией некоторые моменты могут не работать должным образом из-за особенностей работы CIBlockElement::GetList(). Самая большая проблема: Если в инфоблоке есть множественные свойства, то запросы с limit(), take(), и first() будут отрабатывать неверно и получать меньше элементов чем нужно и не с полным набором множественных свойств. Если вы всё же собираетесь использовать ElementModel с инфоблоками первой версии, то обязательно выставите const IBLOCK_VERSION = 1; внутри класса модели.
Создадим, модель для инфоблока Товары.
Для работы модели необходимо лишь задать ID инфоблока в константе. Для юзеров не требуется и этого.
Если вы не хотите привязываться к ID инфоблоков, можно переопределить в моделе метод
public static iblockId()
и получать в нём ID инфоблока например по коду. Это дает большую гибкость, но скорее всего вам потребуются дополнительные запросы в БД.
В дальнейшем мы будем использовать наш класс Product
как в статическом, так и в динамическом контексте.
Добавление продукта
Заметка: В случае если поля $product в дальнейшем как-то используются, рекомендуется сразу же обновить объект новым запросом в БД . Нужно это из-за того, что форматы полей в CIblockElement::Add() и CIblockElement::GetList() не совпадают на 100% Делается это так:
Обновление
Инстанцирование модели без запросов к базе.
Для ряда операций нет необходимости в получении информации из БД, достаточно лишь ID объекта. В этом случае достаточно инстанцировать объект модели, передав в конструктор идентификатор.
Получение полей объекта из базы
Описанные методы сохраняют данные из БД внутри экземпляра класса-модели. Объекты-модели реализуют ArrayAccess, поэтому с ними можно во многом работать как с массивами.
Преобразование моделей в массив/json.
По умолчанию, все поля модели становятся доступными в массиве, что не всегда желательно.
У моделей есть специальные свойства protected $visible = [];
и protected $hidden = [];
,
при помощи которых можно составить белый/черный список полей для преобразования модели в массив/json.
Получение информации из базы
Наиболее распостраненный сценарий работы с моделями - получение элементов/списков из БД. Для построение запроса используется "Fluent API", который использует в недрах себя стандартный Битриксовый API.
Для начала построения запроса используется статический метод ::query()
.
Данный метод возвращает объект-построитель запроса (ElementQuery
, SectionQuery
или UserQuery
), через который уже строится цепочка запроса.
Простейший пример:
На самом деле данная форма приведена больше для понимания, есть более удобный вид, который использует __callStatic
для передачи управления в объект-запрос.
Любая цепочка запросов должна заканчиваться одним из следующих методов:
->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
)Примеры:
->sort(['NAME' => 'ASC', 'ID => 'DESC'])
->sort('NAME', 'DESC') // = ->sort(['NAME' => 'DESC'])
->sort('NAME') // = ->sort(['NAME' => 'ASC'])
->filter($array)
- аналог$arFilter
->navigation($array)
-
->select(...)
- аналог$arSelect
Примеры:
->select(['ID', 'NAME'])
->select('ID', 'NAME')
select()
поддерживает два дополнительных значения -'FIELDS'
(выбрать все поля),'PROPS'
(выбрать все свойства). Для пользователей также можно указать'GROUPS'
(добавить группы пользователя в выборку). Значение по-умолчанию дляElementModel
-['FIELDS', 'PROPS']
->limit($int)
,->take($int)
,->page($int)
,->forPage($page, $perPage)
- для навигации
Fetch и GetNext
По-умолчанию, внутри моделей для итерации по полученным из базы элементам/разделам/юзерам используется производительный метод ->Fetch()
В отличии от ->GetNext()
, он не приводит данные в html безопасный вид и не преобразует DETAIL_PAGE_URL, SECTION_PAGE_URL в реальные урлы элементов и категорий.
Если в результате выборки вам нужны эти преобразования, то можно переключиться на этот метод.
-
Можно переключить сразу всю модель задав ей свойство
- Можно переключить для одного единственного запроса.
Некоторые дополнительные моменты:
- Для ограничения выборки добавлены алиасы
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
.
Пример "query scope"-a уже присутсвующего в пакете.
В "query scopes" можно также передавать дополнительные параметры.
Данные скоупы уже присутсвуют в пакете, ими можно пользоваться.
Остановка действия
Иногда требуется остановить выборку из базы из query scope. Для этого достаточно вернуть false. Пример:
В результате запрос в базу не будет сделан - getList()
вернёт пустую коллекцию, getById()
- false, а count()
- 0.
Того же самого эффекта можно добиться вызвав вручную метод
->stopQuery()
Кэширование запросов
Для всех вышеупомнянутых Битрикс-моделей есть простой встроенный механизм кэширования.
Достаточно лишь добавитьв цепочку вызовов ->cache($minutes)->
и результат выборки из базы будет закэширован на указанное количество минут.
Пример: $products = Products::query()->cache(30)->filter(['ACTIVE' => 'Y'])->getList()
Под капотом кэширование происходит используя стандартный механизм из d7 Битрикса. Клоюч кэширования зависит от модели и всех параметров запроса.
Accessors
Временами возникает потребность как то модифицировать данные между выборкой их из базы и получением их из модели. Для этого используются акссессоры. Также как и для "query scopes", для добавления аксессора необходимо добавить метод в соответсвующую модель.
Правило именования метода - $methodName = "get".camelCase($field)."Attribute"
.
Пример:
Этим надо пользоваться с осторожностью, потому оригинальное значение становится недоступным.
Аксессоры можно создавать также и для несуществущих (виртуальных) полей, например:
Для того чтобы такие виртуальные аксессоры отображались в toArray() и toJson(), их необходимо явно указать в поле $appends модели.
Языковые аксессоры
Для многоязычных сайтов типичным является подход, когда для каждого языка создается своё свойство, например, UF_TITLE_RU, UF_TITLE_BY В этом случае для каждого такого поля можно создать аксессор:
Так как эти аксессоры однотипны и имеют неприятную особенность засорять модели, то для них можно использовать специальный краткий синтаксис
События моделей (Model Events)
События позволяют вклиниваться в различные точки жизненного цикла модели и выполнять в них произвольный код. Например, автоматически проставлять символьный код при создании элемента. События моделей не используют событийную модель Битрикса (ни старого ядра, ни D7) и касаются лишь того, что происходит внутри моделей. Использование Битриксовых событий покрывает больше кейсов.
Обработчик события задается переопределением соответсвующего метода в классе-модели.
Сигнатуры обработчиков других событий совпадают с приведенными выше.
Список доступных эвентов:
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
, в котором содержатся ключи полей которые мы собираемся обновлять.
D7 Model
Немного особняком стоит D7Model
В отличии от предыдущих моделей она вместо старых GetList-ов и т д использует в качестве бэкэнда D7
Через неё можно работать как с обычными сущностями D7, так и с хайлоадблоками
Пример для хайлоадблока:
Понятно, что логика получения класса хайлоадблока может быть любой, но важно не забыть скомпилировать его, иначе он не будет работать. Пожалуй самый удобный вариант - использовать вспомогательный пакет https://github.com/arrilot/bitrix-iblock-helper/ С ним мы получаем следующее:
Если мы работаем не с хайлоадблоком, а с полноценной сущностью D7 ORM, то просто возвращаем в этом методе полное название класса этой сущности. Цепочки вызовов и названия методов для D7Model такие же как и для предыдущих моделей. Всё что мы передаем в эти методы будет передано далее в D7.
Пример получения всех подписчиков с именем John и с кэшированием на 5 минут:
Полный список методов следующий
За подробностями смотрите vendor/arrilot/bitrix-models/src/Models/D7Model.php
и vendor/arrilot/bitrix-models/src/Queries/D7Query.php
Связи между моделями (Relations)
Помимо работы с отдельными Битрикс-моделями, также имеется возможность и строить связи между моделями, что делает их легко-доступными для получения через основные объекты данных. Например, товар связан с вопросами о товарах. С помощью объявления этой связи вы можете получить объекты модели вопроса с помощью выражения $product->questions, которое возвращает информацию о вопросах в виде колелкции объектов класса Question (дочерний класс BaseBitrixModel).
Объявление связей
Имена связей чувствительны к регистру.
При объявлении связи, вы должны указать следующую информацию:
- кратность связи: указывается с помощью вызова метода hasMany() или метода hasOne().
- название связного класса: указывается в качестве первого параметра для метода hasMany() / hasOne().
- связь между двумя типами данных: второй аргумент - поле во внешней модели, третий - поле во внутренней (по-умолчанию ID).
- в отличии от других ORM множественные связи не используют промежуточные (pivot) таблицы. Вместо этого используются множественные свойства Битрикса.
Доступ к связным данным
После объявления связей вы можете получать доступ к связным данным с помощью имён связей. Доступ осуществляется по свойствам объекта. Название свойства = название метода связи (но без скобочек метода конечно)
Отложеная и жадная загрузка
В примерах выше используется отложенная загрузка (данные загружаются при первом обращении к ним). Когда идет работа с массивами данных, получаем запросы в цикле, проблему n + 1.
Чтобы избежать это необходимо использовать жадную загрузку:
- Метод with можно вызывать несколько раз.
- В качестве параметра принимает строку - название связи или несколько строк/массив таких строк
->with('brand', 'questions')
/->with(['brand', 'questions'])
. - Вы можете указать вложенные связи
->with('questions.answers')
(в таком случае загрузится сразу и список вопрос и для каждого вопроса список ответов. Всего 3 запроса - на товары, на вопросы, на ответы). - Для модификации запросов можно использовать колбеки. Например чтобы загрузить только активные вопросы, и для них загрузить ответы:
Использование моделей Eloquent
Вторая часть пакета - интеграция ORM Eloquent для пользовательских таблиц в Битриксе, то есть созданных при разработке вручную, а не поставляемых вместе системой. По сути это альтернатива прямым запросам в базу, D7 ORM и моделям D7Model из этого пакета.
Через Eloquent
можно работать не только с пользовательскими таблицами, но и с Highload-блоками, что очень удобно.
При этом мы работаем с таблицей Highload-блока минуя какое-бы то ни было API Битрикса.
Стоит учитывать, что в отличии от Битрикса, Eloquent
использует в качестве расширения PHP для доступа к mysql не mysql/mysqli, а PDO.
А это значит что:
- необходимо чтобы PDO был установлен и настроен
- будут создаваться два подключения к базе на запрос.
Заметка: Вопрос: Зачем в одном пакете Eloquent если уже есть D7Model? Что лучше выбрать? Ответ: Выбор между ними зависит от условий проекта и личных предпочтений. Eloquent удобнее и функциональнее чем всё что есть в Битриксе и в D7Model Например там есть полноценные связи между моделями через промежуточные таблицы и т д С другой стороны это большая внешняя зависимость со своими требованиями
Недостатки
Установка
Первым делом нужно поставить еще одну зависимость - composer require illuminate/database
После этого добавляем в init.php
еще одну строку - Arrilot\BitrixModels\ServiceProvider::registerEloquent();
Теперь уже можно создавать Eloquent модели наследуясь от EloquentModel
Если таблица называется products
(множественная форма названия класса), то protected $table = 'products';
можно опустить - это стандартное для Eloquent поведение.
Из нестандартного
- Первичным ключом является
ID
, а неid
- Поля для времени создания и обновления записи называются
UF_CREATED_AT
иUF_UPDATED_AT
, а неcreated_at
иupdated_at
Если вы решили не добавлять в таблицу поля UF_CREATED_AT и UF_UPDATED_AT, то в модели нужно задать
public $timestamps = false;
Работа с хайлоадблоком через Eloquent
Представим что мы создали Highload-блок для брендов Brands
, задали для него таблицу brands
и добавили в него свойство UF_NAME
.
Тогда класс-модель для него будет выглядеть так:
А для добавления новой записи в него достаточно следующего кода:
Для полноценной работой с Eloquent-моделями важно ознакомиться с официальной документацией данной ORM (ссылка еще раз)
В заключение обращу внимание на то что, несмотря на то что API моделей Битрикса и моделей Eloquent очень похожи (во многом из-за того что bitrix-models разрабатывались под влияением Eloquent) это всё же разные вещи и внутри они совершенно независимые. Нельзя допустим сделать связь (relation) Eloquent модели и Битриксовой модели.
Множественные свойства highload-блока и Eloquent модели
Множественные свойства в хайлоадблоках реализованы немного хитро. Данные хранятся сразу в 2х местах:
- непосредственно в таблице хайлоадблока в сериализованном виде.
- в дополнительной таблице для этого свойства.
К счастью пакет умеет довольно неплохо решает эту проблему.
При добавлении множественного свойства достаточно добавить в модель код этого свойства в поле-массив
$multipleHighloadBlockFields
модели Напримерpublic $multipleHighloadBlockFields = ['UF_SOME_MULTIPLE_FIELD'];
После этого: $model['UF_SOME_MULTIPLE_FIELD']
будет возвращать десериализованный массив- Для добавления/обновления значения поля, тоже достаточно положить в
$model['UF_SOME_MULTIPLE_FIELD']
массив, вручную сериализовать его не нужно. - При добавлении/обновлении значения поля изменения будут автоматически применены к вспомогательной таблице. Вручную в ней ничего менять не требуется.
Чтобы последний пункт работал требуется установить дополнительную зависимость -
composer require illuminate/events
. Без этой зависимости вспомогательные таблицы обновляться не будут. Немного подробнее про неё написано в следующем абзаце.
События в Eloquent моделях
В Eloquent есть так называемые События моделей / Model events, которые позволяют вклиниться в какой-то момент работы модели.
В целом довольно похожая на Битриксовые события OnBeforeIblockElementUpdate
и т д вещь.
Если они вам нужны, то вместе с illuminate/database
вам нужно поставить еще и зависимость illuminate/events
:
composer require illuminate/events
Query Builder
При подключении Eloquent мы бесплатно получаем и Query Builder от Laravel https://laravel.com/docs/master/queries, который очень полезен если необходима прямая работа с БД минуя уровень абстракции моделей.
Он удобнее и несравнимо безопаснее чем $DB->Query()
и прочее.
Работа с билдером проводится через глобально доступный класс DB
.
Например добавление элемента бренда в HL-блок будет выглядеть так:
Постраничная навигация (pagination)
И Битрикс-модели и Eloquent-модели поддерживают ->paginate()
и ->simplePaginate()
(см. http://laravel.com/docs/master/pagination)
Для того чтобы затем отобразить постраничную навигацию через ->links()
нужно
- Установить https://github.com/arrilot/bitrix-blade/
- Скопировать дефолтные вьюшки из https://github.com/laravel/framework/tree/master/src/Illuminate/Pagination/resources/views в
local/views/pagination
После этого вьюшки можно модифицировать или создавать новые.
Активность элементов в D7Model/EloquentModel
В инфоблоках битрикса есть поле ACTIVE = 'Y'/'N', фильтрация по которому очень часто используется. В хайлоадблоках и кастомных таблицах такого поля по-умолчанию нет, однако пакет предоставляет trait который помогает в создании подобной функциональности. Как это работает:
- Добавляем поле UF_DEACTIVATED_AT типа datetime в таблицу/хайлоадблок.
- Добавляем в D7Model/EloquentModel трейт
use \Arrilot\BitrixModels\Models\Traits\DeactivationTrait;
-
Теперь в модели доступны следуюющие методы:
3.1.
$model->deactivate()
и$model->activate()
- деактивирует или активирует элемент в БД.3.2.
$model->markForDeactivation()
и$model->markForActivation()
- тоже самое, но только меняет php переменную, не выполняет ->save(). Полезно если вместе с активацией нужно сделать и другие изменения в таблице и не хочется делать дополнительный запрос в БД.3.3. Скоупы
->active()
и->deactivated()
. НапримерSomeD7Model::query()->active()->getList()
.