PHP code example of beta / bx.model

1. Go to this page and download the library: Download beta/bx.model library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

beta / bx.model example snippets


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"}'

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']

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();                   // вернет ассоциативный массив

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',
]);

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

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);

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();     // возвращает коллекцию товаров в соответствии со сформированными параметрами выборки

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();                     // представление в виде ассоциативного массива

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);
    }
}


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

use Bx\Model\UI\Admin\ModelGrid;

;
$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();
}, 'Изменить');


use Bx\Model\Services\UserService;

$userService = new UserService();
$userContext = $userService->login('[email protected]', 'mypassword');
$isAuthorized = $userService->isAuthorized();
$userId = $userContext->getId();

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

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

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'
);