Приветствую всех неравнодушных! В статье я расскажу, как мы смогли подружить сложные 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)
Happiest_d
12.12.2024 14:46Аннотации все же есть, генерите в пару команд и будет вам счастье (насколько это возможно с Битриксом), ссылка на доку: https://dev.1c-bitrix.ru/learning/course/index.php?COURSE_ID=43&LESSON_ID=11733
DVZakusilo Автор
12.12.2024 14:46Спасибо за комментарий, мы конечно рассматривали этот момент, и его используем для аннотаций, я его забыл упомянуть в статье, мой косяк))
Его проблема в том, что он аннотирует только битрикс модули, которые по стандартам битрикса делаются, а мы выбрали подход DDD в архитектуре и не завязываемся на битрикс стандарты, что видно по неймспейсам в примерах, битрикс пока так не могут анализировать.
Нам такой подход позволяет легко переносить разработки из проекта в проект, просто копированием директорий, сделали один сервис для работы с тем же rabbitMQ, скопировали и он точно также заработал в другом проекте. Об этом пишется отдельная статья.
Кроме того, внешняя архитектура позволяет обучать чисто битрикс разработчиков к работе с другими фреймворками, где нету этих правил битры.
Slym99
Ох уж эти современные аббревиатуры... Одно ясно: ни гитарами, ни Delphi тут не пахнет, хотя стойкий сильный запах иных субстанций наблюдается...