Аннотация классов в ORM bitrix
Объекты ORM, утилита генерации
С 18 версии в ORM Битрикс появились объекты.
О работе с объектами подробно можно узнать из курса
А также из видео партнерских конференций:
https://youtu.be/QmAf1xxkX0Q
https://youtu.be/mnX8oeE_lO0
Класс объекта создается в процессе выполнения кода, а практически все методы основываются на «магических» методах (__call, __get).
В результате чего IDE (например, phpStorm) будет считать обращение к таким методам ошибкой, а также не будет выводить подсказки. Для решения проблемы был выпущен bitrix cli с поддержкой команды orm:annotate. Инструмент предназначен для работы через командную строку, требует наличия библиотеки symfony/console (устанавливается через composer).
Процесс установки описан в курсе:
https://dev.1c-bitrix.ru/learning/course/index.php?COURSE_ID=43&LESSON_ID=4637
В этом же курсе приведены примеры использования:
https://dev.1c-bitrix.ru/learning/course/index.php?COURSE_ID=43&LESSON_ID=11733
В результате вызова команды orm:annotate мы получаем файл /bitrix/modules/orm_annotations.php который содержит описание всех генерируемых классов ORM в формате phpdoc, IDE видит этот файл (если он находится в рамках проекта) и начинает выводить все необходимые подсказки.
Правила генерации
Аннотация генерируется только для классов, которые заканчиваются на Table и являются наследниками класса \Bitrix\Main\ORM\Data\DataManager (либо его алиаса для старых версий Bitrix\Main\Entity\DataManager).
Поиск классов, соответствующих указанным правилам, осуществляется в нескольких местах:
- В get_declared_classes()
- Рекурсивно в директориях lib для указанных модулей
php bitrix.php orm:annotate -m tasks forum
- Формируется список директорий (папки lib) в которых будет производится поиск
/bitrix/modules/tasks/lib/
Если директория не существует, то она исключается из списка. Далее для каждой директории:
/bitrix/modules/tasks/dev/lib/
/local/modules/forum/lib/
/local/modules/forum/dev/lib/ - Вызывается get_declared_classes(), чтобы определить, какие классы существовали до обхода директории. Результат сохраняется в переменную
- Подключаются все php файлы внутри директории и поддиректорий.
- Далее снова вызывается get_declared_classes(), значение сравнивается с полученным ранее и для новых классов выполняется проверка.
- Формируется список директорий (папки lib) в которых будет производится поиск
- После вызова события onVirtualClassBuildList модуля main.
Событие работает только для registerEventHandler (addEventHandler в данном случае использовать не получится), т.к. установлен фильтр для передаваемых модулей
Например, для строки
php bitrix.php orm:annotate -m tasks
Будет вызвано только событие, которое зарегистрировал модуль tasks.
Логика следующая:
- Вызывается get_declared_classes(), чтобы определить, какие классы существовали до вызова события
- Вызывается событие
- Далее снова вызывается get_declared_classes(), значение сравнивается с полученным ранее и для новых классов выполняется проверка.
Ограничение
В соответствии с описанным выше, аннотация классов не будет сгенерирована в случае, если классы системы не принадлежат модулям и подключаются, например, по psr-4 через composer или через \Bitrix\Main\ Loader::registerAutoLoadClasses(null, …)
Событие onVirtualClassBuildList
Далее приведен упрощенный пример, как можно использовать событие.
Добавляем класс:
namespace Pixelplus\OrmAnnotations; use Bitrix\Main\EventManager; class Handlers { public static function onVirtualClassBuildList() { //Основной код } public static function registerHandler() { EventManager::getInstance()->registerEventHandler( 'main', 'onVirtualClassBuildList', 'main', self::class, 'onVirtualClassBuildList' ); } }
Разово вызываем метод
\Pixelplus\OrmAnnotations\Handlers::registerHandler().Для отдельного модуля это можно сделать при установке, в качестве toModuleId указывая свой модуль.
Внутри метода \Pixelplus\OrmAnnotations\Handlers::onVirtualClassBuildList
Для классов без модуля, зарегистрированных через Bitrix\Main\Loader::registerAutoLoadClasses, вызываем class_exists, таким образом они попадают в get_declared_classes(). Альтернативный вариант – где-то сохранить список классов, добавляемых в загрузчик.
$rp = new \ReflectionProperty('Bitrix\Main\Loader', 'arAutoLoadClasses'); $rp->setAccessible(true); $classes = $rp->getValue(); $rp->setAccessible(false); foreach ($classes as $class => $classData) { if (!$classData['module']) { class_exists($class); } }
Для классов, загруженных через composer по psr-4
(например такого)
- Подключаем composer через composer =) composer require composer/composer
- Используем из библиотеки класс \Composer\Autoload\ClassMapGenerator для получения карты классов
- Для каждой директории psr-4, psr-0, описанной в composer.json генерируем карту классов
- Для каждого класса вызываем class_exists
if (class_exists('\Composer\Autoload\ClassMapGenerator')) { $composerJsonFile = $_SERVER['DOCUMENT_ROOT'].'/local/composer.json'; $map = []; if (file_exists($composerJsonFile)) { $jsonContent = json_decode(file_get_contents($composerJsonFile), true); if (array_key_exists('psr-4', $jsonContent['autoload'])) { foreach (['psr-4', 'psr-0'] as $psrType) { foreach ($jsonContent['autoload'][$psrType] as $path) { if (substr($path, 0, 1) != '/') { $path = dirname($composerJsonFile).'/'.$path; } $map = array_merge($map, array_keys(\Composer\Autoload\ClassMapGenerator::createMap( $path ))); } } } } foreach ($map as $class) { class_exists($class); } }где $composerJsonFile - полный путь к файлу composer.json
Таким образом все наши дополнительные классы попадут в файл аннотаций
← Назад в раздел