Николай Ланец
10 февр. 2015 г., 1:21

Все, что вы хотели знать о процессорах, но боялись спросить

Про процессоры у нас написано много, но вот этот комментарий натолкнул меня на мысль, что некоторые вообще не представляют себе что такое MODX-процессоры и с чем их едят. Учитывая то, что практически все наши разработки (в том числе и сборка ShopModxBox) основываются на работе процессоров, я решил написать эту статью, в которой постараюсь максимально подробно раскрыть тему процессоров. Если вы не понимаете процессоров, у вас никогда не получится нормально тюнинговать сборку ShopModxBox под себя, так что советую максимально четко изучить данный материал (для этого будет приведено множество примеров). Обязательно попробуйте выполнить представленные примеры самостоятельно и понять как они работают. Освоите — многое для вас станет понятней и проще.
Сразу скажу, что понимание принципов ООП сильно поможет вам в освоении этого материала, так что если у кого пока нет знаний в php-ООП, советую к изучению вот эту страничку.
Для начала выполним простейший скрипт (здесь и далее скрипты выполнять будем в компоненте Console в админке ShopModxBox (чтобы точно все примеры работали)).
<?php print '<pre>'; ini_set('display_errors', 1); $modx->switchContext('web'); $action = 'web/catalog/products/getdata'; $ns = 'modxsite'; $params = array( "limit" => 6, ); if(!$response = $modx->runProcessor($action, $params , array( 'processors_path' => $modx->getObject('modNamespace', $ns)->getCorePath().'processors/', ))){ print "Не удалось выполнить процессор"; return; } print_r($response->getResponse());

