Чем серьёзнее мы относимся к своим проектам, тем больше нам хочется, чтобы задачи решались лучшим из возможных способов. Например, хотим мы предоставить клиенту качественную админку в адекватные сроки. Лично мне в такие моменты сразу вспоминается Django: создал модель – получи админку. Или виджеты в Yii. Или чудная комбинация из хуков и классов в Drupal 7. Или Sonata в Symfony, про которую я, правда, только лишь слышал. А что делать, если нам достался Битрикс?

Админка по битриксовому «фен-шую»


К сожалению, Битрикс, несмотря на попытки разработчиков как-то исправить положение, во многих своих аспектах остаётся системой архаичной: процедурные куски кода в несколько сотен строк, копипаст, возведённый до уровня мануала, классы, от которых невозможно нормально наследоваться – всё это по сей день остаётся реальностью для тех, кому приходится работать с этой системой. И, уверен, пройдёт это не скоро.

Что должен делать разработчик, если ему нужно создать административный интерфейс для какой-либо кастомной таблицы в БД? Согласно мануалу, нам нужно скопировать «рыбу» с кодом в 417 строк – для страницы списка элементов и 365 строк – для страницы редактирования элемента. Ну или написать всё самим, если мы счастливые обладатели феноменальной памяти. Что ж, 2016 год на дворе – хорошее начало!

Но ведь у нас ещё ничего не работает! После того, как мы совершили акт копипаста, нам нужно внимательно вычитать 782 строки кода, удалить всё лишнее и дописать своё. А именно:

  1. Написать валидацию данных фильтров.
  2. Указать список колонок для фильтрации выборки.
  3. Написать обработку действий над отдельным элементом и над группой элементов списка.
  4. Сделать саму выборку. Причём обычно никто не парится, делают просто SELECT * FROM … — в «рыбе» от битрикса никак не предлагается ограничивать список выбираемых полей только теми, которые необходимы.
  5. Указать список колонок для вывода в списке.
  6. В процессе вывода списка для каждой колонки вывести определённый элемент управления.
  7. Вывести футер таблицы.
  8. Вывести фильтр над таблицей.

Это для страницы списка. Я специально указывал пункты не в том порядке, в котором подсказывает логика, и в котором они выводятся на результирующей странице, а в том порядке, в котором этот код встречается в «рыбе» из мануала.

Теперь, что нам нужно сделать, если мы решили, скажем, добавить в список ещё одно поле? Или даже просто переименовать какое-то существующее? Мы должны в 7-и местах прописать это новое поле или изменить существующее, ни разу не ошибившись! Ситуация осложняется тем, что вместе с php-кодом в этом же файле идёт и html, притом совершенно не в том порядке, в котором он выводится на странице, нечитабельный ни вашей любимой IDE, ни человеческим глазом, потому что многие теги генерируются где-то в недрах. В этом всём очень сложно ориентироваться. Особенно когда страница совсем уже не простая и на ней содержится ещё и JS-код, как правило писанный инлайном.

Что мы получаем в итоге? Баги. Сложность поддержки. Неоправданно высокие временные затраты даже при изменении какой-либо мелочи. Для страницы редактирования элемента ситуация такая же. Искренне не понимаю, как столько лет можно было жевать такой кактус?!

Как всё могло бы быть


Как ни странно, API для админки у Битрикса спроектирован неплохо. После вышеописанных ужасов в это трудно поверить, однако это действительно так. Потому что проблема не в самом API, а в том, как его дальше стали использовать. Создаётся такое впечатление, что разработчик(и) API имели насчёт него какие-то планы на будущее, либо просто некие смутные прозрения, но не сделали простого и логичного следующего шага: создания набора MVC-классов. Вероятно, причиной тому – отсутствие до недавнего времени единого интерфейса работы с БД.

После просмотра великого множества самописных админок становится ясно, что, вне зависимости от сложности и особенностей задачи, процесс построения админки включает в себя одни и те же шаги, которые я описал выше. А значит, и код везде один и тот же, остаётся только менять входные данные. Можно выделить следующие сущности пока не привязываясь к коду:

  1. Конфиг интерфейса: список полей, который будет использоваться для формирования фильтров, колонок таблицы списка или набора инпутов на странице редактирования.
  2. Класс-представление для вывода интерфейса. На входе он должен получать конфиг, «под капотом» у него будет вся та логика, которую мы видим в «рыбе» от битрикса, на выходе он выдаст отрисованную страничку.
  3. Виджет. Содержит в себе логику работы отдельного поля админки. В списке с его помощью отрисовываются ячейки таблицы, на странице редактирования – поля элемента.

Справедливости ради надо сказать, что отголоски этой концепции видны в исходном коде битрикса: в частности, «пользовательские типы», которые есть как для инфоблоков, так и для «Highload»-инфоблоков – не что иное, как «виджеты» в вышеописанной схеме.

Реализовав вышеописанные классы, мы могли бы существенно сократить «рыбу» от битрикса до чего-то подобного:

