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

в 19:27, , рубрики: modx, 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.
Это позволяет использовать одни и те же процессоры как для серверной логики, так и псевдо-клиентской.
Если будет интерес, более подробно о коннекторах напишу позже.

Автор: Fi1osof

Поделиться

* - обязательные к заполнению поля