Парадигма программирования процессорами в MODx Revolution

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

    В данной статье я хотел бы рассказать насколько MODx Revolution в целом изменил мой подход к программированию.
    Не знаю как кто программирует, но я считаю, что давным давно уже программирую методами ООП. К чему же в общих чертах сводилось программирование? Я писал (или брал готовые) классы под свои задачи (класс по работе с базой данных, класс по работе с шаблонами, класс еще под что-нибудь). Большинство классов были довольно большие и выполняли множество необходимых задач по своему профилю. Чаще всего по мере роста проекта многие классы разрастались, или обзаводились расширениями, или и то и другое. Так или иначе, уверен многие сталкивались с ситуацией часового разбора объекта в пару тысяч строк и несколькими десятками методов, чтобы разобраться куда можно внести очередные изменения, но так, чтобы при этом что-то другое не сломалось. Но на мой взгляд сложнее всего обеспечить гармоничное взаимодействие различных объектов между собой, особенно в плане перехвата ошибок при выполнении тех или иных действий, а главное в момент ошибки решить насколько она критичная и стоит ли прервать процесс выполнения, или можно идти дальше. А еще сюда же приписать клиент-серверные решения, чтобы стандартизировать ответы выполнения и для серверной части (с дальнейшей ее отрисовкой в шаблоны), и для Ajax-запросов.

    Какой же именно инструментарий предлагает MODx Revolution для программирования логики проекта? Всего два класса: Процессор (выполняется классом modProcessor) и Коннектор (выполняется классом modConnector).
    Что это такое? Процессор — отдельный файл с чаще всего одной или несколькими мелкими задачами, в результате выполнения которого ответ должен быть только положительным (что будет свидетельствовать о положительном результате выполнения), или отрицательный (желательно с конкретным сообщением об ошибке), что будет само собой говорить, что произошло что-то критически неправильно.

    Приведу маленький пример. Мы хотим создать новый документ. Если документ будет создан, то замечательно, если нет, нам необходимо получить сообщение об ошибке.

    1. Создадим папку наших процессоров в core/model/modx/processors/, назовем ее, к примеру, assets/
    2. Создадим файл-процессор doc/create.php (полный путь получится core/model/modx/processors/assets/doc/create.php)
    3. Пропишем в нем нужный нам код
    <?
    if(!$scriptProperties['pagetitle']){
    	return $modx->error->failure('Не был указан заголовок страницы');
    }
    if(!isset($scriptProperties['parent'])){
    	return $modx->error->failure('Не был указан родитель для документа');
    }
    
    $doc = $modx->newObject('modResource', scriptProperties);
    
    if(!$doc->save()){
    	return $modx->error->failure('Не удалось сохранить документ');
    }
    
    return $modx->error->success('Документ успешно создан', $doc->toArray());
    


    4. А теперь в нужном нам месте вызовем данный процессор
    $response = $modx->runProcessor('assets.doc.create', array(
        'parent' => 0,
        'pagetitle' => 'Наш документ'
    ));
    
    if($response->isError()){
    	print "Произошла ошибка". $response->getMessage();
    }
    else{
    	$object = $response->getObject();
    	print "Был создан документ с ID {$object['id']}";
    }
    
    /*$modx->runProcessor - стандартный метод MODx. Расширение файла .php для исполняемого файла добавляется автоматически, то есть нам не надо писать create.php
    Точка - разделитель папок. То есть в данном случае assets.doc.create говорит о том, что исполняемый файл лежит
    в папке assets/doc/
    По умолчанию поиск  исполняемого  файла выполняется в папке core/model/modx/processors/
    Вы  можете передать третьим параметром массив опций, в частности указав свою нанку процессоров
    $modx->runProcessor($path, $properties, array( processors_path => self::$processorsPath));
    Переданный второй аргумент $properties - массив параметров, который будет виден в рамках процессора в виде переменной scriptProperties. Этот положительный момент мы оценим чуть позже.
    Так же внутри процессора виден объект $modx, что освобождает нас  от необходимости глобалить его в рамках наших пользовательских методов.
    */
    


    Это частный пример и не раскрывает даже примерно того, о чем я хотел сказать.
    Красота использования данного метода больше раскрывается при использовании сразу нескольких вложенных процессоров.
    Вот мы вызовем процессор, в котором вызываются другие процессоры (приведен код именно вызываемого процессора).
    <?
    
    $user = false;
    $doc  = false;
    
    // Пытаемся создать пользователя
    $response = $modx->runProcessor('security/user/create', $scriptProcessor);
    
    // Если ошибка, возвращаем сообщение из  выполняемого процессора
    if($response->isError()){
    	return $modx->error->failure($response->getMessage());
    }
    else{
    	// Иначе получаем пользователя
    	$user = $response->getObject();
    }
    
    // Если все ОК, выполняем процессор на создание документа
    $response = $modx->runProcessor('assets.doc.create', array(
        'parent' => 0,
        'pagetitle' => 'Наш документ'
    ));
    
    // Опять-таки, если ошибка, возвращаем сообщение из выполняемого процессора
    if($response->isError()){
    	return $modx->error->failure($response->getMessage());
    }
    else{
    	// Иначе получаем документ
    	$doc = $response->getObject();
    }
    
    // Если все ОК, возвращяем успех
    return array(
    	'success' => true,
    	'message' => '',
    	'object' => array(
    		'user' 	=> $user,
    		'doc'	=> $doc,
    	)
    );
    
    /*
    	Обратите внимание, что в конце мы использовали не метод $modx->error->success(failure),
    	а вернули именно массив.
    	Принципиально на выходе вы можете ничего и не заметить, то есть через $response->getObject() вы получите тот же самый объект,
    	но  все же разница есть.
    	Возвращая $modx->error->success('', $object); нельзя вернуть именно объект. 
    	Возвращаемый объект методом $modx->error->success будет преобразован в массив,
    	при чем преобразование будет выполнено рекурсивно по всем уровням и ключи разных уровней будут переписаны,
    	в результате чего можно получить одноуровневый массив с потерей данных.
    	Именно по этому не смотря на то, что системный процессор security/user/create возвращает не $user->toArray(), а именно $user,
    	в результате мы получаем не объект $user, а именно массив. То есть мы не сможем с ним выполнить методы класса modUser типа ->toArray()
    	Возвращая сразу массив с элементом 'object', можно вернуть именно объект, 
    	то есть на выходе выполнить типа $response->getObject()->get('id');
    */
    

    Таким образом на выходе мы получим четкий результат, или $response->isError(), или нет.
    При этом мы не задумываемся о степени вложенности этих процессоров, сколько бы уровней вложенности процессоров не было, при правильной организации выполнения независимо от того, на каком уровне вложенности произойдет ошибка, мы будем об этом знать и выведем текстовое сообщение, так как return $modx->error->failure($response->getMessage()); каждый раз будет возвращать сообщение следующего вложенного процессора.

    Надо отметить, что вся админская часть MODx написана на процессорах (посмотрите папку /core/model/modx/processors/) и если правильно использовать процессоры, то многие сторонние пакеты могут вообще не понадобиться.
    Что может быть проще, чем $modx->runSnippet('security/login', $scriptProcessor);?
    MODx выполнит все необходимое и в случае чего вернет одно из прописанных на все случаи сообщений об ошибке. В этих процессорах и проверки на права доступов, и всякие системные переменные, и вызовы других связанных процессоров. В общем все что нужно…

    Таким образом мы как бы под каждое небольшое действие создаем нужный нам процессор и для выполнения более обширных задач собираем отдельные процессоры в кучу.
    Во-первых, такие отдельные процессоры легче понять. В них четко понятно что для чего делается и где логическое завершение задачи. И наверно самое главное — меньше всяких лишних ветвлений if, else и т.п., так как у некоторых эти if-else порой достигают нескольких сотен (а то и тысяч) строк кода.
    Во-вторых, выход из строя одного процессора оказывает гораздо меньшее негативное влияние на работу всего сайта, по сравнению с выходом одного большого класса из-за ошибки разбора кода или типа того (случай конечно непонятный нормальному программисту, но тем не менее бывает всякое).
    Ну а в-третьих, в целом движок с подобной реализацией обслуживается гораздо легче и интуитивней.

    Но еще один очень мощный плюс в данном подходе — это стандартизированные ответы как на стороне сервера, так и браузера (при использовании Ajax).
    Если вы отправите запрос на коннектор, в котором будет вызван нужный вам процессор, ответ будет возвращен в виде JSON-объекта, где будут и success, и message и object.
    Это позволяет использовать одни и те же процессоры как для серверной логики, так и псевдо-клиентской.
    Если будет интерес, более подробно о коннекторах напишу позже.
    Метки:
    Поделиться публикацией
    Комментарии 16
    • +1
      Позвольте, Вас немного исправить. Есть неофициальный ответ от представителя MODX LLC о том, что MODx является устаревшим названием платформы. Сейчас правильно писать MODX.
      • +2
        ОК, обязательно возьму это на заметку.
      • 0
        Увидел заголовок статьи, завис, мозг начал выдавать варианты про программирование видеокартами, HDD, RAM…
        • 0
          Предполагал такой вариант. Но все же статья в блоге MODX Revolution, и многим любителям этого движка так или иначе понятно, что такое процессор.
          Но рад был бы, если бы кто-то предложил какое-то свое название всему этому безобразию…
          • 0
            Просто я, честно говоря, впервые с этим сталкиваюсь. Поэтому программирование процессорами для меня — это что-то из области забивания гвоздей микроскопами.
            • 0
              Но ведь и процессор — это не всегда железка, так же как и шина, винчейстер и т.п.
              В общем как я и предупреждал с самого начала — это статья-размышления. Некоторые вещи сложно бывает назвать своими именами.
        • НЛО прилетело и опубликовало эту надпись здесь
          • +1
            Для меня более важен момент, что проверки будут проходить как задумали авторы MODX, а не как я криво написал.

            И это гарантирует, что расширение, работающее через процессоры, будет работать во всех новых версиях MODX и все системные действия будут отрабатывать штатно.
            • 0
              Да нет, вы меня видимо неправильно поняли.
              Первое: я не говорю, что это мое творение. Об этом вообще не было речи. Я говорил о своем стиле разработки, который перенял от MODX.
              Второе: я не говорил, что правильно создавать ресурс через объект. Безусловно правильно объект создавать через процессор resource/create
              Но предполагалось же, что кто-то вообще не знает что такое процессор, и я хотел вкратце показать маленький процессор, так как $doc->save() интуитивней более понятен для многих, в том числе тех, что с MODX может и не сталкивался, но сталкивался с PDO.
              А суть была показать работу вложенных процессоров, так как это в корне отличается от привычных методов написания профильных классов под все случаи жизни.
            • +1
              Я бы только вот уточнил такую деталь, как «классные» процессоры. Все же ребята из MODX сделали огромную работу, переведя кучу спагетти-кода процессоров в классы. И раз речь шла об ООП, то правильнее было бы использовать именно их. За счет наследования кода становится намного меньше в самих процессорах и код более понятный.
              • –1
                И скажем честно, обычные процессоры после «классных» — херня на постном масле.

                Заметка про них и заготовка компонента, с их использованием — там же есть и видео, как легко начать делать новый компонент.
                • 0
                  Даже если это «классный» процессор, все же суть его остается прежней — выполнить какое-то свое, локальное действие и или вернуть успех, или ошибку. Как бы то ни было, это все равно куча файлов под свои задачи.
                  • +1
                    Универсальных, удобных, с кучей скрытого, но нужного кода. Подход себя вполне оправдывает, особенно когда имеем дело с аджаксовой админкой.
                    • 0
                      Да, это я как раз и пытался донести. Самое главное в этом подходе, что независимо от того, вызову я этот процессор на стороне сервера чисто для серверной логики, или запрошу выполнение через коннектор, я знаю точно, что я получу стандартизированный ответ и все будет ОК.
                      Лично для меня сейчас такой подход кажется наилучшим.
                • 0
                  правильно ли я понял, что процессоы (если очень упрощенно) представляют собой аналоги функций?
                  • 0
                    В более ранних ветках MODX-а именно так и было. Один процессор — как бы одна функция, котораямогла вызывать и другие процессоры-функции, но обязательно возвращала какой-то результат (собственный, или результат других процессоров). А не возврат результата расценивается как отрицательный результат.

                    Но теперь в MODX процессоры — это уже полноценные классы. Есть базовый класс modProcessor, есть еще несколько классов, производных от него, для выполнения тривиальных задач (create, update, remove, list и т.п.). И теперь своими процессорами можно просто расширять эти базовые, в которых уже прописано много полезного функционала, и процессор на создание своего объекта-записи может уложиться в 10 строк, включая проверку доступов, вывод результата и т.п. Но суть осталась та же — четко вернуть какой-то результат, а задача четко локализована.

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.