В ответ мы получим примерно такой ответ:
Array ( [success] => 1 [message] => [count] => 6 [total] => 6 [limit] => 6 [page] => 0 [object] => Array ( [134] => Array ( [id] => 134 [type] => document [contentType] => text/html [pagetitle] => Toshiba Satellite C50-A-K7K 15,6" [longtitle] => .......................
Наблюдательные читатели могли заметить схожесть параметров в примере с вызовом процессора в смарти, а именно:
$action = 'web/catalog/products/getdata'; $ns = 'modxsite'; $params = array( "limit" => 6, );

и
{processor action="web/catalog/products/getdata" ns="modxsite" params="limit=`6`" assign=result}
И это не случайно. Фактически, вызывая процессор в Смарти, мы вызываем представленный код, а точнее метод $modx->runProcessor($processor, $params, $options); Так или иначе, и в одном и в другом случае мы получим ответ в одном и том же формате — массиве (на самом деле ответ может быть не только в виде массива, но мы будем рассматривать здесь стандартный ответ).
Для начала разберем вызов процессора.
$action = 'web/catalog/products/hot/getdata'; $action — это путь до вызываемого процессора. В нашем случае это вот этот процессор. На то, в какой папке будет выполняться поиск процессора, отвечает элемент 'processors_path', который содержит путь до папки процессоров.
Здесь для нас важную роль играет параметр $ns = 'modxsite'; Это название пространства имен MODX-а (как правило пространства имен создаются автоматически при установке компонентов, а управление ими доступно в главном меню Настройки — Пространства имен).
В нашем случае мы получаем путь до процессоров неймспейса modxsite (в целом, можно использовать синоним Компонент, но всетаки компонент и неймспейс — это не одно и то же). Бывает, что у нас вызываются процессоры и из других компонентов, например здесь вызывается процессор из компонента basket, который, как наверняка многие знают, отвечает за работу корзины.
И третий параметр — $params, который содержит передаваемые в процессор параметры. Внутри процессора эти параметры будут доступны через метод $this->getProperty() или в массиве $this->properties. В нашем случае, передав в процессор параметр 'limit' => 6, мы ему «указали», что надо получить максимум 6 записей.
А теперь попробуйте в своем Смарти-шаблоне прописать такой код:
{processor action="web/catalog/products/getdata" ns="modxsite" params="limit=`6`" assign=result} <pre> {print_r($result, true)} </pre>
Теперь на своей странице, где будет вызван этот код, вы получите такой же ответ, как и в админке в консоли. Вот именно с этим ответом чаще всего и приходится работать, в нем содержится информация о полученных данных и их, как правило, мы используем для того же вывода списка товаров.
Давайте рассмотрим данные ответа.
Сначала стандартные элементы: success => 0 || 1. Флаг успешного или не успешного выполнения процессора. Под не успешным подразумевается случай, когда процессор выполнен, но в нем возникли ошибки (например, мы прописали проверку каких-то обязательных полей, и обрабатывая запрос при отсутствии необходимых данных возвращаем ошибку с сообщением заполнить необходимые поля). message. Сообщение, возвращаемое процессором (чаще всего методами $this->success($message) или $this->failure($message)). Сообщение может отсутствовать. count — количество полученных записей. total — количество всего записей, соответствующих условиям выборки данных. limit — лимит на количество получаемых записей. object — массив данных полученных записей.
Это были стандартные поля, которые как правило возвращаются любым MODX-процессором. Параметр page — это уже добавленный нами в процессоры компонента modxsite, чтобы удобней было работать с постраничностью. Общее количество записей (total), количество записей на одну страницу (limit) и номер текущей страницы (page) — это все то, что нам нужно знать для формирования постраничности. К слову, если вызывать шаблон постраничности pagination.tpl, передав в него полученный ответ процессора $result, вам будет сформирован HTML-код постраничности. Пример реализации можно подсмотреть здесь.
Так откуда же растут ноги у этих процессоров?
На самом деле все MODX-процессоры так или иначе берут начало от главного класса — modProcessor. Все базовые MODX-процессоры прописаны в modprocessor.class.php. Перечислим их:
modProcessor. Этот класс является абстрактным (объявлен как abstract class modProcessor), то есть его нельзя вызывать напрямую, можно только расширить его другим классом и вызывать уже тот класс. В нем прописаны все базовые методы. Разберем основные:
setProperty($k,$v). Устанавливает свойства процессора (добавляет в массив $this->properties переменные со своими значениями (за один вызов только одно значение)).
$this внутри объекта — это магическая переменная, ссылающаяся на сам объект. К примеру, если вы хотите внутри процессора получить его свойства, вы в нем прописываете $properties = $this->getProperties();
getProperty($k,$default = null). Получает свойство процессора.
unsetProperty($key). Удаляет свойство процессора. Это имеет смысл когда вы хотите избежать переопределение какого-либо свойства объекта. Дело в том, что все передаваемые в процессор параметры попадает в его свойства $this->properties, и, к примеру, если у вас выполняется обновление объекта в рамках update-процессора modObjectUpdateProcessor (который мы чуть подробней рассмотрим ниже), в свойства полученного объекта передаются полученные свойства процессора. К примеру, если вызовем процессор на обновление документа так: $modx->runProcessor('resource/update', array('id'=> 1, 'pagetitle' => 'new pagetitle'));, то будет получен объект документа с id => 1, и его заголовок (pagetitle) изменен на переданный в процессор параметр 'pagetitle' => 'new pagetitle'. Так вот, если мы в своем процессоре хотим избежать того, что кто-то передаст новый заголовок и и заголовок документа изменится, мы в функции initialize() можем прописать $this->unsetProperty('pagetitle'); Таким образом даже если кто-то и передаст в запрос параметр pagetitle, он будет удален из параметров.
setProperties($properties). Устанавливает сразу несколько свойств процессора.
getProperties(). Получает все параметры процессора (на самом деле возвращает массив $this->properties).
setDefaultProperties(array $properties = array()). Так же как и setProperties($properties), устанавливает параметры процессора, но с той лишь разницей, что он не замещает уже имеющиеся параметры. То есть если, к примеру, при вызове процессора был передан параметр sort, а в процессоре вызывается $this->setDefaultProperties(array('sort' => 'id',)), то переданный параметр sort не будет затерт устанавливаемым дефолтным значением.
checkPermissions() Проверяет доступ к вызываемому процессору. К примеру, в нем можно прописать так:
public function checkPermissions() { return $this->modx->user->id && parent::checkPermissions(); }
Таким образом только если пользователь будет авторизован (объект пользователя $modx->user содержит значение id), а так же родительский процессор вернет успех на проверку checkPermissions(), тогда только будет возвращено true, и значит процессор может выполняться. Иначе будет возвращено Access denied.
initialize(). В этом методе как правило прописывается проверка передаваемых данных, установка дефолтовых значений и т.п. В нем позволительно возвращать не только true|false, но и просто текстовое сообщение. Данное сообщение будет так же расцениваться как ошибка и будет содержаться в параметре message описанного выше формата ответа. Рассмотрим довольно типичный код:
public function initialize(){ $this->setDefaultProperties(array( "subject" => "Заказ звонка с сайта", )); if(!$this->getProperty('name')){ $this->addFieldError('name', 'Не указано имя'); } if(!$this->getProperty('phone')){ $this->addFieldError('phone', 'Не указан телефон'); } // Если есть ошибки, возвращаем ошибку с сообщением if($this->hasErrors()){ return "Не все обязательные поля заполнены"; } return parent::initiaize(); }
Здесь мы установили значение по умолчанию «subject» => «Заказ звонка с сайта» (Оно может быть переопределено входящим параметром в вызове процессора или в методе initialize() расширяющего процессора), затем проверили значения name и phone, чтобы были заполнены, и в случае если какой-то из них не заполнен ($this->addFieldError() добавляет ошибку, а $this->hasErrors() проверяет есть ли ошибки в процессоре), вернули ошибку.
success($msg = '',$object = null). Возвращает успешный ответ выполнения процессора (в массиве ответа тогда параметр success содержит true).
failure($msg = '',$object = null). Возвращает ошибку.
Методы success() и failure() как правило возвращаются в методе process().
hasErrors() как и говорилось выше, проверяет есть ли ошибка в процессоре или нет. Здесь есть одно очень важное замечание: как я уже не раз говорил, у процессоров нет собственного объекта обработки ошибок, поэтому, если у вас вазывается сразу несколько процессоров, и в каком-то из них будет ошибка, то все последующие процессоры при проверке $this->hasErrors() будут возвращать ошибку, так как на все один единый объект — $modx->error. Так что желательно после вызова процессора выполнять сброс ошибок $modx->error->reset(). В случае, если процессор вызывается в Smarty-шаблоне, такой сброс ошибок не требуется, так как сброс прописан в самом смарти-плагине (modxSmarty v1.0.0+).
addFieldError($key,$message = ''). Добавляет множественное сообщение об ошибке (удобно, к примеру, при проверке нескольких полей формы).
getLanguageTopics(). Здесь мы можем указать массив словарей, которые нужно будет MODX-у инициализировать перед выполнением процессора.
process(). Этот метод в базовом классе абстрактный, что обязывает в расширяющих процессорах прописать собственный метод process() с пользовательским кодом. Если этот метод не прописать, будет возвращена фатальная ошибка. К примеру, в расширяющем его процессоре modObjectCreateProcessor прописан свой метод process(), так что если ваш процессор расширяет его, то метод process() уже не обязательно прописывать. run(). Это самый главный метод процессора, который задает общую логику работы процессора, выполняя основные его методы в нужном порядке и обрабатывая ошибки. Давайте рассмотрим его код внимательней с комментариями.
public function run() { // Проверяем права на выполнение if (!$this->checkPermissions()) { // Если прав нет, ответ будет содержать ошибку доступа $o = $this->failure($this->modx->lexicon('permission_denied')); // Права есть, выполняем основной код } else { // Получаем массив словарей, если указан $topics = $this->getLanguageTopics(); foreach ($topics as $topic) { // Подгружаем словари $this->modx->lexicon->load($topic); } // Выполняем инициализацию процессора $initialized = $this->initialize(); // Если инициализация не вернула четко истину, // то ответ будет содержать ошибку if ($initialized !== true) { $o = $this->failure($initialized); // иначе успех } else { $o = $this->process(); } } // Получаем объект ответа процессора $response = new modProcessorResponse($this->modx,$o); // Возвращаем ответ return $response; }

Этот базовый метод задает стандарт выполнения любого MODX-процессора и его ответа. Метод run() в принципе не принято переопределять (это тот один из немногих случаев, когда я бы методу задал атрибут final).
Вот, собственно, отсюда и пляшут все расширяющие процессоры. Давайте продолжим перечислять основные.
modObjectProcessor расширяет modProcessor и так же является абстрактным классом, то есть его нельзя вызывать напрямую. Он устанавливаем базовые свойства для нескольких типовых дочерних классов, выполняющими действия с xPDO-объектами:
modObjectGetListProcessor. Получает массив xPDO-объектов (к примеру, массив пользователей).
modObjectCreateProcessor. Создает xPDO-объект (к примеру, новый документ).
modObjectUpdateProcessor. Обновляет xPDO-объект.
modObjectDuplicateProcessor. Создает копию xPDO-объекта.
modObjectRemoveProcessor. Удаляет xPDO-объект.
modObjectSoftRemoveProcessor. Обновляет xPDO-объект, отмечая его как удаленный (устанавливает свойства deleted => 1).
modObjectExportProcessor Экспорт xPDO-объекта в XML (для последующего скачивания).
modObjectImportProcessor. Импорт xPDO-объекта их XML.
Для всех Object-процессоров важен параметр $classKey, который должен содержать название xPDO-класса (в дальнейшем объекта). К примеру, если вы вызываете modObjectCreateProcessor, который должен в итоге создать новый объект пользователя (modUser), то надо в этом параметре прописать значение 'modUser'. Смотрите как это сделано в системном modUserCreateProcessor.
К слову, последние два процессора помогли бы вот в этом вопросе, но, к сожалению, в этих процессорах не прописана подгрузка зависимых объектов, что не позволяет выгрузить документ товара вместе со всеми данными товара. На досуге подумаю на счет такого механизма.
Советую посмотреть вот этот ресурс: fossies.org/dox/modx-2.3.3-pl/modprocessor_8class_8php.html /assets/images/resized/2015/1563/4e83a5ece6.jpg
/assets/images/resized/2015/1563/2e1c3890dc.jpg
/assets/images/resized/2015/1563/9b1a6ead88.jpg
Вообще, если освоить навигацию по тому ресурсу и хорошенько покопаться, можно много всего найти и узнать. К примеру, можно ощутить, что MODX со своими процессорами — это целая вселенная! /assets/images/resized/2015/1563/513a9fb28b.jpg
Здесь видна лишь маленькая часть имеющихся в MODX-е процессоров, которые так или иначе все являются предками главного класса modProcessor. Это процессоры, которые отвечают за создание/редактирование/удаление и т.п. практически всех сущностей в нем (документы, пользователи, контексты, настройки, ТВ-параметры и т.д. и т.п.). Это реально очень мощный механизм, который никак нельзя обходить стороной. Любителям сниппетов я бы сказал так: сам механизм управления сущностями в MODX-е не построен на сниппетах или типа того. Только процессоры. Поэтому если вы хотите разрабатывать действительно мощные веб-проекты, без процессоров вам никак не обойтись (хотя правильней сказать одними сниппетами вам не обойтись).
Ну а теперь рассмотрим процессоры из компонента modxsite (напомню, что практически все наши getdata-процессоры в сборке основаны на них). Самый основной из них — modSiteWebGetlistProcessor. Он расширяет родной MODX-овый процессор modObjectGetListProcessor, но несколько переопределяет и дополняет его логику работы. К примеру, в нем предусмотрено кеширование результатов выборки. Если передать в вызов параметр cache => 1, то результаты выборки будут закешированы, и при повторном вызове данные будут браться из кеша, а не опять формировать запрос к базе данных, выполнять его и обрабатывать. Тут оговорюсь, что в формировании ключа кеша учитываются все входящие параметры процессора, так что для запросов, к примеру, с разными параметрами page будут сформированы разные кеш-результаты. В целом этот процессор можно особо не копать (достаточно просто знать что он есть, какие параметры принимает и что возвращает), ибо логика в нем местами запутанная, но если у вас хорошие знания php и вы планируете создавать не один проект на базе ShopModxBox, то тогда поковырять его хорошенько будет очень даже полезно.
Второй процессор — modSiteWebGetdataProcessor, расширяющий modSiteWebGetlistProcessor. Этот процессор, в отличие от своего родителя (который сам по себе на самом деле редко используется), оперирует не с xPDO-объектами, а с чистыми данными из таблицы указанного в $classKey классе. Объясню: modSiteWebGetlistProcessor получает коллекцию объектов методом $modx->modx->getCollection(), то есть не просто получает данные из БД, а создает на основе этих данных xPDO-объекты. Это может быть необходимо, чтобы проверить права пользователя на эти объекты средствами MODX-а, которые требуют наличия самого объекта (а не данных его в БД, на основе которых на самом деле на уровне БД нельзя выполнить проверки (во всяком случае родной механизм MODX-а этого не предусматривает)). Но инициализация объектов требует во много раз больше ресурсов, чем просто получить данные этих объектов из БД, поэтому чаще всего мы используем именно getdata-процессор, если нам нужны просто данные записей и мы знаем заранее, что они не требуют специальных проверок на доступ (к примеру, если каталог публичный, без всяких лишних требований к пользователям, то нам просто надо получить данные этих записей и все. Без инициализации объектов все будет выполнено гораздо быстрее).
Другое важное отличие getdata-процессора от getlist-процессора — это обработка множественных записей TV-полей (в случае выполнения выборки их в запросе) и набивка этих данных в уникальный элемент документа в общем массиве данных. Просто, насколько наверняка многим известно, данные TV-параметров страниц находятся в отдельной таблице от документов и связь их один-ко-многим, то есть на одну запись документа может содержаться несколько записей TV-полей. Здесь все эти данные TV-полей будут набиты в массивы tvs для каждого документа в отдельности. В скором времени скорее всего этот блок кода перекочует в свой, более узкопрофильный процессор для получения документов — modSiteWebResourcesGetdataProcessor
А теперь давайте закрепим наши теоретические знания практическими, выполнив несколько упражнений.
1. Создадим новый документ. Для этого воспользуемся родным MODX-процессором resource/create. Все исполняемые процессоры самого MODX-а находятся в папке MODX_PROCESSORS_PATH
<?php print '<pre>'; ini_set('display_errors', 1); $modx->switchContext('web'); $modx->setLogTarget('HTML'); $action = 'resource/create'; $ns = ''; $params = array( "pagetitle" => "New document", "content" => "some content", ); if(!$response = $modx->runProcessor($action, $params , array( 'processors_path' => $ns ? $modx->getObject('modNamespace', $ns)->getCorePath().'processors/' : null, ))){ print "Не удалось выполнить процессор"; return; } print_r($response->getResponse());
Выполним его. Если у вас все хорошо выполнилось, вы получите ответ, содержащий success => 1 и id созданного документ, типа такого:
Array ( [success] => 1 [message] => [total] => 0 [errors] => Array ( ) [object] => Array ( [id] => 155 ) )
Иначе будет ошибка, например такая:
Array ( [success] => [message] => [total] => 2 [errors] => Array ( [0] => Array ( [id] => uri [msg] => Ресурс с идентификатором 155 уже использует URI new-document.html. Пожалуйста, введите уникальный псевдоним или используйте «Заморозить URI», чтобы вручную заменить его. ) [1] => Array ( [id] => alias [msg] => Ресурс с идентификатором 155 уже использует URI new-document.html. Пожалуйста, введите уникальный псевдоним или используйте «Заморозить URI», чтобы вручную заменить его. ) ) [object] => Array ( ) )

2. Обновим существующий документ. Для этого нам надо будет вызвать процессор resource/update. Единственно, в случае с обновлением документа не достаточно будет передать только его id-шник и изменяемые поля, так как в процессоре часть кода основывается на передаваемых данных, а не на данных полученного объекта документа, но это не беда.
<?php print '<pre>'; ini_set('display_errors', 1); $modx->switchContext('web'); $modx->setLogTarget('HTML'); $action = 'resource/update'; $ns = ''; $doc_id = 1; $params = array_merge($modx->getObject('modResource', $doc_id)->toArray(), array( "pagetitle" => "New pagetitle", )); if(!$response = $modx->runProcessor($action, $params , array( 'processors_path' => $ns ? $modx->getObject('modNamespace', $ns)->getCorePath().'processors/' : null, ))){ print "Не удалось выполнить процессор"; return; } print_r($response->getResponse());

3. Получим данные товаров в каталоге Для этого вызовем процессор web/catalog/products/getdata самой сборки ShopModxBox.
<?php print '<pre>'; ini_set('display_errors', 1); $modx->switchContext('web'); $modx->setLogTarget('HTML'); $action = 'web/catalog/products/getdata'; $ns = 'modxsite'; $params = array( "limit" => 3, "sort" => "createdon", "dir" => "desc", ); if(!$response = $modx->runProcessor($action, $params , array( 'processors_path' => $ns ? $modx->getObject('modNamespace', $ns)->getCorePath().'processors/' : null, ))){ print "Не удалось выполнить процессор"; return; } print_r($response->getResponse());

В ответ вы должны получить массив данных товаров (максимум трех по условию запроса), отсортированных по дате создания в обратном порядке.
На этом на сегодня все. А в качестве домашнего задания попробуйте проследить всю родословную процессора из последнего примера, и понять как формируется запрос на получение данных и какие таблицы он затрагивает.
Как-то делал, может, пригодятся: ? ?
Спасибо :) Это я на заре постижения работы с процессорами делал, чтобы вникнуть лучше
Ну ты-то давно уже вник))
Может, кому-то еще поможет лучше с логикой разобраться
Про процессоры у нас написано много ...
Николай, написано много и не только у вас но много не означает толково, но ЭТОТ материал лично для меня огромной ценности.
Большое спасибо за данный материал.
п.с. кстати у вас подача замечательная, читается на ура
В самом начале примера правильнее будет print_r взять в фигурные скобки. вот так
{print_r($result)}
Пожалуйста! :) 6 часов писал…
Cпрошу наверное я вот что ) Скажу сразу тема понятное дело облизана в интернете со всех сторон, но нет ничего лучше примера кода поэтому я нашел на Гите modHybridAuth и начал изучать.
и мне интересно вот что. есть connector.php который запускает процессор 'profile/auth'. Этот процессор расширяет процессор modProcessor.
У меня вроде все и получилось но вот ответ приходит имя процессора, я подозреваю что проблема доступом к modProcessor. Что не так?
Код один в один за исключением названия пакета)
class myTestProcessor extends modProcessor { public function process(){ return 'done'; } } return 'myTestProcessor';
1. Давайте полный листинг вызываемого кода, включая коннектор (даже если все повторяется). Мы не можем гадать измененный у вас код или нет (выкладывайте куда-нибудь на гист или типа того). 2. Приведите четко сообщение ошибки. Копипаст, плиз, а не «дословный перевод». 3. На сам modProcessor никаких прав не надо. Ошибка — это не всегда отсутствие доступа.
ошибки нет. в логах все чисто. я получаю статус 200 и в ответ название процессора, а не worked
п.с. чистый копипаст названия такие потому что просто разбираюсь, если вы об этом
Потому что название файла должно быть test.class.php, а не test.php Просто test.php — это устаревший флэт-процессор, он возвращает то, что у него в return. А у вас return 'myTestProcessor';
спасибо, то что надо… значить все файлы процессоров должны быть *.class.php
Да, должны быть. Не просто же так у нас процессоры getdata.class.php, getlist.class.php и т.п.
Здравствуйте. В процессе разбора процессоров возникла проблема с которой не могу справится. Работаем с пользователями нужно выдернуть с базы всех пользователей у которых sudo = 0. Результатом работы процессора должна быть выборка в которой указан только username С критерием для выбора разобрался а вот с тем что нужно вернуть не могу.
Процессор отдает полностью все поля таблицы. А нужно например только id и username.
В процессорах это метод setSelection,
$c->select(array( 'id', 'username', ));
Рекомендую почитать документацию по xPDO, поскольку процессоры общаются с базой через нее.
Спасибо. постепенно дойдем и туда…
п.с. лирическое ощущение… modx совсем не такой каким я его себе представлял
поспешил отписаться… все равно $response->getResponse() возвращает всю таблицу
class xtestGetUsersProcessor extends modObjectGetListProcessor{ public $classKey = 'modUser'; public $defaultSortField = 'id'; public $defaultSortDirection = 'ASC'; public $objectType = 'modUser'; public function prepareQueryBeforeCount(xPDOQuery $c) { $c->where(array( 'sudo' => 1, )); return $c; } public function setSelection(xPDOQuery $c){ $c->select(array( 'id', 'username', )); return $c; } } return 'xtestGetUsersProcessor';
А, вот что :) Я по инерции подумал, что из modxSite процессор за основу взят Если посмотреть core/model/modx/modprocessor.class.php, то у процессора modObjectGetListProcessor нет метода setSelection. Здесь select можно разместить как раз в функции prepareQueryBeforeCount
ага)я тоже уже пересмотрел…
дело в том что даже если вот так
public function prepareQueryBeforeCount(xPDOQuery $c) { $c->where(array( 'sudo' => 1, )); $c->select(array( 'id', 'username', )); return $c; }
$response->getResponse() выдает полностью все колонки таблицы
что ж за беда то такая
Надо в метод `outputArray` смотреть
можно вставить перед
return $c;
такие строки
$c->prepare(); print_r($c->toSQL()); die;
и если выполнить этот процессор в консоли, выведется sql-запрос
Можно и там, но лучше все-таки сформировать запрос без лишних полей. Нагрузка на мускул меньше будет.
я знал что есть такая штука как процессоры но это все было далеким и каким то туманным. вся работа заключалась в сниппет->phpClass
начав знакомство с процессорами я вижу что при таких возможностях использование snippet->phpClass это просто кощунство
`outputArray`
т.е. формировать строку ответа в ручную?
Так с этим никто не спорит. Я как бы намекнул, что надо таки туда поглядеть и увидеть, что базовый гетлист процессор в методе `getData` получает коллекцию, а в методе `iterate` дергается `prepareRow` с `$object->toArray()` под капотом; и `select` полетит, но не в результатах для вывода. Просто лень все это было печатать…
Нет. Написал ответ выше
Ты наверно имел в виду метод process?
я бы все-таки посоветовал действительно поставить пакет modxsite — там есть зороший набор уже проверенных процессоров. сот от того getlist лучше и наследовать.
Сам я глубоко особо не копал в системных процессорах, но конкретно в объявлении modObjectGetListProcessor вызывает беспокойство слово abstract — это говорит, что он неполный (просто основа для дальнейшего развития), и какие-то необходимые для работы методы необходимо дописать.
я бы все-таки посоветовал действительно поставить пакет modxsite — там есть зороший набор уже проверенных процессоров.
это не боевой проект я никуда не спешу и раз уж так пошло дело с процессорами то думаю попытаться вникнуть до конца. а modxSite установлен :)
ну то что я хотел я получил осталось узнать на сколько все это правильно сделано.
class xtestGetUsersProcessor extends modObjectGetListProcessor{ public $classKey = 'modUser'; public $defaultSortDirection = 'ASC'; public $defaultSortField = 'username'; ......... public function prepareRow(xPDOObject $object) { $objectArray = array(); $objectArray['id'] = $object->get('id'); $objectArray['username'] = $object->get('username'); return $objectArray; } } return 'xtestGetUsersProcessor';
это тоже будет работать, но при этом в запрос попадают все поля, и соответственно возрастает нагрузка и потребление памяти. Лучше все-таки до моментаобращения к мерверу правильно настроить select, чтобы в выборку попали только нужные поля. Странной дело, пока не могу понять
public function prepareQueryBeforeCount(xPDOQuery $c){ $c=parent::prepareQueryBeforeCount($c); $c->select(['modUser.id','modUser.username']); $c->prepare(); print $c->toSQL(); die; }
возвращает правильный запрос:
SELECT modUser.id, modUser.username FROM `spmx_users` AS `modUser`
а на выходе действительно отдает все поля
Array ( [success] => 1 [total] => 5 [results] => Array ( [0] => Array ( [id] => 1 [username] => admin [password] => P3/gWbTaphPr//hV8djePt1qdcjN5CIxBVKE5tqdJyQ= [cachepwd] => [class_key] => modUser [active] => 1 [remote_key] => [remote_data] => [hash_class] => hashing.modPBKDF2 [salt] => b052eb5470a21ff53697a5bf4cb43649 [primary_group] => 1 [session_stale] => Array ( [1] => spravochniki [2] => web ) [sudo] => 1 ) [1] => Array
нигде в коде больше $c->select(...) не встречается вплоть до $modx->getCollection. Ничего не понимаю.
я не знаю прав ли я но так просто нельзя сделать… коллекция получает значения с таблицы и забивает ими класс xPDO. т.е. на выходе мы принимает не массив, а объекты конкретного класса… наверное
п.с. из консоли не выход getCollection с селектом
Потому что надо смотреть в сторону методе prepareRow(). Как мы разбирали предметно ранее, xPDOObject::fromArray() выдернет из базы данных все строки объекта, так что $this->object->select() определенных колонок ничего не изменит, но нагрузки на сервер добавит. Надо переопределить этот метод в своем процессоре и вернуть типа
return array( 'id' => $this->object->id, 'username' => $this->object->username, );
Саш, смотри ниже. И статью смотри, указанную в комментарии. Таким образом ты только увеличишь нагрузку на сервер. Это такие особенности xPDO.
Эх, не зря Серега мой лучший ученик :)))
Все правильно сделано. Единственное, просто по синтаксису, проще так:
return array( 'id' => $this->object->id, 'username' => $this->object->username, );
Сегодня, Саша, мир твой рухнет))) Статью на этот счет почитать я тебе выше написал))
Да уж, действительно :) Я там еще не ковырял, не было необходимости :) Век живи…
Как мы разбирали предметно ранее, xPDOObject::fromArray() выдернет из базы данных все строки объекта, так что $this->object->select() определенных колонок ничего не изменит, но нагрузки на сервер добавит
шикарно… хотел было сказать «ой, как жаль что статья не попала ко мне раньше» но я рад что разобрали все это методом проб и ошибок, а статья как раз в вовремя, объяснила все более точно и показала откуда ноги растут
Сегодня, Саша, мир твой рухнет)))
:) это все сейчас похоже на то как маленькие дети игрушку новую получать… «ВАУ, а тут и дверца еще открывается...»
Всем большое спасибо!!!
«ВАУ, а тут и дверца еще открывается...»
:))) Пожалуйста!
«ВАУ, а тут и дверца еще открывается...»
Ага, именно так :) Жизнь периодически тыкает носом в статьи, которые видел раньше, но на том моем уровне они вызывали зевоту :) Потом забывались. А зря… :) Все чаще появляется навязчивая мысль перечитать весь modxclub.ru c нуля :)
А все-таки странное поведение xPDO… Хотя не глупые люди писали, зачем-то надо :) Коля, а почему тогда в твоих процессорах работает конструкция $c->select(...)? Потому, что не урезаешь, а расширяешь?
Почитай:) Тут много чего интересного есть. Одна из наиболее интересных статей вот эта. Если все поймешь, что там написано (и всю сопутствующую информацию), то тогда у тебя не будет больше вопросов «а что же сайт так тормозит?», то есть будешь управлять кешированием и производительностью как положено.
Спасибо за наводку, обязательно изучу. Теперь уровень чуть выше :)
Коля, а почему тогда в твоих процессорах работает конструкция $c->select(...)?
Потому что по умолчанию вообще метод xPDOQuery::select() не выполняется. Попробуй вот так сделать:
$q = $modx->newQuery('modResource'); $q->prepare(); print $q->toSQL();
Увидишь все колонки запрошенного класса, при чем алиасные, а не как они есть в БД.
Это я понял. Не пойму, зачем? Нелогично как-то, на мой взгляд. Если мне надо только name, так и дай мне только его :) Хотя, как говорил, видимо было какие-то обоснования. Возможно, выбратьб все поля проще, чем отфильтровывать при формировании выборки… По универу помню только, что нам вбивали, что надо максимально запрос оптимизировать, сократить нагрузку на сервер. Может, и поменалась политика партии с тех времен :)
да не «выбирает» оно поля… создаются объекты… хотя опять таки выбирает но для того что бы создать объекты :)
Сделай так:
$q = $modx->newQuery('modUser'); $s = $q->prepare(); $s->execute(); print_r($s->fetch(2));
Получишь результат типа
Array ( [modUser_id] => 2 [modUser_username] => xxxx [modUser_password] => xxxxxx [modUser_cachepwd] => [modUser_class_key] => modUser [modUser_active] => 1 [modUser_remote_key] => [modUser_remote_data] => [modUser_hash_class] => hashing.modPBKDF2 [modUser_salt] => [modUser_primary_group] => 22 [modUser_session_stale] => [modUser_sudo] => 1 )
И сделай так:
$q = $modx->newQuery('modUser'); $q->select(array( "{$q->getAlias()}.*", )); $s = $q->prepare(); $s->execute(); print_r($s->fetch(2));
Результат:
Array ( [id] => 2 [username] => xxxxxx [password] => xxxxxxx [cachepwd] => [class_key] => modUser [active] => 1 [remote_key] => [remote_data] => [hash_class] => hashing.modPBKDF2 [salt] => [primary_group] => 22 [session_stale] => [sudo] => 1 )
Найди 10 отличий.
Да… Надо чаще встречаться :) Не было пока необходимости, не вдавался в такие подробности, вполне хватает пока твоих getdata, при необходимости от них и наследую… Все. Выберу время в ближайшее время и займусь шпатлеванием пробелов :)
Давай. Не будет лишним.
усвоил. не скажу что все прям понятно, родилось еще больше вопросов, но об этом потом…
для себя была поставлена задача сделать табличку пользователей с помощью процессора
процессор getusers.class.php
require_once (dirname(dirname(dirname(dirname(dirname(dirname(__FILE__)))))).'/model/modx/processors/security/user/getlist.class.php'); class xtestGetUserListProcessor extends modUserGetListProcessor{ public function prepareQueryBeforeCount(xPDOQuery $c) { $c->leftJoin('modUserProfile','Profile'); $c->leftJoin('modUserGroupMember', 'UserGroupMembers'); $c->leftJoin('modUserGroup', 'UserGroup', 'UserGroupMembers.user_group = UserGroup.id'); return $c; } public function prepareQueryAfterCount(xPDOQuery $c) { $c->select($this->modx->getSelectColumns('modUser','modUser')); $c->select($this->modx->getSelectColumns('modUserProfile','Profile','',array('fullname','email','blocked'))); $c->select($this->modx->getSelectColumns('modUserGroup','UserGroup','',array('name'))); return $c; } public function prepareRow(xPDOObject $object){ return array( 'id' => $object->id, 'username' => $object->username, 'fullname' => $object->fullname, 'email' => $object->email, 'group' => $object->name, ); } } return 'xtestGetUserListProcessor';
ну и вот такой результат
уже при написании поста возник вопрос а что если у пользователя 2 группы… отложил пост полез пробовать… если у пользователя 2 группы то покажет последнюю а если сделать вот так
$c->select($this->modx->getSelectColumns('modUser','modUser', '', array('id', 'username')));
то в таблице будет 2 строки с разными группами…
а еще есть такая штука как права… для выполнения вышеуказанного процессора пользователь должен иметь разрешения 'view_user'.
нравится «зараза».
Вот это: require_once (dirname(dirname(dirname(dirname(dirname(dirname(__FILE__)))))).'/model/modx/processors/security/user/getlist.class.php'); замените на require_once MODX_PROCESSORS_PATH.'security/user/getlist.class.php');
Про дубляжи записей: изучайте SQL, там вопрос только в нем, а не в MODX-е. Какой SQL-запрос будет сформирован, такие данные и будут получены.
Вечер добрый.
Николай, а скажите пожалуйста, почему Вы для получения объекта используете *GetListProcessor, с чем это связано?
и вот есть такая беда есть:
*.tpl
{processor action='web/groups/getgroup' ns='xtest' assign=result} {$result|@var_dump} // показывает NULL
require_once dirname(dirname(__FILE__)) . '/getobject.class.php'; class xtestGetGroupProcessor extends xtestGetObjectProcessor{ public $classKey = 'modUserGroup'; // ну даже так public function initialize(){ $this->setProperty('id', 2); return parent::initialize(); } } return 'xtestGetGroupProcessor'; class xtestGetObjectProcessor extends modObjectGetProcessor { public $checkViewPermission = false; } return 'xtestGetObjectProcessor';
Через ajax->connector или консоль процессор возвращает то что от него хотят
корень зла определен. сразу как то не обратил внимание потому что smarty работало.
при обращении к странице по ajax smarty то работает но вот modxSmarty неа… Николай писал об этом, решение нашлось в статье о каталоге :)
Я об этом не раз писал, и именно поэтому и писал статью про правильный Ajax-каталог. Конечно может и есть более правильная его организация, но на мой взгляд Ajax на документах правильней делать, если нужно HTML получить. А если просто данные нужны, тогда через коннекторы.
Подскажите пожалуйта, а как правильно работать с процессором modUserCreateProcessor На сколько я понимаю там вся проблема в параметрах. для начала через форму скармливаю 4 поля username, email, password, password_confirm
процессору дополнительно передается параметр passwordnotifymethod = 's'
В итоге я получаю пользователя но система сама ему генерирует пароль, т.е. на сколько я понимаю поля пароля и дубля пароля игнорируются и даже не проверяются. Найти в инете хоть какое то описание как работает этот процессор у меня не получилось(
нашел в коде админки :)
$this->setDefaultProperties( array( 'active' => true, 'passwordnotifymethod' => 'w', // e - слать на mail, s - уведомление в окно, w- мне просто не надо ни то ни то 'passwordgenmethod' => 'spec', // ручной ввод пароля 'specifiedpassword' => $this->getProperty('password'), 'confirmpassword' => $this->getProperty('password_confirm'), ) );
Вот, достаточно просто почитать процессор и его валидатор :)
За fossies.org спасибо отдельное. За статьи у вас наверное спасибки — девать некуда, в отличии от донейтов. Вот решил попробовать получить данные из своего процессора, а не с помощью newQuery, а из своего процессора. Скинул modExtra, накидал схему, переименовал все, как мне надо, в общем запустил стандартно CMP — работает отлично. Ну что, создал в папке с процессорами рядом с mgr /processors/web/ В папку web скопировал процессор, который наследует modObjectGetListProcessor. Буквально указываю первые пять параметров (класские и что сортировать по id-DESC), и закрываю скобку. В принципе, почти такой же с совсем малой правкой в CMP благополучно данные кидает. Пишу в создаваемом процессоре require_once с классом данной таблицы. Абсолютно обычный класс, который extends xPDOSimpleObject Вызываю в консоле, как в начале статьи вы другие процессоры дергаете — приходит ошибки Could not get table class for class и {«success»:true,«total»:«0»,«results»:[]} Ставлю в консоли $modx->addPackage('booking', MODX_BASE_PATH.'path/path/model/'); — ноль внимания, фунт призрения. Добавляю $d= $modx->getObject('myClass',1); — работает! Я понимаю, что можно прописать загрузку модели в настройках extension_packages, но если я хочу все же, чтобы модель загружалась только там, где я дергаю данные, а на остальных страницах небыло? Как вообще пакеты по-грамотному подгружать к процессорам?
С неймспейсом все ок. $modx->getObject('modNamespace', $ns)->getCorePath(); выдает правильный путь.
Да и если require в процессоре не верно прописан — ошибка об этом вылезает.
спасибки — девать некуда, в отличии от донейтов.
Это уж точно :)
Добавляю $d= $modx->getObject('myClass',1); — работает!
Куда именно добавляешь? И выложи полный листинг твоего процессора и скрин файловой структуры твоего компонента, чтобы было видно что и как у тебя там лежит.
Куда именно добавляешь?
Да в консоль же добавляю. Отписал отдельную тему modxclub.ru/topics/vyizov-svoego-proczessora-1632.html
Добрый день. Делаю компонент, в котором одна из кнопок вызывает окно импорта данных из csv в таблицу БД. В одном поле выбирается файл (этот путь потом передается в процессор), а в другом поле выбирается второе необходимое для импорта значение (оно статично для всех данных импорта.).
Проблема в том, что мой процессор по итогу заносит в БД лишь одно значение - причем либо первое, либо последнее. Подскажите, где у меня ошибка, и как мне сделать выполнение моего процессора в цикле, чтобы все данных заносились? Вот код моего процессора:

