iswin/borm

ORM Library for Bitrix iblocks and highload blocks

v1.3-alpha 2018-09-18 10:24 UTC

This package is not auto-updated.

Last update: 2024-06-05 15:14:08 UTC


README

Модуль позволяет работать с разделами и элементами инфоблока, используя ORM. По синтаксису идентичный Bitrix ORM. Также модуль расширяет Bitrix ORM, добавляя возможность использовать подзапросы и получать на выходе объекты, а не массивы.

Сущности объектов

Перед началом использования, необходимо подготовить сущности объектов используемых в вашем проекте. Сущности должны располагаться в папке вашего модуля.

  1. Сущности объектов highload блоков должны располагаться в папке: /local/modules/vendor.module/lib/entities/hlblock или /bitix/modules/vendor.module/lib/entities/hlblock. Все сущности объектов highload блоков, должны наследовать абстрактный класс \Iswin\Borm\HlBlock\HighLoadEntity и включать методы getTableName, getMap и getName. Пример сущности:

     <?php 
        
     namespace Vendor\Module\Entities\HlBlock;
        
     use Iswin\Borm\HlBlock\HighLoadEntity;
     use Bitrix\Main\Entity;
        
     class ColorTable extends HighLoadEntity {
            
             public static function getTableName ()
             {
                 return "project_colors";
             }
                
                
             public static function getMap() {
                 return [
                     new Entity\IntegerField('ID', [
                         'title' => 'ID',
                         'primary' => true,
                         'autocomplete' => true
                     ]),
                     new Entity\StringField('UF_NAME', ['title' => 'UF_NAME']),
                     new Entity\StringField('UF_XML_ID', ['title' => 'UF_XML_ID'])
            
                 ];
             }
                
             public function getName() {
                 return $this->getField('UF_NAME');
             }
                
             public function setName($name)
             {
                 return $this->setField('UF_NAME', $name);
             }
                
             //... рекомендуется на все значимые поля создать гетеры
             // и сеттеры, аналогичные getName() и setName($name)
     }
    
  2. Сущности разделов ифноблоков должны располагаться в папке: /local/modules/vendor.module/lib/entities/iblock или /bitix/modules/vendor.module/lib/entities/iblock. Все сущности разделов инфоблоков, должны наследовать абстрактный класс \Iswin\Borm\Iblock\Section\SectionEntity и содержать метод getIblockCode. Пример сущности:

     <?php 
        
     namespace Vendor\Module\Entities\Iblock;
        
     use Iswin\Borm\Iblock\Section\SectionEntity;
        
     class CatalogSection extends SectionEntity {
            
         const IBLOCK_CODE = 'catalog';
            
         public static function getIblockCode() {
             return self::IBLOCK_CODE;
         }
            
         public function getName() {
             return $this->getField('UF_NAME');
         }
            
         public function setName($name)
         {
             return $this->setField('UF_NAME', $name);
         }
            
         //... рекомендуется на все значимые поля создать гетеры
         // и сеттеры, аналогичные getName() и setName($name)
            
     }
    
  3. Сущности элементов ифноблоков должны располагаться в папке: /local/modules/vendor.module/lib/entities/iblock или /bitix/modules/vendor.module/lib/entities/iblock. Все сущности разделов инфоблоков, должны наследовать абстрактный класс \Iswin\Borm\Iblock\Element\ElementEntity и содержать метод getIblockCode. Пример сущности:

     <?php 
        
     namespace Vendor\Module\Entities\Iblock;
        
     use Iswin\Borm\Iblock\Element\ElementEntity;
        
     class CatalogElement extends ElementEntity {
            
         const IBLOCK_CODE = 'catalog';
            
         public static function getIblockCode() {
             return self::IBLOCK_CODE;
         }
            
         public function getName() {
             return $this->getField('UF_NAME');
         }
            
         public function setName($name)
         {
             return $this->setField('UF_NAME', $name);
         }
            
         //... рекомендуется на все значимые поля создать гетеры
         // и сеттеры, аналогичные getName() и setName($name)
            
     }
    

