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

Про процессоры у нас написано много, но вот этот комментарий натолкнул меня на мысль, что некоторые вообще не представляют себе что такое 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,k,v). Устанавливает свойства процессора (добавляет в массив $this->properties переменные со своими значениями (за один вызов только одно значение)). thisвнутриобъекта—этомагическаяпеременная,ссылающаясянасамобъект.Кпримеру,есливыхотитевнутрипроцессораполучитьегосвойства,вывнемпрописываете‘this внутри объекта — это магическая переменная, ссылающаяся на сам объект. К примеру, если вы хотите внутри процессора получить его свойства, вы в нем прописываете `properties = $this->getProperties();
    • getProperty(k,k,default = null). Получает свойство процессора.
    • **unsetProperty(key)**. [Удаляет](https://github.com/modxcms/revolution/blob/8efb61f5d1bb30c5df2ed9eba803aa5b4e805774/core/model/modx/modProcessor.class.php#L66) свойство процессора. Это имеет смысл когда вы хотите избежать переопределение какого-либо свойства объекта. Дело в том, что все передаваемые в процессор параметры попадает в его свойства `this->properties, и, к примеру, если у вас выполняется обновление объекта в рамках update-процессора [modObjectUpdateProcessor](https://github.com/modxcms/revolution/blob/8efb61f5d1bb30c5df2ed9eba803aa5b4e805774/core/model/modx/modprocessor.class.php#L757) (который мы чуть подробней рассмотрим ниже), в свойства полученного объекта передаются полученные свойства процессора. К примеру, если вызовем процессор на обновление документа так: modx−>runProcessor(′resource/update′,array(′id′=>1,′pagetitle′=>′newpagetitle′));‘,тобудетполученобъектдокументас‘id=>1‘,иегозаголовок(pagetitle)измененнапереданныйвпроцессорпараметр‘′pagetitle′=>′newpagetitle′‘.Таквот,еслимывсвоемпроцессорехотимизбежатьтого,чтокто−топередастновыйзаголовокиизаголовокдокументаизменится,мывфункции‘initialize()‘можемпрописать‘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 = array())**. [Так же как и setProperties(properties)](https://github.com/modxcms/revolution/blob/8efb61f5d1bb30c5df2ed9eba803aa5b4e805774/core/model/modx/modProcessor.class.php#L232), устанавливает параметры процессора, но с той лишь разницей, что он не замещает уже имеющиеся параметры. То есть если, к примеру, при вызове процессора был передан параметр 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=′′,msg = '',object = null). Возвращает успешный ответ выполнения процессора (в массиве ответа тогда параметр success содержит true).
    • failure(msg=′′,msg = '',object = null). Возвращает ошибку.

    Методы success() и failure() как правило возвращаются в методе process().

    • hasErrors() как и говорилось выше, проверяет есть ли ошибка в процессоре или нет.

    Здесь есть одно очень важное замечание: как я уже не раз говорил, у процессоров нет собственного объекта обработки ошибок, поэтому, если у вас вазывается сразу несколько процессоров, и в каком-то из них будет ошибка, то все последующие процессоры при проверке $this->hasErrors() будут возвращать ошибку, так как на все один единый объект — $modx->error. Так что желательно после вызова процессора выполнять сброс ошибок $modx->error->reset(). В случае, если процессор вызывается в Smarty-шаблоне, такой сброс ошибок не требуется, так как сброс прописан в самом смарти-плагине (modxSmarty v1.0.0+).

    • addFieldError(key,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

Процессоры MODX Процессоры MODX Процессоры MODX

Вообще, если освоить навигацию по тому ресурсу и хорошенько покопаться, можно много всего найти и узнать. К примеру, можно ощутить, что MODX со своими процессорами — это целая вселенная!

Процессоры MODX

Здесь видна лишь маленькая часть имеющихся в 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());

В ответ вы должны получить массив данных товаров (максимум трех по условию запроса), отсортированных по дате создания в обратном порядке.

На этом на сегодня все. А в качестве домашнего задания попробуйте проследить всю родословную процессора из последнего примера, и понять как формируется запрос на получение данных и какие таблицы он затрагивает.

Comments

Как-то делал, может, пригодятся: mxc1.jpg mxc2.jpg

Про процессоры у нас написано много ...

Николай, написано много и не только у вас но много не означает толково, но ЭТОТ материал лично для меня огромной ценности.

Большое спасибо за данный материал.

п.с. кстати у вас подача замечательная, читается на ура

Cпрошу наверное я вот что ) Скажу сразу тема понятное дело облизана в интернете со всех сторон, но нет ничего лучше примера кода поэтому я нашел на Гите connector.php и начал изучать. и мне интересно вот что. есть connector.php который запускает процессор 'profile/auth'. Этот процессор расширяет процессор modProcessor. У меня вроде все и получилось но вот ответ приходит имя процессора, я подозреваю что проблема доступом к modProcessor. Что не так? Код один в один за исключением названия пакета)

class myTestProcessor extends modProcessor {
    public function process(){
        return 'done';
    }
}
return 'myTestProcessor';
  1. Давайте полный листинг вызываемого кода, включая коннектор (даже если все повторяется). Мы не можем гадать измененный у вас код или нет (выкладывайте куда-нибудь на гист или типа того).
  2. Приведите четко сообщение ошибки. Копипаст, плиз, а не «дословный перевод».
  3. На сам modProcessor никаких прав не надо. Ошибка — это не всегда отсутствие доступа.

Gist

ошибки нет. в логах все чисто. я получаю статус 200 и в ответ название процессора, а не worked

п.с. чистый копипаст названия такие потому что просто разбираюсь, если вы об этом

Потому что название файла должно быть test.class.php, а не test.php.

Просто test.php — это устаревший флэт-процессор, он возвращает то, что у него в return. А у вас return 'myTestProcessor';

спасибо, то что надо… значить все файлы процессоров должны быть *.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() выдает полностью все колонки таблицы что ж за беда то такая

можно вставить перед

return $c;

такие строки

$c->prepare();
print_r($c->toSQL());
die;

и если выполнить этот процессор в консоли, выведется sql-запрос

Можно и там, но лучше все-таки сформировать запрос без лишних полей. Нагрузка на мускул меньше будет.

я знал что есть такая штука как процессоры но это все было далеким и каким то туманным. вся работа заключалась в сниппет->phpClass

начав знакомство с процессорами я вижу что при таких возможностях использование snippet->phpClass это просто кощунство

outputArray

т.е. формировать строку ответа в ручную?

Так с этим никто не спорит. Я как бы намекнул, что надо таки туда поглядеть и увидеть, что базовый гетлист процессор в методе getData получает коллекцию, а в методе iterate дергается prepareRow с $object->toArray() под капотом; и select полетит, но не в результатах для вывода. Просто лень все это было печатать…

Я бы все-таки посоветовал действительно поставить пакет 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=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. Про ошибки да, виноват))

@Александр Марков, Я переписал свой процессор таким образом:

<?>
/**
 * 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, а просто скриптом все оформить в котором будет вызываться другой процессор?

Да, можно и так. только я не вижу смысла оформлять это в процессор. Процессор - это атомарная операция, для одного действия. Как например 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...

Ну и из коробки идёт тип "Список ресурсов" - но мне он не нравится, там нет автопоиска т фильтрация ограничена.