Приветствую всех неравнодушных! В статье я расскажу, как мы смогли подружить сложные D7 свойства инфоблоков с нашей IDE. 

Есть в одном проекте такая волшебная штука, как подборы. В них столько свойств, что обычный getList() по 30 записям съедает 6 Гб оперативной памяти, а для оптимизации этого монстра приходится использовать ядро D7. Что же может нам рассказать интернет о том, как правильно обращаться к свойствам инфоблоков, чтобы проект не "ушел отдыхать", обидевшись на всех?

1. Изучим концепцию

Нам, как во многих фреймворках, предлагают описать нашу сущность, создать модель и по ней уже баловаться, как пожелаем. Но в нашей сущности получается 570 полей. Одно описание этих полей займет о-о-ой как много времени — не наш вариант.

2. Стандартное решение

Идем дальше, берем решение с динамической генерацией свойств через DataManager. Но ядро изначально не умеет работать со свойствами, о чем мы можем узнать из этой статьи, где попытались "натянуть сову на глобус" и сделать самописное решение.

3. Решение от битрикс-разработчиков

Битрикс взяли все в свои руки и такие: «А мы вам нарисуем, как все делается, потому что в документацию и аннотацию кода - это долго и кропотливо».

Каждый инфоблок является самостоятельным типом данных со своим собственным набором свойств. В ORM он представляется отдельной сущностью

Если вам этого мало, то мы еще магическими методами __call позволим вам выбирать свойства через генерируемые на лету таблицы. Решение интересное, если не учитывать то, что далеко не все разработчики, работающие с битриксом, понимают, как это работает, ну и IDE, конечно, не будет дружить с магией, что усложняет использование, когда все надо держать в голове.

Ура, проблема решена!

Скрытый текст

Осталось, рассказать IDE - как это все работает…

4. Итак, что мы имеем? 

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

Loader::includeModule('iblock');


$this->iblock = Iblock::wakeUp($this->iblockEnum->value);
$this->iblock->fill('API_CODE');
return $this->iblock->getEntityDataClass()::getList($parameters)->fetchCollection();
Скрытый текст

Даже не пытайтесь вывести на var_dump содержимое коллекции ;-)

Или такую запись для одного элемента:

Loader::includeModule('iblock');

$this->iblock = Iblock::wakeUp($this->iblockEnum->value);

$this->iblock->fill('API_CODE');

return $this->iblock->getEntityDataClass()::getByPrimary(primary: $primary, parameters: $parameters)->fetchObject();


И, конечно же, IDE понятия не имеет, что возвращают такие методы и что в них содержится:

На что разработчики Битрикса дают совет: “что вы, как дети? Просто используйте аннотации — и будет вам счастье!”.

Мы, как добропорядочные граждане, добавляем эти аннотации, и IDE такая: "Ребята, вы что там курите? Я, как бы, не умею компилировать код, и смотреть созданный динамически класс”.

5. Уточнения

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

  • ITEM — значение свойства типа "список";

  • ELEMENT — привязанный элемент;

  • SECTION — привязанный раздел;

  • FILE — файл.

Если свойство множественное, то getSomePropertyCode() вернет коллекцию.

Причем, у каждого из перечисленных типов есть свои методы. Например:

  • FILE — getFile()->getSource() и т. д., о чем можно узнать только на просторах интернета.

  • ITEM — getItem()->getXmlId()

  • ELEMENT — getElement()->getEntity() и т. д.по иерархии… 

Однако IDE понятия не имеет, какие у них методы.

6. Решение

Для сущностей я создал псевдокласс, который возвращает мне и сам генерируемый класс, и класс-подмен, откуда будет браться иерархия методов.

В принципе, достаточно в phpDoc-блоке @var указывать псевдокласс, а его — наследовать от ORM-сущности. Приведу пример: сущность (элемент)

namespace App\Domains\<Selections>\Entities;

use App\Shared\Contracts\Entities\Properties\BaseInterface;

use App\Shared\Contracts\Entities\Properties\ItemInterface;

use Bitrix\Iblock\EO_Section_Collection;

use Bitrix\Iblock\EO_Section_Entity;

use Bitrix\Main\ORM\Objectify\EntityObject;

/**

*

* @method BaseInterface getProcessingVersion()

* @method BaseInterface getIsProcessed()

* @method BaseInterface getIsSentTo<RemoteService>()

* @method BaseInterface getId<RemoteService>()

* @method BaseInterface getClient()

* @method BaseInterface getIsSentTo<RemoteService>()

* @method BaseInterface getDateSentTo<RemoteService>()

* @method ItemInterface getSource()

* @method ItemInterface getCallStatus()

* @method EO_Section_Entity getIblockSection()

* @method EO_Section_Collection getSections()

*/

class ElementEntity extends  EntityObject

{

}