Выборка элементов и разделов инфоблока

  1. Пример простого запроса выбирающий только названий всех активных разделов инфоблока, отсортированных по полю SORT:

     <?php 
        
     use \Vendor\Module\Entities\Iblock\CatalogSection;
        
      /** @var CatalogSection[] $activeSections */
      $activeSections = CatalogSection::query()
         ->addSelect('NAME')
         ->addFilter('ACTIVE', 'Y')
         ->setOrder(['SORT' => 'asc'])
         ->fetchAll();
    
2. Пример активных разделов, со связанным с ним справочником через пользовательское свойство UF_DIRECTORY 
(использование подзапросов для выборки связанных объектов, позволяет не осуществлять запросы в цикле, все связанные значения выбираеются одним запросом):

<?php 

use \Vendor\Module\Entities\Iblock\CatalogSection;
use \Vendor\Module\Entities\HlBlock\DirectoryTable;

 /** @var CatalogSection[] $activeSections */
 $activeSections = CatalogSection::query()
    ->addSelect('NAME')
    //таким образом можно добваить в выборку любое пользовательское свойство
    ->addSelectProp('UF_TEST_PROP')
    ->addSelectLink(
        'UF_DIRECTORY',
        //в подзапросе нужно указать какие поля связанного объекта нужно выбрать
        DirectoryTable::query() 
            ->addSelect('UF_NAME')
            ->addSelect('UF_DESCRIPTION')
    )
    ->addFilter('ACTIVE', 'Y')
    ->fetchAll();
 
 /** @var CatalogSection $activeSection */
 foreach($activeSections as $activeSection) {
     //выведет название раздела
    print $activeSection->getName() . "\n"; 
    //выведет название связанного через свойство UF_DIRECTORY элемента highload  блока
    //если свойство множественное, нужно использовать getLinkObjectByCode - чтобы получить массив
    print $activeSection->getOneLinkObjectByCode('UF_DIRECTORY')->getName();
    //выведет описание связанного через свойство UF_DIRECTORY элемента highload  блока
    print $activeSection->getOneLinkObjectByCode('UF_DIRECTORY')->getField('UF_DESCRIPTION');    
    //Выведет значение пользовательского свойства UF_TEST_PROP
    print $activeSection->getField('UF_TEST_PROP') . "\n"; 
 }


3. Структура запросов для элементов инфоблока, ничем не отличается от запросов для разделов:


<?php 

use \Vendor\Module\Entities\Iblock\CatalogProduct;
use \Vendor\Module\Entities\HlBlock\BrandTable;

 /** @var CatalogProduct[] $activeSections */
 $activeProducts = CatalogProduct::query()
    ->addSelect('NAME')
    //таким образом можно добваить в выборку любое свойство элемента инфоблока
    ->addSelectProp('TEST_PROPERTY')
    ->addSelectLink(
        'BRAND',
        //в подзапросе нужно указать какие поля связанного объекта нужно выбрать
        BrandTable::query() 
            ->addSelect('UF_NAME')
            ->addSelect('UF_DESCRIPTION')
    )
    ->addFilter('ACTIVE', 'Y')
    ->fetchAll();
 
 /** @var CatalogProduct $activeProduct */
 foreach($activeProducts as $activeProduct) {
     //выведет название товара
    print $activeProduct->getName() . "\n"; 
    //выведет название связанного через свойство BRAND элемента highload  блока
    //если свойство множественное, нужно использовать getLinkObjectByCode - чтобы получить массив
    print $activeProduct->getOneLinkObjectByCode('BRAND')->getName();
    //выведет описание связанного через свойство UF_DIRECTORY элемента highload  блока
    print $activeProduct->getOneLinkObjectByCode('BRAND')->getField('UF_DESCRIPTION');     
    //Выведет значение свойства TEST_PROPERTY, такая работа со свойствами - это единственное отличие от разделов      
    print $activeProduct
            ->getPropsCollection() //\Iswin\Borm\Iblock\Element\PropsCollection
            ->getProperty('TEST_PROPERTY')  //\Iswin\Borm\Iblock\Element\Property
            ->getValue();
   
    //это объект для работы с данными каталога продукта, в т.ч. и ценами
    //см. класс \Iswin\Borm\Iblock\Element\CatalogData
    $catalogData = $activeProduct->getCatalogData();
 }