<?php /** * Import an User */ class sxUserCreateProcessor extends modObjectCreateProcessor { public $objectType = 'sxUser'; public $classKey = 'sxUser'; public $languageTopics = array('sx'); public $permission = 'new_document'; /** * @return bool */ public function prepareQueryBeforeCount(xPDOQuery $c) { $c->innerJoin('sxUserGroup', 'sxUserGroup', 'sxUserGroup.id = sxUser.usergroup_id'); $c->select($this->modx->getSelectColumns('sxUser', 'sxUser')); $c->select('sxUserGroup.name as group_name'); if ($usergroup_id = $this->getProperty('usergroup_id')) { if (!empty($usergroup_id)) { $c->where(array('sxUserGroup.usergroup_id' => $usergroup_id)); } } return $c; } public function beforeSet() { $path = $this->modx->getOption('base_path'); $file_dir = $path . $this->getProperty('import_source'); // Путь к файлу $file = file_get_contents($file_dir); $lines = explode(PHP_EOL, $file); foreach ($lines as $key => $value) { $this->setProperty('email', $value); return true; } return !$this->hasErrors(); } } return 'sxUserCreateProcessor';

@Batyabest,
1. откуда Вы взяли у modObjectCreateProcessor метод prepareQueryBeforeCount? он используется в процессорах, которые предполагают получение списка объектов.
2. modObjectCreateProcessor рассчитан на добавление только одного объекта. т.е. нужно сперва получить файл, считать данные, а потом в цикле вызывать процессор создания объекта.

foreach ($lines as $key => $value) { $this->setProperty('email', $value); return true; }
тут не пойму, зачем цикл, если при первом же проходе идёт выход?

$this->hasErrors();
ошибки ниоткуда не берутся. Их нужно создавать, например,

$this->addFieldError('email','такой email уже есть');
вот потом можно и проверять на ошибки.
Вообще, рекомендую изучить коды основных процессоров в файле core/model/modx/modprocessor.class.php
@Александр Марков,
1. Я наверное не правильно использовал этот метод, он у меня для того, чтобы в окне в котором я выбираю файл во втором поле я могу выбрать группу (это тот самый второй параметр для импорта.) То есть нажимаю кнопку Импорт - открывается окно в котором 2 поля: выбрать файл и выбрать группу. Вот группа подгружается из другой таблицы БД. Был бы благодарен, если подскажете как это сделать правильно.
2. В таком случае, для того, чтобы у меня заносились все данные получить в каком-то стороннем файле (скрипте), а потом в нем вызвать процессор создания объекта?
3. Про ошибки да, виноват))
@Александр Марков,
Я переписал свой процессор таким образом:


<?php /** * Create an User */ class sxUserImportProcessor extends modObjectCreateProcessor { public $objectType = 'sxUser'; public $classKey = 'sxUser'; public $languageTopics = array('sx'); public $permission = 'new_document'; /** * @return bool */ public function beforeSet() { $path = $this->modx->getOption('base_path'); $file_dir = $path . $this->getProperty('import_source'); // Путь к файлу $file = file_get_contents($file_dir); $lines = explode(PHP_EOL, $file); foreach ($lines as $value) { $this->setProperty('email', $value); $processorProps = array( 'email' => $value, 'usergroup_id' => $this->getProperty('usergroup_id') ); $otherProps = array( // Здесь указываем где лежат наши процессоры 'processors_path' => $this->modx->getOption('base_path') . 'core/components/sx/processors/' ); $response = $this->modx->runProcessor('mgr/user/create', $processorProps, $otherProps); //print_r($response->response); } return !$this->hasErrors(); } } return 'sxUserImportProcessor';
Теперь импорт данных происходит хорошо, но последние в файле данные импортируются 2 раза. Я так понимаю из-за того, что сначала происходит импорт через процессор, вызываемый в цикле, а затем последние данные заносятся через return 'sxUserImportProcessor'; ? Как исправить подскажите пожалуйста.

Может быть вообще не нужен здесь процессор sxUserImportProcessor, а просто скриптом все оформить в котором будет вызываться другой процессор?
Перенес все в
public function process() { /* ... */ }
все заработало.
Да, можно и так. только я не вижу смысла оформлять это в процессор. Процессор - это атомарная операция, для одного действия. Как например user/create. Сам импорт можно организовать обычным скриптом , который читает файл и в цикле вызывает процессор.

И да, лучша использовать метод process. это основной метод, который задаёт последовательность вызовов остальных. Посмотрите, как реализован, например, этот метод у процессора ObjectGetList.

@Александр Марков,
Спасибо за подсказки.
У меня возникла необходимость подгрузить в окне список ресурсов сайта. Родного modx-combo-resource я так понял не существует? Нашело такое решение, но почему-то оно не работает:


MODx.combo.Resource = function(config){ config = config || {}; Ext.applyIf(config,{ id: 'modx-combo-resource' ,name: 'resourceID' ,hiddenName: 'resourceID' ,displayField: 'pagetitle' ,valueField: 'id' ,mode: 'remote' ,fields: ['id','pagetitle'] ,forceSelection: true ,editable: false ,enableKeyEvents: true ,pageSize: 20 ,url: MODx.config.connectors_url+'resource/index.php' ,baseParams: { action: 'getList' ,showNone: true } }); MODx.combo.Resource.superclass.constructor.call(this, config); }; Ext.extend(MODx.combo.Resource,MODx.combo.ComboBox); Ext.reg('modx-combo-resource',MODx.combo.Resource);

нет, это вроде не то)) Мне нужно создать свой xtype получается. Как создать xtype который будет выбирать ресурсы?
вообще-то, в той статье описано, как набрать в список id ресурсов, причем есть автопоиск по названию и можно задавать любые критерии отбора, вплоть до TV-шек.
А так из коробки есть встроенный тип ввода - список ресурсов
А как называется встроенный тип список ресурсов?
это как раз оно. по запросу с автопоиском можно набрать список любых ресурсов. сохраняется в виде id1||id2||id3...
Ну и из коробки идёт тип "Список ресурсов" - но мне он не нравится, там нет автопоиска т фильтрация ограничена.

Добавить комментарий