Здесь, согласно статьям выше, мы описываем методы для необходимых нам сущностей, которые получаются магическими методами getPropertyCode() и указываем им один из 4х типов интерфейсов, для которых также подготавливаем дочерние интерфейсы:

5.1 Базовый интерфейс

namespace App\Shared\Contracts\Entities\Properties;

/**

*

*  Свойства элементов

*  Свойства можно получить методом getXyz, где Xyz - CamelCased-код свойства.

*  У свойств есть VALUE и DESCRIPTION, которые можно получить методами getValue и getDescription соответственно.

*  Для некоторых типов добавляются дополнительные поля для доступа к дополнительной информации

*  (ITEM - значение свойства типа список, ELEMENT - привязанный элемент, SECTION - привязанный раздел, FILE - файл).

*@see https://mrcappuccino.ru/blog/post/iblock-elements-bitrix-d7

*/

interface BaseInterface

{

   public function getValue(): mixed;

   public function setValue(mixed $value): void;

   public function getDescription(): string|null;

   public function setDescription(string $description): void;

   public function hasDescription(): bool;

}

5.2 Интерфейс элемента

namespace App\Shared\Contracts\Entities\Properties;

use Bitrix\Iblock\EO_Element;

/**

* PROPERTY.ELEMENT

*/

interface ElementInterface extends BaseInterface

{

   public function getElement(): EO_Element;

}

5.3 Файлы

namespace App\Shared\Contracts\Entities\Properties;

use Bitrix\Main\EO_File;

/**

* PROPERTY.FILE

*/

interface FileInterface extends BaseInterface

{

   public function getFile(): EO_File;

}

5.4 Итем

namespace App\Shared\Contracts\Entities\Properties;

/**

* PROPERTY.ITEM

*/

interface ItemInterface extends BaseInterface

{

   public function getItem(): ItemActionsInterface;

}

5.4.1 Который, в свою очередь, имеет свой набор методов внутри getItem()

namespace App\Shared\Contracts\Entities\Properties;

use Bitrix\Iblock\EO_PropertyEnumeration_Collection;

interface ItemActionsInterface

{

   public function getId(): int;

   public function getXmlId(): string;

   public function getValue(): string;

   /**

    * Метод доступен, если getItem вернет коллекцию.

    * @see EO_PropertyEnumeration_Collection

    * @return array

    */

   public function getAll(): array;

}

Далее, если нам возвращается коллекция, то мы, по такому же методу, как и у элемента, делаем псевдокласс. В этом классе описываем, какие именно псевдоэлементы нам приходят:

namespace App\Domains\<Selections>\Entities;

use Bitrix\Main\ORM\Objectify\Collection;

/**

* @method ElementEntity[] getAll()

*/

class CollectionEntity extends Collection

{

}

И IDE теперь точно знает из документации, что нам при помощи getAll() возвращается массив из псевдоэлементов инфоблока подборов, который, в свою очередь, имеет иерархию своих методов из интерфейсов.

6 Результат

6.1 Возвращает класс, в котором методы коллекций;

6.2 Возвращает коллекцию элементов конкретно этого инфоблока;

6.3 Возвращает свойство инфоблока и методы работы со свойством;

6.4 Возвращает элемент или коллекцию, в случае, когда поле множественное;

6.5 Возвращает методы для работы с итемом.

IDE, наконец-то, понимает, что от него хотят, и будущие поколения разработчиков вознесут хвалу тому, кто опишет, что находится в этих генерируемых на лету классах.

Бонусом вы сможете видеть, какие свойства есть у инфоблока:

Приятного аппетита, кушайте на здоровье!

Комментарии (3)


  1. Slym99
    12.12.2024 14:46

    Ох уж эти современные аббревиатуры... Одно ясно: ни гитарами, ни Delphi тут не пахнет, хотя стойкий сильный запах иных субстанций наблюдается...


  1. Happiest_d
    12.12.2024 14:46

    Аннотации все же есть, генерите в пару команд и будет вам счастье (насколько это возможно с Битриксом), ссылка на доку: https://dev.1c-bitrix.ru/learning/course/index.php?COURSE_ID=43&LESSON_ID=11733


    1. DVZakusilo Автор
      12.12.2024 14:46

      Спасибо за комментарий, мы конечно рассматривали этот момент, и его используем для аннотаций, я его забыл упомянуть в статье, мой косяк))
      Его проблема в том, что он аннотирует только битрикс модули, которые по стандартам битрикса делаются, а мы выбрали подход DDD в архитектуре и не завязываемся на битрикс стандарты, что видно по неймспейсам в примерах, битрикс пока так не могут анализировать.
      Нам такой подход позволяет легко переносить разработки из проекта в проект, просто копированием директорий, сделали один сервис для работы с тем же rabbitMQ, скопировали и он точно также заработал в другом проекте. Об этом пишется отдельная статья.
      Кроме того, внешняя архитектура позволяет обучать чисто битрикс разработчиков к работе с другими фреймворками, где нету этих правил битры.