4. Постраничная навигация в разделах и элементах инфоблока (работает по одинаковому принципу):
<?php 

use \Vendor\Module\Entities\Iblock\CatalogSection;
use \Iswin\Borm\Iblock\PageNav;

$currentPageNum = 1; //текущий номер страницы
$pageSize = 10; //кол-во элементов на странице

$nav = PageNav::getInstance($currentPageNum, $pageSize);

 /** @var CatalogSection[] $activeSections */
 $activeSections = CatalogSection::query()
    ->addSelect('NAME')
    ->addFilter('ACTIVE', 'Y')
    ->setOrder(['SORT' => 'asc'])
    ->setNav($nav)
    ->fetchAll();

 
 var_dump($nav->getAllCount()); //выведет общее кол-во элементов попадающих под фильтр
 var_dump($nav->getPageCount()); //выведет общее кол-во страниц для постраничной навигации

Выборка элементов highload  блоков
--------

1. Пример сущности инфоблока, имеющего связь с другим хайлоад инфоблока, с элементов инфоблока, а также с разделом инфоблока:
<?php 

namespace Vendor\Module\Entities\HlBlock;

use Iswin\Borm\HlBlock\HighLoadEntity;
use Iswin\Borm\HlBlock\References\IblockReference;
use Bitrix\Main\Entity;

class BrandTable extends HighLoadEntity {
    
        public static function getTableName ()
        {
            return "brands";
        }
        
        
        public static function getMap() {
            return [
                new Entity\IntegerField('ID', [
                    'title' => 'ID',
                    'primary' => true,
                    'autocomplete' => true
                ]),
                new Entity\StringField('UF_NAME', ['title' => 'UF_NAME']),
                new Entity\StringField('UF_XML_ID', ['title' => 'UF_XML_ID']),
                
                //Обычный битриксовый функционал объявления связи между highload блоками
                new Entity\IntegerField('UF_COUNTRY', ['title' => 'UF_COUNTRY']),
                new Entity\ReferenceField(
                    'COUNTRY',
                    '\Vendor\Module\Entities\HlBlock\Country',
                    array('=this.UF_COUNTRY' => 'ref.ID')
                ),
                
                //Объявление связи с разделом инфоблока с помощью модуля borm
                new Entity\IntegerField('UF_SECTION', ['title' => 'Какой-то раздел инфоблока']),
                new IblockReference(
                    'SECTION',
                    '\Vendor\Module\Entities\Iblock\CatalogSection',
                    array('=this.UF_SECTION' => 'ref.ID')
                ),
                
                                    
                //Объявление связи с элементом инфоблока с помощью модуля borm
                new Entity\IntegerField('UF_ELEMENT', ['title' => 'Какой-то элемент инфоблока']),
                new IblockReference(
                    'ELEMENT',
                    '\Vendor\Module\Entities\Iblock\CatalogProduct',
                    array('=this.UF_ELEMENT' => 'ref.ID')
                )
                
                
            ];
        }
        
        public function getName() {
            return $this->getField('UF_NAME');
        }
        
        public function setName($name)
        {
            return $this->setField('UF_NAME', $name);
        }
}

2. Выборка всех брендов со всеми связанными объектами:
<?php 

use \Vendor\Module\Entities\Iblock\CatalogSection;
use \Vendor\Module\Entities\Iblock\СatalogProudct;
use \Vendor\Module\Entities\HlBlock\CountryTable;
use \Vendor\Module\Entities\HlBlock\BrandTable;


$brands = BrandTable::query()
    ->addSelect('UF_NAME')
    ->addSelect('UF_DESCTIPTION')
    //добавим стандартным битриксовым способом в выборку все поля связанной страны
    //отличие будет заключаться только в результате выборки, в постороении запроса
    //выборка связанных таблиц происходит в соответствии с правилами Bitrix Orm
    ->addSelect('COUNTRY.*')
    //Выберем связанный раздел инфоблока
    ->addSelect(
        'SECTION',
        //как и в случае с объектами инфоблоков перечислим какие
        // поля из связанного объекта нам нужны           
        CatalogSection::query()
            ->addSelect('NAME')
            ->addSelect('DESCRIPTION')
    )
    //выберем связанный элемент инфоблока
    ->addSelect(
        'ELEMENT',
        //как и в случае с объектами инфоблоков перечислим какие
        // поля из связанного объекта нам нужны           
        CatalogProduct::query()
            ->addSelect('NAME')
            ->addSelect('PREVIEW_TEXT')
    );
