"Классные" процессоры в MODX 2.2
Предлагаю вам свой очень вольный перевод записи из блога Mark Hamstra о новых процессорах, основанных на классах. Я буду называть их «классными» процессорами — так короче и точнее отражает их суть.
Одно из изменений в MODX 2.2 это новая, полностью переделанная система процессоров, основанных на классах («классные» процессоры), позволяющие вам cущественно упростить создание процессоров для компонентов. Как и любая обновка — эта позволяет вам использовать несколько новых фокусов.
Я использовал эти процессоры в своем новом проекте, и теперь хочу распространить эти наработки где только можно.
Что такое «классные» процессоры?
Если вы уже создавали компоненты для MODX, то вы знаете, что такое процессор — это php файл, который запускается AJAX коннектором (или modX::runProcessor), делает, все что от него требуется и возвращает ответ в виде JSON строки.
«Классные» процессоры упрощают эту работу. Например, вы можете использовать подобный код для обновления какого-то элемента:
Что делает modObjectUpdateProcessor?
Несмотря на минимум кода, который мы использовали, для его корректого выполнения происходит много всяких неочевидных процессов.
Разбираясь в классе modProcessor, я нашел 14 этапов для выполнения modObjectUpdateProcessor
Расширяем наш процессор для обработки чекбоксов
Когда вы сделали замечательное окно на modExt/ExtJS и прилепили туда активный чекбокс (xtype: 'checkbox') — оно может работать не так, как вы задумали. Возможно, ваша схема БД ожидает булево true или false (я знаю — у меня так!), которое будет сохранено в БД в поле с типом tinyint 1 или 0. А вот форма вам отправит поле чекбокса как «on», если он отмечен, или пустоту — если нет. Оба-на…
К счастью, у нас есть много функций, чтобы расширить процессор update и привести значение вредного чекбокса к нужному виду, для сохранения в объект через fromArray() как полагается.
Посмотрите снова на список из 14 шагов. Метод fromArray() запускается на 6м этапе, значит, нам надо поменять значение чекбокса до него. Как вы смотрите на «BeforeSet», в 5м этапе? По моему, должно сработать!
Если вы не особо знакомы с объектно-ориентированным программированием (ООП), то вся эта писанина может показаться вам непонятной фигней. На самом деле, мы просто переписываем родные методы стандартного процессора своими, чтобы изменить его как нам надо. В интернете навалом книжек по ООП в PHP, которые вы можете почитать, если интересно.
Итак, теперь мы меняем наш первый пример, чтобы изменить значение чекбокса при помощи «beforeSet»:
Мы можем сделать что-то подобное и с процессором remove, для предотвращения удаления элемента, если он все еще связан с другим элементом.
Например, следующий процессор не удалит объект «fdmCharacteristic», если тот еще связан с объектами «fdmRoomTypeCharacteristic».
Я использовал тип «checkbox», хотя в MODX с версии 2.1 есть тип «xcheckbox», который присылает значение (1 или 0, если мне не изменяет память). Не знаю, почему я не использовал его, но наверняка «классный» процессор будет работать и с ним. Просто не проверял, и я люблю все контролировать.
«Просто интересно… Вы работаете над компонентом по управлению недвижимостью?»
Как вы догадались?! :)
Что меня выдало: объект «fdmRoomType», «fdmRoomTypeCharacteristic» или «fdmProperty»? :)
Супер-пупер компонент, над которым я сейчас работаю называется FrontDeskMan и я надеюсь закончить его в течении нескольких недель. Он не будет доступен как Open Source, но наверняка мы будем продавать на енго лицензии и поддерживать его, как расширение для индустрии туризма и развлечений в моем совместном проекте с Jared Loman.
Вы уже можете готовить клиентов местных отелей к новому вебсайту с интегрированным управлением комнатами и бронированием, когда мы стартуем!
Одно из изменений в MODX 2.2 это новая, полностью переделанная система процессоров, основанных на классах («классные» процессоры), позволяющие вам cущественно упростить создание процессоров для компонентов. Как и любая обновка — эта позволяет вам использовать несколько новых фокусов.
Я использовал эти процессоры в своем новом проекте, и теперь хочу распространить эти наработки где только можно.
Что такое «классные» процессоры?
Если вы уже создавали компоненты для MODX, то вы знаете, что такое процессор — это php файл, который запускается AJAX коннектором (или modX::runProcessor), делает, все что от него требуется и возвращает ответ в виде JSON строки.
«Классные» процессоры упрощают эту работу. Например, вы можете использовать подобный код для обновления какого-то элемента:
<?php
class fdmPriceCodeUpdateProcessor extends modObjectUpdateProcessor {
public $classKey = 'fdmPriceCode';
public $languageTopics = array('frontdeskman:pricing');
public $objectType = 'fdmp';
}
return 'fdmPriceCodeUpdateProcessor';… и MODX сделает всю работу за вас.Что делает modObjectUpdateProcessor?
Несмотря на минимум кода, который мы использовали, для его корректого выполнения происходит много всяких неочевидных процессов.
Разбираясь в классе modProcessor, я нашел 14 этапов для выполнения modObjectUpdateProcessor
- Стартует ваш процессор, устанавливаются его свойства.
- Запускается функция checkPermissions(), который вы можете использовать для проверки любых прав доступа.
- Запускается функция getLanguageTopics(), который возвращает массив языковых словарей, затем они загружаются.
- Стартует функция initialize(): она берет первичный ключ (из публичной переменной primaryKeyField, обычно это «id»), затем достает объект нужного класса по этому ключу, и, если это наследник modAccessibleObject, проверяет юзера на соответствие политике «save». Если initialize() не возвращает true — запрос отменяется, с ошибкой.
- Стартует beforeSet(): по умолчанию он просто проверяет, не установлено ли ошибок (например, так — $this->addFieldError('fieldname','error')). Если эта функция не возвращает true — запрос завершается с ошибкой.
- Вызывается fromArray() из объекта (он доступен через $this->object) для присвоения поученных при запуске свойств.
- Запускается beforeSave(). Как и beforeSet(), этот метод проверяет наличие ошибок. Если он не возвращает true — запрос возвращает сообщение об ошибке.
- Объект вызывает validate(). Если эта функция не возвращает положительное значение — выставляются ошибки в соответствие с проверенными значениями.
- Если вы выставили переменную beforeSaveEvent в своем классе, вызывается выставленное событие (invoke event) и если не возвращается true — сохранение не происходит. Учитывая, что fireBeforeSaveEvent() отмечена как публичная, обычно вам не нужно выставлять эту переменную.
- Вызывается saveObject(), которая просто делает save() объекта. В соответствии с комменариями в коде, эта функция может быть переназначена для каких-то особенных изменений объекта.
- Запускается fireAfterSaveEvent(), которая вызывает событие, указанное в переменной afterSaveEvent класса.
- Срабатывает logManagerAction(), для записи стандартного сообщения в Журнал системы MODX (вы его найдете в «Отчеты» → «Журнал системы управления»).
- Возвращается сообщение об успешном завершении процесса через функцию cleanup(). Она, на самом деле, просто оборачивает $modx->error->success/failure, который использовался в процессорах до версии 2.2.
- Ответ (успех или ошибка) парсится в объект modProcessorResponse и возвращается вам.
Расширяем наш процессор для обработки чекбоксов
Когда вы сделали замечательное окно на modExt/ExtJS и прилепили туда активный чекбокс (xtype: 'checkbox') — оно может работать не так, как вы задумали. Возможно, ваша схема БД ожидает булево true или false (я знаю — у меня так!), которое будет сохранено в БД в поле с типом tinyint 1 или 0. А вот форма вам отправит поле чекбокса как «on», если он отмечен, или пустоту — если нет. Оба-на…
К счастью, у нас есть много функций, чтобы расширить процессор update и привести значение вредного чекбокса к нужному виду, для сохранения в объект через fromArray() как полагается.
Посмотрите снова на список из 14 шагов. Метод fromArray() запускается на 6м этапе, значит, нам надо поменять значение чекбокса до него. Как вы смотрите на «BeforeSet», в 5м этапе? По моему, должно сработать!
Если вы не особо знакомы с объектно-ориентированным программированием (ООП), то вся эта писанина может показаться вам непонятной фигней. На самом деле, мы просто переписываем родные методы стандартного процессора своими, чтобы изменить его как нам надо. В интернете навалом книжек по ООП в PHP, которые вы можете почитать, если интересно.
Итак, теперь мы меняем наш первый пример, чтобы изменить значение чекбокса при помощи «beforeSet»:
class fdmPriceCodeUpdateProcessor extends modObjectUpdateProcessor {
public $classKey = 'fdmPriceCode';
public $languageTopics = array('frontdeskman:pricing');
public $objectType = 'fdmp';
public function beforeSet() {
$this->setProperty('active',($this->getProperty('active') == 'on'));
return parent::beforeSet();
}
}
return 'fdmPriceCodeUpdateProcessor';Здесь мы использовали тернарный оператор вместе с функциями getProperty и setProperty от процессора. Но если вам нравится более подробный код — можно написать иначе, на результат это никак не влияет:/* ... */
public function beforeSet() {
$active = $this->getProperty('active');
if ($active == 'on') { $active = true; }
else { $active = false; }
$this->setProperty('active',$active);
return parent::beforeSet();
}
/* ... */Мы можем сделать что-то подобное и с процессором remove, для предотвращения удаления элемента, если он все еще связан с другим элементом.
Например, следующий процессор не удалит объект «fdmCharacteristic», если тот еще связан с объектами «fdmRoomTypeCharacteristic».
<?php
class CharacteristicRemoveProcessor extends modObjectRemoveProcessor {
public $classKey = 'fdmCharacteristic';
public $languageTopics = array('frontdeskman:property');
public $objectType = 'rtc';
public function beforeRemove() {
$chars = $this->modx->getCount('fdmRoomTypeCharacteristic',array('characteristic' => $this->getProperty('id')));
if ($chars > 0) {
return $this->modx->lexicon('rtc.remove.roomtypesStillLinked');
}
return parent::beforeRemove();
}
}
return 'CharacteristicRemoveProcessor';Последний пример на сегодня расширяет modObjectGetListProcessor для присоединения (join) таблицы и фильтрации по ее свойствам. Мы этого добиваемся изменением функции prepareQueryBeforeCount(). Так же, мы присваиваем еще немного переменных.<?php
class RoomTypeGetListProcessor extends modObjectGetListProcessor {
public $classKey = 'fdmRoomType';
public $languageTopics = array();
public $defaultSortField = 'name';
public $defaultSortDirection = 'ASC';
public $objectType = 'fdmRoomType';
/**
* @param xPDOQuery $c
* @return \xPDOQuery
*/
public function prepareQueryBeforeCount(xPDOQuery $c) {
$query = $this->getProperty('property');
if (!empty($query) && is_numeric($query)) {
$c->innerJoin('fdmRoomTypeProperty','RTP','fdmRoomType.id = roomtype');
$c->where(array(
'RTP.property' => (int)$query
));
}
return $c;
}
}
return 'RoomTypeGetListProcessor';Замечание про чекбоксы в modExtЯ использовал тип «checkbox», хотя в MODX с версии 2.1 есть тип «xcheckbox», который присылает значение (1 или 0, если мне не изменяет память). Не знаю, почему я не использовал его, но наверняка «классный» процессор будет работать и с ним. Просто не проверял, и я люблю все контролировать.
«Просто интересно… Вы работаете над компонентом по управлению недвижимостью?»
Как вы догадались?! :)
Что меня выдало: объект «fdmRoomType», «fdmRoomTypeCharacteristic» или «fdmProperty»? :)
Супер-пупер компонент, над которым я сейчас работаю называется FrontDeskMan и я надеюсь закончить его в течении нескольких недель. Он не будет доступен как Open Source, но наверняка мы будем продавать на енго лицензии и поддерживать его, как расширение для индустрии туризма и развлечений в моем совместном проекте с Jared Loman.
Вы уже можете готовить клиентов местных отелей к новому вебсайту с интегрированным управлением комнатами и бронированием, когда мы стартуем!
Комментарии: 48
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
«каждый „классный“ процессор (create, update, getlist, remove и get) имеет собственные и методы» — думаю «и» не нужно >> «имеет свои собственные методы»
Дочитайте до конца и давайте все правки сразу, списком =)
все дочитал, перевод удачный, сравнивал некоторые отрывки с оригиналом, в общем понравилось, спасибо! :)
хотя, признаюсь, материал понятен пока на 70-80%, нужно будет еще почитать и вернуться, когда будет писать свои компоненты :)
пока увидел только еще одно исправление — «Когда вы сделали замечательно окно на modExt/ExtJS» — наверно, «замечаетльное»
Плюс еще у меня $objectType никак не влияет на значения в лексиконе. Задаю к примеру $objectType = 'my_cmp.message' и $_lang['my_cmp.message_id'] = 'ID сообщения'. В табличке вывожу заголовок header: _('id') — получается пустой. А если выводить header: _('my_cmp.message_id') — он выводится.
И смотрю в консоли браузера что выводится коннектором.
Про лексикон ничего не могу сказать, тут нужно разбираться, а мне некогда.
Нашел в ошибках, что этот процессор запрашивает таблицу, начинающуюся на modx_ хотя у меня в схеме без него. В сниппетах все прекрасно работало. Подскажите, пожалуйста, где убирается этот префикс?
Это перевод, а не лично мой опыт работы. Я пока только getlist пробовал погонять по исходникам самого MODX, проблем с префиксом не было, все нормально работало.
Могу посоветовать поглядеть продвинутые компоненты, которые используют эти «классные» процессоры.
Articles например — https://github.com/splittingred/Articles/tree/develop/core/components/articles/processors
А $objectType только на ошибки в процессоре влияет.
Или в них модель отдельно подключали?
Вот исходник автоапдейта системных параметров — https://github.com/modxcms/revolution/blob/develop/core/model/modx/processors/system/settings/updatefromgrid.class.php
Процессору шлется строка data, он его принимает, проверяет, преобразует из JSON в массив и выставляет переменные объекта.
Очень красиво.
Вот нормальная:
goo.gl/BgZfm
Пытаюсь получить только данные из двух столбцов таблицы, пишу вот в этот метод след. код
public function prepareQueryBeforeCount(xPDOQuery $c)
$c->select(array('id','pagetitle'));
Все равно выбираются все столбцы из таблицы. Я уже и называние класса добавлял перед названием столбца, тот же результат. Может быть у вас есть решение?
Т.к. только из-за этого пока не могу полностью перейти на классные процессоры
А время отклика через «классный» процессов выше (т.к. там куча проверок и событий) чем через процедурный.
Покажутся все столбцы, так как это записано в модели, но реально выберутся данные только из указанных — остальные должны быть пусты.
Дальше да, prepareRow() и всех делов.
Ниже иллюстрации.
i26.fastpic.ru/big/2012/1011/f5/4421f51f21031820061407016b0e33f5.png
i26.fastpic.ru/big/2012/1011/5b/f592963e8b248c963eb4d64c97861f5b.png
SELECT `id`,`pagetitle`,`parent` FROM `modx_site_content` AS `modResource` WHERE ((`modResource`.`published` = 1 AND `modResource`.`deleted` = 0) AND `modResource`.`id` IN (13,14,52)) ORDER BY `modResource`.`menuindex` ASC LIMIT 20
То есть, SQL запрос в getCollection попадает верный, а вот почему оно выбирает все столбцы, вместо указанных — мне не ведомо.
Проверил — и в сниппете так же, и в методе getIterator(). Похоже, с каких-то пор они просто игнорируют SELECT.
То ли баг, то ли так и надо.
Раньше точно работало — проверял. Сейчас нет времени разбираться.
версия MODX 2.2.5
Проблема явно не в кэш, там надо исходники копать. Правда, не ясно зачем, если вопрос уже решен?
Да id=1 это site_start, дефолтная установка 2.2.5
$modx->getCriteria($className, $criteria, $cacheFlag);
Т.е. обратный процесс методу toSQL()
pastebin.com/j7JBtHLr
i25.fastpic.ru/big/2012/1011/54/b8fc5426ff408e5e197906a73643c954.png
Сделал innerJoin как в последнем примере. Смотрю в консоли — поля связанной таблицы по-прежнему не возвращает. Может быть нужно в $c->select прописывать в классе getlist?
Пробовал и такой вариант:
и такой
Может быть определяю неверно, может не в той функции определяю. Подскажи, пожалуйста.
То есть:
Если закомментировать строку с select, то выводится это
Функция целиком выше.
xPDO такие ошибки пишет в системный журнал.
как раз получилось.
Только поле name таблицы TdTypes затерло поле name таблицы TdItems при выводе. Видимо в алиасах дело. Поразбираюсь, спасибо за помощь.
Будет дамп SQL запроса, который можно загонять для проверки в phpmyadmin.
Эта процедура дает такой запрос
Лично я не знаю более элегантного решения объединить 3 таблицы в одной выборке, чем join.
Кстати, можно немного упростить, выбирая поля псевдонимами: