Собираем и устанавливаем первую версию пакета

На прошлом занятии мы определились с примерным функционалом, написали схему таблиц и сгенерировали модель xPDO для работы с БД MySql.
А сегодня нам нужно собрать и установить первую версию пакета и разобраться, как работают Custom Manager Pages (CMP).
Учитывая, что мы используем заготовку modExtra, и уже разобрали, как она работает - сборка пакета заключается в выполнении скрипта build.transport.php на сервере.
Если с конфиге build.config.php выставлена константа PKG_AUTO_INSTALL, то компонент будет сразу установлен на сайт.
Итак, я запускаю http://c2263.paas2.ams.modxcloud.com/Sendex/\_build/build.transport.php и в конфиге у меня включена автоустановка, поэтому сразу после сборки пакета им уже можно пользоваться.
Иначе нужно было бы зайти в управление пакетами, поискать их локально, и установить. Пакеты я собираю часто, и делать это каждый раз давно надоело.
Давайте теперь разберем, как же работает CMP - то есть, наш новый раздел админки Sendex.

Меню

Все меню MODX состоят из двух частей: собственно пункт меню и действие, которое он вызывает. Как и всё в Revolution, меню и действие - тоже объекты, и мы устанавливаем их в файле transport.menu.php.
Видите, там в массиве с пунктами меню (у нас он один) modMenu, есть и ключ action - вот это и есть параметры для создаваемого modAction.
Если мы захотим туда добавить еще одни пункт, то будет примерно так:

$tmp = array(
    'sendex' => array(
        'description' => 'sendex_menu_desc',
        'action' => array(
            'controller' => 'index',
        ),
    ),
    'another_menu' => array(
        'description' => 'Мое описание',
        'action' => array(
            'controller' => 'а здесь файл-контроллер',
        ),
    ),

);
Еще мы можем указать, в каком родительском меню будет находиться наш пункт - это параметр parent, и в нашем случае он стандартный - components. А вот у miniShop2, который изначально планировался под расширения, меню находится не в components, корне - вот, посмотрите на его transport.menu.php. Первый пункт там имеет parent = '', а следующие уже parent = 'minishop2'.
Для таких случаев нужно еще использовать параметр handler - это javascript функция, которая будет вызвана при клике на пункт меню. Для основного пункта меню MS2, который является просто контейнером подменю, это return false; - то есть, не делать ничего при клике.
Но у нас случай простой, можно сказать классический - один CMP и один пункт меню, который лежит в components.
Как правило (не не обязательно), modMenu связан с modAction - его цель состоит в запуске определённого контроллера. Поэтому с массиве с action мы указываем пункт controller.

Настройка для разработки

Прежде чем перейти к разбору контроллера, нужно сделать кое-какие настройки, чтобы нам было удобно работать на удалённом сервере.
Напоминаю, что весь наш проект находится в директории Sendex в корне сайта. А только что установленный пакет распаковался в /core и /assets и теперь, если мы что-то меняем - то это синхронизируется с директорией в корне, а на установленные файлы никак не влияет.
И тут у нас 2 варианта: после каждого изменения собирать и устанавливать пакет, или научить MODX загружать скрипты из директории /Sendex.
Конечно, второй вариант предпочтительнее, поэтому идём в СистемаПространства имён и делаем так: Это позволит MODX обращаться за исходниками в нашу директорию. А теперь нужно создать еще системные настройки sendex_core_path и sendex_assets_url (а демонстрационную настройку sendex_some_setting можно удалить): -
sendex_assets_path необязательна, но тоже может пригодиться. Эти настройки нужны нам, чтобы знать, где искать наши файлы.
В принципе, можно не создавать их, а каждый раз доставать modNamespace, но это не очень удобно.
С этого момента мы можем получать рабочие директории проекта вот так:

$corePath = $this->modx->getOption('sendex_core_path', $config, $this->modx->getOption('core_path') . 'components/sendex/');
$assetsUrl = $this->modx->getOption('sendex_assets_url', $config, $this->modx->getOption('assets_url') . 'components/sendex/');
Ну и последний штрих: нужно добавить
ini_set('display_errors', 1);
ini_set('error_reporting', -1);
в системные файлы MODX: /index.php и /manager/index.php - они нужны нам для того, чтобы MODXCloud не прятал от нас ошибки во время разработки.
Забегая немного вперед, кажу еще что нужно подредактировать /assets/components/sendex/connector.php, потому что он загружает MODX при запросах из админки, и должен уметь работать и при обычном расположении файлов, и при разработке в директории Sendex. Лично я и здесь добавляю вывод ошибок, чтобы видеть ругань при ajax запросах из админки. В итоге у меня получился вот такой коннектор, советую скопипастить.

Контроллеры CMP