/** @var BrandTable  $brand */
foreach($brands as $brand) {
    
    var_dump($brand->getField('UF_NAME')); //выведет название бренда
    var_dump($brand->getField('UF_DESCRIPTION')); //выведет описание бренда
    
    /** @var CountryTable $country */
    $country = $brand->getField('COUNTRY');
    var_dump($country->getName()); //выведет название страны
    
    /** @var CatalogSection $iblockSection */
    $iblockSection = $brand->getField('SECTION');
    var_dump($iblockSection->getName()); //выведет название связанного раздела инфоблока
    
    /** @var CatalogProduct $iblockProduct */
    $iblockProduct = $brand->getField('ELEMENT');
    var_dump($iblockProduct->getName()); //выведет название связанного элемента инфоблока
}


Сохранение объектов
--------

1. Простой пример сохранения основных полей (справедлив и для объектов инфоблоков и для объектов highload блоков):
<?php 

use \Vendor\Module\Entities\Iblock\CatalogSection;

 /** @var CatalogSection $activeSection */
 $activeSection = CatalogSection::query()
    ->addSelect('NAME')
    ->addFilter('ACTIVE', 'Y')
    ->addFilter('ID', 123)
    ->setOrder(['SORT' => 'asc'])
    ->getOneResult(); //вернет null если раздел не будет найден
 
 //обновит название выбранного раздела
 $activeSection
    //setField в сущностях рекомендуется заменять на сетеры для простоты
    // тогда код будет более читаемым: ->setName('Новое имя')->save();
    ->setName('Новое имя')
    ->save();
 
 $newSection = CatalogSection::getInstance()
    ->setName('Новый раздел')
    //установим значение пользовательского свойства
    ->setField('UF_TEST_PROP', 'test value')
    //проставляет флаг активности
    ->enable()
    //генерирует символьный код из названия
    ->makeCode()
    ->save();
 
 var_dump($newSection->getId()); //выведет ID вновь созданного раздела


2. Пример обновления свойств и данных каталога для элемента инфоблока:
<?php 

use \Vendor\Module\Entities\Iblock\CatalogProduct;
use \Iswin\Borm\Iblock\Element\CatalogPrice;

 /** @var CatalogProduct $activeProduct */
 $activeProduct = CatalogProduct::query()
    ->addSelect('NAME')
    ->addFilter('ACTIVE', 'Y')
    ->addFilter('ID', 123)
    ->setOrder(['SORT' => 'asc'])
    ->getOneResult(); //вернет null если раздел не будет найден
 
 //обновит название выбранного раздела
 $activeProduct
    //установим значение свойства элемента инфоблока
    ->getPropsCollection()->setValue('TEST_STRING_PROPERTY', 'new prop value');
 
 //если мы не уверены в том, что элемент инфоблока - продукт
 //но нам нужно выставить ему цены (или другие данные каталога), то необходима проверка
 $catalogData = $activeProduct->getCatalogData() ? : $activeProduct->makeCatalogData();
 
 //выставим настройки продукта, подробнее см. в классе \Iswin\Borm\Iblock\Element\CatalogData
 $catalogData
     ->setProductTypeWithOffers()
     ->setPriceTypeSingle();
 
 $basePrice = $catalogData->getBasePrice();
 //если по каким то причинам базовой цены у продукта нет, создадим ее
 if (!$basePrice) {
     //создадим пустой инстанс цены
     $basePrice = CatalogPrice::getInstance(false, []);
     //выставим тип цены, как базовый см. 
     $basePrice->setType(CatalogProduct::getBasePriceType());
     $catalogData->addPrice($basePrice);
 }
 
 //сохраняет продукт, включая все свойства и данные каталога
 $activeProduct->save();