$fields = include(‘fields.conf.php’);
$adminListHelper = new MyHelper($fields);
$adminListHelper->buildList(array($by => $order));
require($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/main/include/prolog_admin_after.php");
$adminListHelper ->createFilterForm();
$adminListHelper ->show();
require($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/main/include/epilog_admin.php");

В этом семистрочном куске кода проглядываются основные шаги по созданию админки, описанные в начале статьи. Но вместо того, чтобы копировать этот, пусть и короткий, сниппет каждый раз, лучше потрудиться ещё немного и сделать вот что:

  • вышеуказанный код прописать в специальном файле route.php, на который будут перенаправляться все запросы к административному интерфейсу, созданному через нашу надстройку над битриксовым API;
  • в файле с описанием конфига интерфейса выполнять регистрацию этого конфига в какой-либо глобальной переменной или статической переменной класса;
  • при обращении к страницам административного интерфейса использовать не прямые URL, а псевдонимы и функции, конструирующие правильный URL из этих псевдонимов;
  • в итоге, все запросы придут в route.php, который и разберётся, какой класс нужно создавать, какой конфиг интерфейса в него передавать, и как всё это выводить.

В итоге, код, необходимый для создания базовых страниц списка и редактирования, сокращается в разы, и, поскольку речь уже не идёт о сотнях строк, я могу привести его здесь:

Класс списка
class TableListHelper extends AdminListHelper
{
    static protected $model = 'MyModelTable';
    static public $module = 'my.module';
    static protected $viewName = 'table_list';
    static protected $editViewName = 'table_detail';
}


Класс страницы редактирования
class TableEditHelper extends AdminEditHelper
{
    static protected $model = 'MyModelTable';
    static public $module = 'my.module';
    static protected $listViewName = 'table_list';
    static protected $viewName = 'table_detail';
}


Настройки интерфейса
AdminBaseHelper::setInterfaceSettings(
    array(
        'FIELDS' => array(
            'ID' => array(
                'WIDGET' => new NumberWidget(),
                'TITLE' => 'ID',
                'TAB' => 'TAB_ONE'
            ),
            'STRING' => array(
                'WIDGET' => new StringWidget(),
                'TITLE' => 'STRING',
                'TAB' => 'TAB_ONE'
            ),
            'NUMBER' => array(
                'WIDGET' => new NumberWidget(),
                'TITLE' => 'NUMBER',
                'TAB' => 'TAB_ANOTHER'
            ),
            'TEXT' => array(
                'WIDGET' => new TextAreaWidget(),
                'TITLE' => 'TEXT',
                'TAB' => 'TAB_ANOTHER'
            )
        ),
        'TABS' => array(
            'TAB_ONE' => Loc::getMessage('TAB_ONE'),
            'TAB_ANOTHER' => Loc::getMessage('TAB_ANOTHER'),
        )
    ),
    array(
        '\TableEditHelper',
        '\TableListHelper'
    ),
    'my.module'
);


Файл menu.php
$menu = array(
    array(
        "parent_menu" => "global_menu_services",
        "section" => "table",
        "sort" => 140,
        "text" => Loc::getMessage('TABLE_MENU_TEXT'),
        "title" => Loc::getMessage('TABLE_MENU_TITLE'),
        "icon" => "table_menu_icon",
        "page_icon" => "table_page_icon",
        "items_id" => "menu_table",
        "url" => TableEditHelper::getListPageURL(),
        "more_url" => array(
            TableListHelper::getEditPageURL()
        ),
    ),
);

return $menu; 


Никаких сотен строк копипаста и копирования файлов в /bitrix/admin – а в результате получаем вполне рабочую админку для таблицы с четырьмя колонками: страницу списка, страницу редактирования и ссылки на них в меню системы. С поддержкой CRUD-операций «из коробки». С нормальным роутингом (в данном примере – /bitrix/admin/route.php?module=my.module&view=tab_list откроет страницу списка. Можно доработать это до «ЧПУ», если есть желание или необходимость). Дальше только переопределяем методы базовых классов, чтобы кастомизировать её поведение под свои задачи. Выглядит заманчиво, не правда ли?

Будущее уже здесь!


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

github.com/DigitalWand/digitalwand.admin_helper

Модуль может работать как с полностью кастомными таблицами, так и с таблицами, созданными через битриксовый функционал «Highload»-инфоблоков, при этом вместо «виджетов» есть возможность использовать классы «пользовательских свойств». Таким образом, весь функционал, доступный в админке «Highload»-инфолоков, доступен и нам, только теперь мы можем без труда кастомизировать его под свои нужды.

Также должен предупредить читателей, что в данной статье был умышленно использован «старый стиль» работы с модулем из его первой версии, дабы нагляднее продемонстрировать внутренний механизм его работы. В последних версиях в классах-хэлперах достаточно указать только модель – всё остальное модуль определит сам.

Хочется завершить статью словами благодарности авторам Qt Framework, который вдохновил на стремление к прекрасному и в вебе, пожеланием успеха тем, кто сейчас активно развивает этот модуль, а также надеждой, что когда-нибудь писать под Битрикс станет не только выгодно, но и приятно.

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