PHPackages                             beta/bx.model - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. beta/bx.model

ActiveBitrix-module[Utility &amp; Helpers](/categories/utility)

beta/bx.model
=============

1.26.4(1y ago)105.2k↑57.8%11[2 issues](https://github.com/beta-eto-code/bx.model/issues)[2 PRs](https://github.com/beta-eto-code/bx.model/pulls)7MITPHPPHP &gt;=7.2CI failing

Since Apr 3Pushed 1y ago3 watchersCompare

[ Source](https://github.com/beta-eto-code/bx.model)[ Packagist](https://packagist.org/packages/beta/bx.model)[ RSS](/packages/beta-bxmodel/feed)WikiDiscussions master Synced today

READMEChangelogDependencies (1)Versions (69)Used By (7)

Bitrix модели
=============

[](#bitrix-модели)

- [Установка](#%D1%83%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0)
- [Модели](#%D0%BC%D0%BE%D0%B4%D0%B5%D0%BB%D0%B8)
    - [Пример описания модели](#%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80-%D0%BE%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D1%8F-%D0%BC%D0%BE%D0%B4%D0%B5%D0%BB%D0%B8)
    - [Пример работы с моделью](#%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80-%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D1%8B-%D1%81-%D0%BC%D0%BE%D0%B4%D0%B5%D0%BB%D1%8C%D1%8E)
    - [Агрегационная модель](#%D0%B0%D0%B3%D1%80%D0%B5%D0%B3%D0%B0%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D0%B0%D1%8F-%D0%BC%D0%BE%D0%B4%D0%B5%D0%BB%D1%8C)
        - [Пример описания агрегационной модели](#%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80-%D0%BE%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D1%8F-%D0%B0%D0%B3%D1%80%D0%B5%D0%B3%D0%B0%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D0%BE%D0%B9-%D0%BC%D0%BE%D0%B4%D0%B5%D0%BB%D0%B8)
        - [Пример работы с агрегационной моделью](#%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80-%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D1%8B-%D1%81-%D0%B0%D0%B3%D1%80%D0%B5%D0%B3%D0%B0%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D0%BE%D0%B9-%D0%BC%D0%BE%D0%B4%D0%B5%D0%BB%D1%8C%D1%8E)
- [Коллекции](#%D0%BA%D0%BE%D0%BB%D0%BB%D0%B5%D0%BA%D1%86%D0%B8%D0%B8)
    - [Пример работы с коллекцией](#%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80-%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D1%8B-%D1%81-%D0%BA%D0%BE%D0%BB%D0%BB%D0%B5%D0%BA%D1%86%D0%B8%D0%B5%D0%B9)
    - [Коллекция моделей](#%D0%BA%D0%BE%D0%BB%D0%BB%D0%B5%D0%BA%D1%86%D0%B8%D1%8F-%D0%BC%D0%BE%D0%B4%D0%B5%D0%BB%D0%B5%D0%B9)
        - [Пример использования](#%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)
    - [MappedCollection &amp; MappedCollectionCallback](#mappedcollection--mappedcollectioncallback)
        - [Пример использования](#%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F-1)
- [Сервисы](#%D1%81%D0%B5%D1%80%D0%B2%D0%B8%D1%81%D1%8B)
    - [Пример описания сервиса](#%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80-%D0%BE%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D1%8F-%D1%81%D0%B5%D1%80%D0%B2%D0%B8%D1%81%D0%B0)
    - [Пример использования сервиса](#%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F-%D1%81%D0%B5%D1%80%D0%B2%D0%B8%D1%81%D0%B0)
    - [Query](#query)
        - [Пример использования](#%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F-2)
    - [Pagination](#pagination)
        - [Пример использования](#%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F-3)
    - [Fetcher](#fetcher)
    - [Пример описания сервиса](#%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80-%D0%BE%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D1%8F-%D1%81%D0%B5%D1%80%D0%B2%D0%B8%D1%81%D0%B0-1)
        - [Пример использования](#%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F-4)
    - [Админ. интерфейсы](#%D0%B0%D0%B4%D0%BC%D0%B8%D0%BD-%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D1%84%D0%B5%D0%B9%D1%81%D1%8B)
        - [Пример использования](#%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F-5)
- [Базовые сервисы](#%D0%B1%D0%B0%D0%B7%D0%BE%D0%B2%D1%8B%D0%B5-%D1%81%D0%B5%D1%80%D0%B2%D0%B8%D1%81%D1%8B)
    - [Сервис для работы с пользователями](#%D1%81%D0%B5%D1%80%D0%B2%D0%B8%D1%81-%D0%B4%D0%BB%D1%8F-%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D1%8B-%D1%81-%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8F%D0%BC%D0%B8)
        - [Пример использования](#%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F-6)
    - [Сервис для работы с файлами](#%D1%81%D0%B5%D1%80%D0%B2%D0%B8%D1%81-%D0%B4%D0%BB%D1%8F-%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D1%8B-%D1%81-%D1%84%D0%B0%D0%B9%D0%BB%D0%B0%D0%BC%D0%B8)
        - [Пример использования](#%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F-7)

Установка
---------

[](#установка)

```
composer require beta/bx.model

```

Модели
------

[](#модели)

Модели отражают сущности используемые в проекте: элементы инфоблоков, элементы hl блоков или записи в произвольной таблице. Представляют собой формализованные данные и предоставляют интерфейс для более удобного оперирования данными.

Каждая модель должна имплементировать интерфейс ModelInterface, в свою очередь данный интерфейс наследует интерфейсы ArrayAccess и IteratorAggregate, то есть с моделью можно работать как с ассоциативным массивом. Так же наследуется интерфейс CollectionItemInterface требующий реализации методов:

- assertValueByKey(string $key, $value): bool - проверяет соответствие значения по ключу переданному значению
- hasValueKey(string $key): bool - проверяет наличие значений по ключу
- getValueByKey(string $key) - возвращает значение по ключу

В данном модуле присутствует не полная реализация интерфейса модели - AbsOptimizedModel, предполагается использовать как основу для создания моделей.

### Пример описания модели

[](#пример-описания-модели)

```
use Bx\Model\AbsOptimizedModel;

class CatalogProduct extends AbsOptimizedModel
{
    protected function toArray(): array
    {
        return [
            'id' => $this->getId(),
            'name' => $this->getName(),
        ];
    }

    public function getId(): int
    {
        return (int)$this['ID'];
    }

    public function setId(int $id)
    {
        $this['ID'] = $id;
    }

    public function getName(): string
    {
        return (string)$this['NAME'];
    }

    public function setName(string $name)
    {
        $this['NAME'] = $name;
    }
}
```

### Пример работы с моделью

[](#пример-работы-с-моделью)

```
$modelData = [
    'ID' => 11,
    'NAME' => 'Some product name',
];

$product = new CatalogProduct($modelData);
$product->getId();                      // 11
$product->getName();                    // 'Some product name'
$product->setName('New product name');

$product['ID'];                         // 11
$product['NAME'];                       // 'New product name'
$product['NAME'] = 'One more product name';

$product->hasValueKey('ID');            // true
$product->getValueByKey('ID');          // 11
$product->assertValueByKey('ID', 11);   // true
$product->assertValueByKey('ID', 12);   // false

/**
 * Результат:
 * ID - 11
 * NAME - New product name
 */
foreach($product as $key => $value) {
    echo "{$key} - {$value}\n";
}

$product->getApiModel();                // ['id' => 1, 'name' => 'One more product name']
json_encode($product);                  // '{"id": 1, "name": "One more product name"}'
```

### Агрегационная модель

[](#агрегационная-модель)

В некоторых случаях требуется предоставить формализованные данные на основе некоторой совокупности данных. Например, вывести общую информацию по ценам конкретного товара в рамках одной модели: максимальная/минимальная цена, среднее значение и т.д. Для подобных задач описан интерфейс AggregateModelInterface. Есть так же не полная реализация данного интерфейса - BaseAggregateModel.

#### Пример описания агрегационной модели

[](#пример-описания-агрегационной-модели)

```
use Bx\Model\BaseAggregateModel;

class AggregatePrice extends BaseAggregateModel
{
    protected function toArray(): array
    {
        return [
            'min' => $this->getMin(),
            'max' => $this->getMax(),
            'actual' => $this->getActual(),
            'description' => $this->getDescription(),
        ];
    }

    public function getMin(): ?Price
    {
        $min = null;
        foreach($this->getCollection() as $price) {
            if ($min === null) {
                $min = $price;
                continue;
            }

            if ($min->getValue() > $price->getValue()) {
                $min = $price;
            }
        }

        return $min;
    }

    public function getMax(): ?Price
    {
        $max = null;
        foreach($this->getCollection() as $price) {
            if ($max === null) {
                $max = $price;
                continue;
            }

            if ($max->getValue() < $price->getValue()) {
                $max = $price;
            }
        }

        return $max;
    }

    public function getActual(): ?Price
    {
        foreach($this->getCollection() as $price) {
            if (/** Некоторая логика **/) {
                return $price;
            }
        }
        return null;
    }

    public function getDescription(): string
    {
        return (string)$this['description'];
    }

    public function setDescription(string $description)
    {
        $this['description'] = $description;
    }
}
```

#### Пример работы с агрегационной моделью

[](#пример-работы-с-агрегационной-моделью)

```
use Bx\Model\ModelCollection;

$price1 = [
    'value' => 1000,
    'currency' => 'RUB',
];
$price2 = [
    'value' => 2000,
    'currency' => 'RUB',
];
$price3 = [
    'value' => 5000,
    'currency' => 'RUB',
];

$priceCollection = new ModelCollection([
    $price1,
    $price2,
    $price3,
], Price::class);

$aggregatePrice = new AggregatePrice($priceCollection, [
    'description' => 'some description',
]);

$aggregatePrice->getApiModel();     // ['min' => ['value' => 1000, 'currency' => 'RUB'], 'max' => ['value' => 5000, 'currency' => 'RUB'], 'actual' => null, 'description' => 'some description']
```

Коллекции
---------

[](#коллекции)

Для работы с совокупностью однотипных данных создана еще одна сущность - коллекция. Коллекции предоставляют удобный интерфейс для работы с подобными наборами данных, который в себя включает: фильтрацию, поиск, добавление/удаление элементов из коллекции, выборка значений по ключу...

Для реализации коллекции необходимо имплементировать интерфейс CollectionInterface или ReadableCollectionInterface если предполагается что набор элементов не изменяется. Коллекции в свою очередь могут включать в себя любые объекты имплементирующие интерфейс CollectionItemInterface. В данном модуле уже есть полноценная реализация коллекции - Collection.

### Пример работы с коллекцией

[](#пример-работы-с-коллекцией)

```
use Bx\Model\Collection;

$priceItem1 = new Price([
    'value' => 1000,
    'currency' => 'RUB',
    'group' = 1,
]);
$priceItem2 = new Price([
    'value' => 2000,
    'currency' => 'RUB',
    'group' = 1,
]);
$priceItem3 = new Price([
    'value' => 4000,
    'currency' => 'RUB',
    'group' = 2,
]);

$collection = new Collection(
    $priceItem1,
    $priceItem2,
    $priceItem3
);

$collection->findByKey('value', 2000);          // $priceItem2
$collection->find(function(Price $price) {      // $priceItem1
    return $price->getValueByKey('value') === 1000 &&
        $price->getValueByKey('currency') === 'RUB'
});

$collection->filterByKey('group', 1);           // вернет новую коллекцию состоящую из $priceItem1 и $priceItem2
$collection->filter(function(Price $price) {    // вернет новую коллекцию состоящую из $priceItem2 и $priceItem3
    return $price->getValueByKey('value') > 1000 &&
        $price->getValueByKey('currency') === 'RUB'
});

$collection->column('value');                   // [1000, 2000, 4000]
$collection->unique('currency');                // ['RUB']
$collection->remove($priceItem2);               // удаляем элемент $priceItem2 из коллекции
$collection->append(new Price([                 // добавляем новый элемент в коллекцию
    'value' => 7000,
    'currency' => 'RUB',
    'group' => 2,
]));

$collection->first();                           // $priceItem1
$collection->count();                           // 3
count($collection);                             // 3

json_encode($collection);                       // JSON представление коллекции
$collection->jsonSerialize();                   // вернет ассоциативный массив
```

### Коллекция моделей

[](#коллекция-моделей)

В модуле есть отдельная реализация коллекции для моделей ModelCollection, данные коллекции могут содержать любые объекты имплементирующие интерфейс ModelInterface.

#### Пример использования

[](#пример-использования)

```
use Bx\Model\ModelCollection;

$productData1 =[
    'ID' => 11,
    'NAME' => 'Product name 11',
];
$productData2 = [
    'ID' => 21,
    'NAME' => 'Product name 21',
];
$product3 = new CatalogProduct([
    'ID' => 31,
    'NAME' => 'Product name 31',
]);

$productCollection = new ModelCollection([
    $productData1,
    $productData2,
    $product3
], CatalogProduct::class);

$productCollection->addModel(new CatalogProduct([
    'ID' => 41,
    'NAME' => 'Product name 41',
]));

$productCollection->add([
    'ID' => 51,
    'NAME' => 'Product name 51',
]);
```

### MappedCollection &amp; MappedCollectionCallback

[](#mappedcollection--mappedcollectioncallback)

Для удобства созданы производные коллекции в которых можно указывать произвольный ключ доступа к элементам коллекции:

- MappedCollection - принимает в конструкторе произвольную коллекцию и создает карту доступа по указанному ключу.
- MappedCollectionCallback - принимает в конструкторе произвольную коллекцию и создает карту доступа по вычисляемому ключу.

#### Пример использования

[](#пример-использования-1)

```
use Bx\Model\MappedCollection;
use Bx\Model\MappedCollectionCallback;

$mappedCollection = new MappedCollection($productCollection, 'ID');
$mappedCollection[41]->getName();                   // Product name 41

$mappedCollectionCallback = new MappedCollectionCallback(
    $productCollection,
    function(CatalogProduct $product){
        return 'product_'.$product->getId();
    }
);
$mappedCollectionCallback['product_41']->getName(); // Product name 41
```

Сервисы
-------

[](#сервисы)

Для интеграции с СУБД используются сервисы - ModelServiceInterface, данные сервисы имплементируют базовые операции:

- Запрос списка элементов сущностей (по определенным критериям) - список моделей или коллекция
- Запрос конкретного элемента сущности - модель
- Добавление/обновление элемента сущности - модели
- Удаление элемента сущности - модели

Для реализации сервиса моделей необходимо имплементировать интерфейс ModelServiceInterface, данный интерфейс объединяет следующие:

- QueryableModelServiceInterface
    - ReadableModelServiceInterface - операции: getList, getCount и getById
    - FilterableInterface - необходим для указания правил фильтрации
    - SortableInterface - необходим для указания правил сортировки
    - LimiterInterface - необходим для указания правила ограничения выборки
- SaveableModelServiceInterface - метод сохранения/обновления
- RemoveableModelServiceInterface - метод удаления

В модуле есть не полная реализации сервиса на основе которой можно создать свой сервис - BaseModelService.

### Пример описания сервиса

[](#пример-описания-сервиса)

```
use Bx\Modle\BaseModelService;
use Bx\Modle\ModelCollection;

class CatalogProductService extends BaseModelService
{
    protected function getFilterFields(): array
    {
        return [
            // указываем разрешенные для фильтрации поля
        ];
    }

    protected function getSortFields(): array
    {
        return [
            // указываем разрешенные для сортировки поля
        ];
    }

    public function getList(array $params, UserContextInterface $userContext = null): ModelCollection
    {
        $list = CatalogProductTable::getList($params)->fetchAll();
        return new ModelCollection($list, CatalogProduct::class);
    }

    public function getCount(array $params, UserContextInterface $userContext = null): int
    {
        $params['select'] = ['ID'];
        $params['count_total'] = true;
        $params['limit'] = 1;

        return $this->getList($params, $userContext)->first();
    }

    public function getById(int $id, UserContextInterface $userContext = null): ?AbsOptimizedModel;
    {
        $params = [
            'filter' => [
                '=ID' => $id,
            ],
            'limit' => 1,
        ];

        return $this->getList($params, $userContext)->first();
    }

    function save(AbsOptimizedModel $model, UserContextInterface $userContext = null): Result
    {
        $data = [
            'NAME' => $model->getName(),
        ];

        if ($model->getId() > 0) {
            return CatalogProductTable::update($model->getId(), $data);
        }

        $result = CatalogProductTable::add($data);
        if ($result->isSuccess()) {
            $model['ID'] = $result->getId();
        }

        return $result;
    }

    function delete(int $id, UserContextInterface $userContext = null): Result
    {
        return CatalogProductTable::delete($id);
    }
}
```

### Пример использования сервиса

[](#пример-использования-сервиса)

```
$catalogProductService = new CatalogProductService();
$productCollection = $catalogProductService->getList([
    'filter' => [
        '=ID' => [1, 5, 10],
    ],
    'select' => [
        'ID',
        'NAME',
    ],
    'order' => [
        'NAME' => 'desc',
    ],
    'limit' => 5,
]);

$productCollection->unique('NAME');
$productCollection->column('ID');

$product = $productCollection->findByKey('ID', 5);
$product->setName('New product name');

$resultSave = $catalogProductService->save($product);
$resultDelete = $catalogProductService->delete(10);
```

### Query

[](#query)

В модуле представлена объектная модель параметров запроса из базы, описана в интерфейсе QueryInterface, так же есть полная реализация данного интерфейса - Query. Объекты данного класса могут содержать параметры: select, filter, order, group, limit,offset. Есть производный интерфейс ModelQueryInterface, в модуле есть полная реализация данного интерфейса QueryModel, работает непосредственно с сервисом моделей, сервис передается в конструктор и посредством двойной диспетчеризации обеспечивается выборка данных, предоставляет дополнительные методы для выборки моделей:

- loadFiler - формирует фильтр на основе переданного ассоциативного массива в соответствии с правилами фильтрации сервиса, описанными в методе ModelServiceInterface-&gt;getFilterFields()
- loadSort - формирует параметры сортировки данных на основе переданного ассоциативного массива в соответствии с правилами сортировки сервиса, описанными в методе ModelServiceInterface-&gt;getSortFields()
- loadPagination - формирует параметры пагинации на основе переданного массива, используются ключи: limit и page
- getPagination - возвращает объект пагинации PaginationInterface
- getList - возвращает коллекцию моделей на основе сформированных параметров выборки

#### Пример использования

[](#пример-использования-2)

```
use Bitrix\Main\Application;

$catalogProductService = new CatalogProductService();
$request = Application::getInstance()->getContext()->getRequest();
$queryParams = $request->getQueryParams();

$query = $catalogProductService->query();   // возвращается объект QueryModel
$query->loadFiler($queryParams)             // загружаем фильтр из http запроса
    ->loadSort($queryParams)                // загружаем параметры сортировки
    ->loadPagination($queryParams);         // загружаем параметры пагинации

$query->hasFilter();                        // проверяет наличие параметров для фильтрации
$query->getFilter();                        // возвращает параметры для фильтрации

$query->hasSort();                          // проверяет наличие параметров для сортировки
$query->getSort();                          // возвращает параметры для сортировки

$query->hasLimit();                         // проверяет наличие параметров для ограничения выборки
$query->getLimit();                         // возвращает параметры ограничения выборки

$query->getPage();                          // номер страницы для пагинации
$query->getOffset();                        // номер элемента с которого начинается выборка

$productCollection = $query->getList();     // возвращает коллекцию товаров в соответствии со сформированными параметрами выборки
```

### Pagination

[](#pagination)

Для более удобной работы с пагинацией описан интерфейс PaginationInterface, в модуле имеется полная реализация данного интерфейса - Pagination. Данный класс работает совместно с интерфейсом QueryInterface.

#### Пример использования

[](#пример-использования-3)

```
use Bitrix\Main\Application;

$catalogProductService = new CatalogProductService();
$request = Application::getInstance()->getContext()->getRequest();
$queryParams = $request->getQueryParams();

$query = $catalogProductService->query();   // возвращается объект QueryModel
$query->loadFiler($queryParams)             // загружаем фильтр из http запроса
    ->loadSort($queryParams)                // загружаем параметры сортировки
    ->loadPagination($queryParams);         // загружаем параметры пагинации

$pagination = $query->getPagination();      // возвращается объект Pagination
$pagination->getPage();                     // номер текущей страницы
$pagination->getCountPages();               // общее количество страниц
$pagination-getTotalCountElements();        // общее количество элементов
$pagination->getCountElements();            // количество элементов на текущей странице
$pagination->getLimit();                    // максимальное количество элементов на странице

json_encode($pagination);                   // JSON представление
$pagination->toArray();                     // представление в виде ассоциативного массива
```

### Fetcher

[](#fetcher)

Довольно часто требуется получить модель данных со связанными моделями (по внешним ключам БД или другим критериям), для упрощения этой задачи был описан интерфейс FetcherModelInterface, данный интерфейс имеет полную реализацию - FetcherModel. Данный класс работает с сервисами моделей, для более простой реализации описан абстрактный класс сервиса моделей BaseLinkedModelService на основе этого класса можно описать совой сервис для выборки моделей со связанными моделями.

### Пример описания сервиса

[](#пример-описания-сервиса-1)

```
use Bx\Model\BaseLinkedModelService;
use Bx\Model\FetcherModel;
use Bx\Model\Query;
use Bx\Model\Interfaces\FileServiceInterface;

class ExtendedCatalogProductService extends BaseLinkedModelService
{
    /**
     * @var FileServiceInterface
     */
    private $fileService;

    public function __construct(FileServiceInterface $fileService)
    {
        $this->fileService = $fileService;
    }

    protected function getFilterFields(): array
    {
        return [
            // указываем разрешенные для фильтрации поля
        ];
    }

    protected function getSortFields(): array
    {
        return [
            // указываем разрешенные для сортировки поля
        ];
    }

    protected function getLinkedFields(): array
    {
        /**
         * В данном методе описываются внешние связи через FetcherModelInterface
         * в виде ассоциативного массива
         */

        $imageQuery = new Query();
        $imageQuery->setFetchList([]);  // пустой массив указывает на то что связанные модели выбирать не нужно, по-умолчанию выбираются все связанные модели
        $imageQuery->setSelect(['ID', 'SIZE', 'DESCRIPTION']);
        $imageQuery->setFilter('=CONTENT_TYPE' => ['jpg', 'png', 'gif']);

        $docsQuery = new Query();
        $docsQuery->setFetchList([]);
        $docsQuery->setFilter('=CONTENT_TYPE' => ['doc', 'docx', 'pdf']);

        return [
            'image' => FetcherModel::initAsSingleValue( // будет выбрана одна модель
                $this->fileService,
                'image',        // ключ по которому будет доступна связанная модель
                'IMAGE_ID',     // внешний ключ текущей сущности
                'ID',           // первичный ключ связанной сущности
                $imageQuery     // указываем доп. параметры выборки
            ),
            'docs' => FetcherModel::initAsMultipleValue( // будет выбрана коллекция моделей
                $this->fileService,
                'docs',
                'ID',
                'PRODUCT_ID',
                $docsQuery
            )->castTo(AggreageDocumentModel::cass), // выбранная коллекция будет преобразована в указанную агрегационную модель
        ];
    }

    protected function getInternalList(array $params, UserContextInterface $userContext = null): ModelCollection
    {
        $list = CatalogProductTable::getList($params)->fetchAll();
        return new ModelCollection($list, CatalogProduct::class);
    }

    public function getCount(array $params, UserContextInterface $userContext = null): int
    {
        $params['select'] = ['ID'];
        $params['count_total'] = true;
        $params['limit'] = 1;

        return $this->getList($params, $userContext)->first();
    }

    public function getById(int $id, UserContextInterface $userContext = null): ?AbsOptimizedModel;
    {
        $params = [
            'filter' => [
                '=ID' => $id,
            ],
            'limit' => 1,
        ];

        return $this->getList($params, $userContext)->first();
    }

    function save(AbsOptimizedModel $model, UserContextInterface $userContext = null): Result
    {
        $data = [
            'NAME' => $model->getName(),
        ];

        if ($model->getId() > 0) {
            return CatalogProductTable::update($model->getId(), $data);
        }

        $result = CatalogProductTable::add($data);
        if ($result->isSuccess()) {
            $model['ID'] = $result->getId();
        }

        return $result;
    }

    function delete(int $id, UserContextInterface $userContext = null): Result
    {
        return CatalogProductTable::delete($id);
    }
}
```

#### Пример использования

[](#пример-использования-4)

```
use Bx\Model\Services\FileService;

$fileService = new FileService();
$productService = new ExtendedCatalogProductService($fileService);

$collection1 = $productService->getList([]);                       // будут выбраны все связанные модели
$collection2 = $productService->getList(['fetch' => []]);          // связанные модели не будут выбраны
$collection3 = $productService->getList(['fetch' => ['image']]);   // из связанных моделей будет выбраны только модели с ключом image

$firstModel = $collection1->first();
$firstModel['image'];      // Объект File
$firstModel['docs'];       // Объект AggreageDocumentModel
```

### Админ. интерфейсы

[](#админ-интерфейсы)

В модуле представлены инструменты для быстрого отображения списка моделей в админ. интерфейсе Битрикса, с возможностью фильтрации, поиска, создания произвольных множественных и одиночных событий.

#### Пример использования

[](#пример-использования-5)

```
use Bx\Model\UI\Admin\ModelGrid;

require_once($_SERVER['DOCUMENT_ROOT'].'/bitrix/modules/main/include/prolog_admin_before.php');

$fileService = new FileService();
$productService = new ExtendedCatalogProductService($fileService);
$grid = new ModelGrid(
    $productService,        // указываем сервис для работы с данными
    'product_list',         // указываем символьный идентификатор списка сущности
    'ID'                    // ключ свойства модели для идентификации
);

/**
 * Указываем поля фильтрации
 */
$grid->addSearchFilterField('name', 'Название');
$grid->addNumericFilterField('id', 'ID');
$grid->addBooleanFilterField('active', 'Активность')
          ->setTrueOption('Активно', 'Y')
          ->setFalseOption('Не активно', 'N');
$grid->addStringFilterField('article', 'Артикул');
$grid->addDateFilterField('date_create', 'Дата создания');
$grid->addListFilterField('status', 'Статус', [
    1 => 'Новый',
    2 => 'Опубликован',
    3 => 'Снят с публикации',
]);

/**
 * Указываем колонки для вывода в таблице
 */
$grid->addColumn('id', 'ID');
$grid->addColumn('name', 'Название');
$grid->addColumn('article', 'Артикул');
$grid->addColumn('date_create', 'Дата создания');
$grid->addCalculateColumn(
    'status',
    function(ExtendedCatalogProduct $product) {
        return $product->getStatusName();
    },
    'Статус'
);

/**
 * Указываем действия над элементами
 */
$grid->setSingleAction('Удалить', 'delete')
    ->setCallback(function (int $id) use ($productService) {
        $productService->delete($id);
    });
$grid->setSingleAction('Перейти', 'redirect')
    ->setJs('location.href="/bitrix/admin/product_detail.php?id=#id#"');

/**
 * Указываем действия над элементами с условием отображения:
 * если callback возвращает false, то действие на элементе не отобразится
 */
$grid->setConditionalSingleAction('Удалить', 'delete')
    ->setShowConditionCallback(function (ExtendedCatalogProduct $model) {
        return !$model->hasValueKey('skuList') || empty($model->getValueByKey('skuList'));
    })
    ->setCallback(function (int $id) use ($productService) {
        $productService->delete($id);
    });

/**
 * Указываем действия над группой элементов
 */
$grid->setGroupAction('Удалить', 'delete')
    ->useConfirm('Подтвердить')
    ->setCallback(function (array $ids) use ($productService) {
        foreach ($ids as $id) {
            $productService->delete((int)$id);
        }
    });
$grid->setGroupAction('Опубликовать', 'accept')
    ->useConfirm('Подтвердить')
    ->setCallback(function (array $ids) use ($productService) {
        $productCollection = $productService->getList([
            'filter' => [
                '=ID' => $ids,
            ],
            'fetch' => [],
        ]);
        foreach ($productCollection as $currentProduct) {
            $currentProduct->setStatus(2);
            $productService->save($currentProduct);
        }
    });

/**
 * Добавляем кнопку в шапку справа от фильтра (вторая и последующие добавятся как выпадающее меню)
 */
$grid->addAdminButtonLink('Добавить', '/bitrix/admin/product_detail.php?lang='.LANG, 'btn_new');

/**
 * Указываем, показывать ли кнопку экспорта в Excel (формирование excel-файла средствами битрикса)
 */
$grid->setShowExcelBtn(true);

/**
 * Добавляем ссылку на строку таблицы (переход по двойному клику мышкой)
 * Если не задать второй аргумент title, по умолчанию будет "Перейти"
 * Если шаблон не подходит, можно использовать setDefaultRowLinkByCallback(callable $fnCalcLink, ?string $linkTitle = null)
 */
$grid->setDefaultRowLinkTemplate('/bitrix/admin/product_detail.php?id=#ID#', 'Изменить');

/**
* Добавляем ссылку на строку таблицы (переход по двойному клику мышкой, альтернативный метод)
*/
$grid->setDefaultRowLinkByCallback(function (ExtendedCatalogProduct $product) {
    return '/bitrix/admin/product_detail.php?id='.$product->getId();
}, 'Изменить');

require($_SERVER['DOCUMENT_ROOT'].'/bitrix/modules/main/include/prolog_admin_after.php');
$grid->show();  // показываем собранную таблицу

require_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/epilog_admin.php");
```

Базовые сервисы
---------------

[](#базовые-сервисы)

В модуле есть несколько реализованных сервисов:

- FileService - позволяет работать с файлами из таблицы b\_file
- UserService - позволяет работать с пользователями из таблицы b\_user

### Сервис для работы с пользователями

[](#сервис-для-работы-с-пользователями)

Помимо стандартных методов сервиса моделей, предоставляет след. методы:

- login(string $login, string $password): ?UserContextInterface - авторизация по логину и паролю
- isAuthorized(): bool - проверка авторизации
- saveExtendedData(User $user, string ...$keyListForSave): Result - сохранение пользователя с произвольным набором данных
- updatePassword(User $user, string $password): Result - обновление пароля пользователя
- getCurrentUser(): ?UserContextInterface - запрос текущего пользователя, по факту возвращает объект контекста из которого уже можно получить модель пользователя

Как видно из описания методов, в ряде случаев сервис возвращает не саму модель, а контекст модели - UserContextInterface, а уже из контекста можно запросить либо идентификатор пользователя getUserId или саму модель пользователя getUser. В данном интерфейсе есть 2 интересных метода:

- setAccessStrategy(AccessStrategyInterface $accessStrategy) - позволяет указать произвольную стратегию по разграничению доступа
- hasAccessOperation(int $operationId): bool - проверяет возможность выполнения пользователем произвольной операции в соответствии с указанной стратегией

#### Пример использования

[](#пример-использования-6)

```
use Bx\Model\Services\UserService;

$userService = new UserService();
$userContext = $userService->login('admin@mail.xyz', 'mypassword');
$isAuthorized = $userService->isAuthorized();
$userId = $userContext->getId();

$user = $userContext->getUser();
$user->getId();
$user->getName();

$userContext->setAccessStrategy(new SimpleAccessStrategy());
$userContext->hasAccessOperation(OperationListInterface::CAN_DELETE_FILES);
```

### Сервис для работы с файлами

[](#сервис-для-работы-с-файлами)

Помимо стандартных методов сервиса моделей, предоставляет след. методы:

- saveFiles(string $baseDir, string ...$filePaths): ModelCollection - сохраняет файлы из перечисленных url/абсолютных путей и возвращает коллекцию
- function saveUploadFiles(string $baseDir, UploadedFileInterface ...$files): ModelCollection - сохраняет файлы из перечисленных объектов интерфейса UploadedFileInterface - [PSR-7](https://www.php-fig.org/psr/psr-7/)
- saveFile(string $baseDir, string $filePath, ?string $name = null, ?string $description = null): ?File - сохраняет файл с возможностью задания альтернативного имени и описания
- replaceFile(int $fileId, string $baseDir, string $filePath, ?string $name = null, ?string $description = null): ?File - удаляет файл с указанным идентификатором и сохраняет новый

#### Пример использования

[](#пример-использования-7)

```
use Bx\Model\Services\FileService;

$fileService = new FileService();
$fileCollection = $fileService->saveFiles(
    'test_dir',
    'https://some-site.xyz/image1.jpg',
    'https://some-site.xyz/image2.jpg',
    'https://some-site.xyz/image3.jpg'
);

$savedFile = $fileService->saveFile('test_dir', 'https://some-site.xyz/image1.jpg', 'new_name1.jpg');
$savedFile = $fileService->replaceFile(
    $savedFile->getId(),
    'test_dir',
    'https://some-site.xyz/image2.jpg',
    'new_name2.jpg'
);
```

###  Health Score

42

—

FairBetter than 88% of packages

Maintenance40

Moderate activity, may be stable

Popularity33

Limited adoption so far

Community25

Small or concentrated contributor base

Maturity62

Established project with proven stability

 Bus Factor1

Top contributor holds 80.9% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~22 days

Recently: every ~77 days

Total

68

Last Release

442d ago

Major Versions

0.17.1 → 1.0.02021-07-14

### Community

Maintainers

![](https://www.gravatar.com/avatar/c75fd54c926c6b835a6b6cd92514d60d55a285d35847b9f22aefcc7ad5ef9ae7?d=identicon)[nnagornyy](/maintainers/nnagornyy)

---

Top Contributors

[![alex19pov31](https://avatars.githubusercontent.com/u/786683?v=4)](https://github.com/alex19pov31 "alex19pov31 (114 commits)")[![Teclor](https://avatars.githubusercontent.com/u/56650175?v=4)](https://github.com/Teclor "Teclor (13 commits)")[![kys](https://avatars.githubusercontent.com/u/760417?v=4)](https://github.com/kys "kys (5 commits)")[![nnagornyy](https://avatars.githubusercontent.com/u/20841535?v=4)](https://github.com/nnagornyy "nnagornyy (5 commits)")[![AnastasiyaDefa](https://avatars.githubusercontent.com/u/69621153?v=4)](https://github.com/AnastasiyaDefa "AnastasiyaDefa (3 commits)")[![mxaleev](https://avatars.githubusercontent.com/u/14152806?v=4)](https://github.com/mxaleev "mxaleev (1 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/beta-bxmodel/health.svg)

```
[![Health](https://phpackages.com/badges/beta-bxmodel/health.svg)](https://phpackages.com/packages/beta-bxmodel)
```

###  Alternatives

[typo3/cms-composer-installers

TYPO3 CMS Installers

6114.7M69](/packages/typo3-cms-composer-installers)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