Итак, при клике на пункт меню, MODX смотрит, с каким действием тот связан и на какой контроллер ссылается, после чего вызывает этот файл.
Контроллер - это специальный php файл, лежащий в директории core компонента, и наследующий modManagerController. Указывается он просто по имени: если версия MODX У нас контроллер лежит в /core/components/sendex/index.class.php, поэтому в modAction указан index. Учитывая наши настройки для разработки, первым делом меняем загрузку класса Sendex и приводим файл вот к такому виду. Теперь нам не нужно каждый раз собирать пакет, и все изменения сразу будут видны.
Ну а сейчас внимательно смотрим на файл и пытаемся въехать в логику. 1. MODX нужен контроллер index, и в нём он ищет для запуска IndexManagerController - это делается автоматически
  1. IndexManagerController наследует абстрактный класс SendexMainController со всеми его методами
  2. SendexMainController наследует modExtraManagerController со всеми его методами
  3. modExtraManagerController является общим классом всех CMP и содержит основную логику работы. Он запускает свой метод initialize()
  4. Этот метод переопределён в дочернем классе SendexMainController, поэтому запускается оттуда.
  5. Мы уже смотрим наши системные настройки и подгуржаем класс Sendex из директории, указанной в системных настройках.
  6. Создаётся новый экземпляр класса Sendex, при этом в PHP 5 вызывается его метод __construct() - и у нас он задаёт переменную Sendex::config, в которую забивает массив с настройками и путями к файлам.
  7. Пути к файлам определяются точно так же, с помощью системных настроек. А значит, мы грузим их из директории /Sendex/...
  8. В этот момент мы уже можем обращаться к любым методам и свойствам класса Sendex, включая $sendex->config
  9. Где-то в глубинах modExtraManagerController уже был добавлен класс modX, и мы можем к нему обращаться через $this->modx
  10. Ну а теперь мы комбинируем конфиг Sendex и методы modX для подключения нужных нам скриптов и стилей их директорий компонента
  11. После того, как мы подключили всё, что нам нужно, передааём дальнейшую логику родительскуму классу - пусть делает, что хочет.
  12. А он хочет узнать, какой дальше загрузить контроллер и класс IndexManagerController говорит - home
  13. Этот класс уже будет загружен из директории /Sendex/core/components/sendex/controllers/home.class.php
Все это вам знать не обязательно! Такая логика используется в 90% всех новых дополнений, и сводится она к тому, чтобы вызвать основной контроллер, тот загрузил основные файлы компонента и передал управление дочернему контроллеру. А тот уже загрузит всё нужное для конкретной страницы компонента.
Обычно у компонента используется одна страница, поэтому все эти сложности можно пропустить мимо ушей. По большому счету, вам вообще больше не понадобится залазить в index.class.php, работать будем только с controllers/home.class.php.
Честно говоря, я и сам не до конца понимаю эту цепочку контроллеров и кто кого загружает, поэтому делаю просто по отлаженной схеме.
Еще одно замечание: у меня в modExtra скрипты и стили загружаются по старинке, методами:

$this->modx->regClientCSS()
$this->modx->regClientStartupScript()
$this->modx->regClientScript()
$this->modx->regClientStartupHTMLBlock()
Если вы хотите, чтобы ваш компонент дружил с AjaxManager - их нужно заменить на

$this->addCss()
$this->addJavascript()
$this->addLastJavascript()
$this->addHtml()
При этом нужно перенести инициализацию страницы из /assets/components/sendex/js/mgr/sections/home.js в home.class.php, иначе при открытии страницы будет ошибка.
Лично я всё это сделал, поэтому просто сверяем и копипастим мои файлы: index.class.php, home.class.php и home.js.
Если вы всё сделали правильно, можно на всякий случай синхронизировать проект, почистить везде кэши и зайти на страницу Sendex в админке: Это страница от modExtra, она вызывает его контроллеры, с его объектами (которые мы удалили), поэтому в логе MODX должны появиться ошибки: Это нормально, они пропадут, когды мы перепишем процессоры.
Можно также и проверить, сраюатывают ли изменения при синхронизации проекта с сервером? Пишем в index.class.php сразу после <?php: <br?>
echo 'Hello world';die;
Сохраняем и обновляем страницу в админке: Если вы видите то же, что и я - всё хорошо.
Кстати, при последующей сборке и установке пакета наш namespace будет перезаписан на стандартный, поэтому я внес пару измений в установщик. Видите, как просто работать с установщиком? Советую сделать так же.

Основные методы контроллеров

Нужно еще немного рассказать о методах контроллера home.class.php, который будет у нас основным рабочим. getPageTitleВывод текста для тега title страницы CMP. Сейчас там выводится sendex из лексикона, поэтому мы видим его в заголовке:
getTemplateFileЭтот метод отдаёт html шаблон страницы, который будет распарсен Smarty. Учитывая, что вся админка сделана на ExtJS, на файл-шаблон /core/components/sendex/elements/templates/home.tpl выглядит так:
<div id="sendex-panel-home-div"></div>
Можете сразу про него забыть, он просто выводит тег, который будет использован для отрисовки компонента ExtJs.
getLanguageTopicsЭтот метод возвращает массив словарей, которые будут использованы в работе компонента
checkPermissionsПроверять или нет права на доступ к странице. Мы можем указать их в настройке меню: чтобы закрыть страницу от кого-то. Например, можно указать save_document, чтобы пускать только юзеро с правом редактирования документов.
loadCustomCssJsОсновной метод контроллера, именно он загружает все нужные скрипты и стили для работы страницы. В него мы еще будем добавлять всякое.

Заключение

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