Николай Ланец
1 апр. 2013 г., 19:18

Подгрузка "классных" процессоров средствами API MODX

Сразу уточню, что материал рассчитан в основном на тех, что уже использует процессоры, знает что это такое и как их готовить. И вообще этот материал многим может показаться сухим и не интересным. Но кто программирует (Илья, это тебя особенно касается;-)), обязательно надо изучить материал. Поверьте на слово, что это очень мощный инструментарий.
Я не раз уже поднимал тему подгрузки файлов-процессоров средствами API MODX. Раньше, когда у нас процессоры были просто php-файлами, нас это вообще не интересовало, мы просто выполняли $modx->runProcessor($action) и все. MODX делал require этого файла и все, возвращал полученный результат. Сейчас же у нас процессоры стали гораздо умнее — их можно расширять другими процесс-классами и т.п. Процессоры сегодня могут содержать очень мощный функционал, и это часто полностью законченные, самостоятельные классы. Так зачем нам писать кучу лишнего кода, когда мы можем использовать уже готовые классы, переопределить их и заставить работать под свои нужды?
Рассмотрим стандартный механизм работы процессора (здесь и далее будем рассматривать только «классные» процессоры). Мы выполняем $response = $modx->runProcessor($action, $params, $options); MODX в методе modX::runProcessor определяет путь до файла процессора, и если это класс, то он его инициализирует и возвращает результат выполнения (по сути это всегда результат метода modProcessor::process()). Но что, если мы не хотим получить результат метода processor::process(), а хотим просто получить результат какого-то внутреннего метода класса? То есть нам нужен просто класс? Для этого мы просто подгружаем файл процессора require_once $processor_absolute_path. И тогда нам доступен сам класс процессора.
Но в чем здесь проблема? Во-первых, элементарно не удобно. То есть каждый раз нам надо писать актуальный путь до процессора. А если мы его переместили, и вообще в другой пакет? Идти высчитывать кол-во методов dirname(), писать папки и т.п.
Во-вторых, отсутствие стандартного метода и единых проверок может даже привести к фатальным ошибкам.
В общем, я и с Джейсоном пытался этот вопрос обсуждать, но как-то мы не пришли к какому-то единому знаменателю (я Джейсона пытался убедить в необходимости ввести метод типа modX::loadProcessor()). А вот Марк Mark-H мне сегодня подкинул очевидную идею — использовать $modx->loadClass(); Не могу даже понять почему я раньше до этого не догадался, но его подсказки хватило, чтобы сразу увидеть удобство этого подхода и решение моих проблем.
Суть метода xPDO::loadClass заключается в том, что он ищет указанный класс во всех указанных папках подключенных пакетов (если не передан параметр, запрещающий делать это). То есть вы один раз можете выполнить $modx->addPackage($packName, $path); и далее уже выполнять подгрузку файла процессора так: $modx->loadClass($classname, '', false, true); (третий параметр false указывает на то, что не надо игнорировать подключенные пакеты, и надо искать по их папкам, а четвертый параметр true указывает на то, что надо игнорировать класс работы с базой данных (ведь у нас это просто процессор)). Все. Теперь не надо высчитывать пути.
Как этот процесс максимально оптимизировать? Каждый раз писать $modx->addPackage() мягко выражаясь — не очень удобно. Потому лучше само собой добавить это в extensionPackages. Но надо учитывать две задачи: 1. Уникальность имен пакетов (то есть нельзя два пакета назвать processors, при этом именно название пакета плюсуется к конечному пути пакета). А ведь нам очень желательно, чтобы процессоры лежали в папке processors, чтобы не рушить стандарты (тот же processor-плагин пакета modxSmarty ищет процессоры именно в папке processors). 2. Возможность указывать несколько пакетов с процессорами.
В общем, обычно экстеншены ведут к папке component/model/packageName/. Нам этот путь не подходит, так как противоречит вышеизложенным правилам. Значит делаем два пакета. Один ведет в component/model/packageName/ (он нам нужен для хранения основных классов, в том числе и тех, которые работают с базой данных), а второй в component/processors/packageName/. Через консоль выполняем два запроса:
$modx->addExtensionPackage('myPackage', '[[++core_path]]components/myPackage/model/',array( "serviceName" => "myservice", "serviceClass" => "myClass", ));
В этом запросе мы не просто добавили свой пакет. Указав serviceName и serviceClass, у нас каждый раз при инициализации MODX будет выполнять еще и $modx->getService($name, $class); То есть у нас всегда в MODX будет свой объект $modx->myservice, который будет инстансом класса myClass.
$modx->addExtensionPackage('myProcessors', '[[++core_path]]components/myPackage/processors/');
А вот это уже мы добавили папку для наших процессоров. То есть надо будет создать папку myPackage в [[++core_path]]components/myPackage/processors/, и там создавать наши процессоры. Код нашего сервис-класса будет выглядеть так:
class myClass{ public $modx = null; function __construct(modX & $modx) { $this->modx = & $modx; } public function loadProcessor($fqn){ return $this->modx->loadClass($fqn, '', false, true); } }
То есть теперь, чтобы не писать каждый раз эти лишние 3 параметра, можно просто выполнять $modx->myservice->loadProcessor($classname); Кстати, не забывайте, что в $classname знак точки — это разделитель директорий, то есть.
И вот теперь один небольшой пример:
$modx->myservice->loadProcessor('path.myclass'); if($instance = myclass::getInstance($modx, 'myclass' )){ $response = $instance->run(); print_r($response->getResponse()); }
В данном случае мы могли не только стандартный метод processs выполнить, но и любой другой дозволенный.
Так же если вы свои процессоры пишите, и какие-то зависят от других, то теперь не обязательно писать require_once, а можно использовать этот метод, выполняя в начале кода $this->myservice->loadProcessor();
Я сейчас включу этот функционал в обновленную сборку и выложу новый снимок.
UPD: В этом методе есть один неприятный момент. xPDO::loadClass не подгрузит два одинаковых по названию файла в разных папках, и не возвращает реальные имена классов процессоров. Переписал этот метод в классе modxsite: gist.github.com/Fi1osof/a87522b0b0ea9e60a2ed/revisions
Но это временное решение. Разговаривал с Джейсоном. Нас скоро ждут кардинальные перемены в ядре MODX-а, в том числе и автоподгрузка классов. И вообще он смотрит в сторону удаления метода loadClass. Это все классно, но мне некоторых плюшек не хватает уже сегодня, потому добавляю это в новую сборку (чуть попозже выложу). Основной метод: $modx->modxsite->loadProcessor(), возвращающий реальное название процесор-класса. Обкатывать это будет уже в процессе.
UPD2: Обновленная сборка с новым методом $modx->modxsite->loadProcessor(); Так же в сборку были добавлены два пакета:
  • PackMan (для быстрой сборки пакетов их имеющегося на сайте)
  • CMPgenerator (для генерации моделей пакетов из таблиц в базе данных)
Если кому-то эти пакеты не известны, изучите в обязательном порядке.
UPD3: Данный метод изначально не предполагался как идеальный, а только временное решение, но вот нашелся в нем большой минус, который накладывает серьезные ограничения — область видимость. Традиционно файлы классов подгружаются самим MODX-ом, и в самом файле (за пределами описания класса) $this — это текущая инстанция MODX-а. При вызове же через $modx->modxsite->loadProcessor $this — это $modx->modxsite, который ни в коем случае не является инстанцией MODX. Ввиду этого не получается использовать $this в этих классах. Понятно, что мало кто этот хук вообще использует, но тем не менее. Можно было бы использовать $this->loadProcessor() для подгрузки других процессоров, но это сопровождается теми же проблемами, плюс к этому еще и мешает использовать этот процессор штатными средствами. По этой причине придется все так же использовать require, а $modx->loadProcessor() использовать исключительно для подгрузки только тех процессоров, которые не используют переменную $this. Можно использовать в файлах классов так:
if($this instanceof modX){ $modxsite = & $this->modxsite; } else{ $modxsite = & $this; } $modxsite->loadProcessor('class');
Но учтите, что хотя если вы про это и забудете, оно будет работать, но по хорошему, лучше этим не злоупотреблять. Только если у вас стабильная выработанная методология. В принципе по мне, так это меньшее зло, чем require_once abs_path;
Честно признаться использовал метод loadClass, но только совместно с методом setPackage, а вот про addExtensionPackage услышал впервые. В очередной раз спасибо за новую информацию.
Пожалуйста. Я про это еще здесь писал: community.modx-cms.ru/blog/documentation/9226.html
Да как только увидел её здесь и всемогущий google помог мне отыскать статью в которой и был он описан, сейчас знакомлюсь)
Топик дополнил новым материалом.
Кстати, есть второй способ прописать пакет, чтобы он подключался при инициализации MODx (по идее это тот же способ, только ручками): заходим в настройки системы, раздел «Система и сервер» и находим там параметр «Пакеты расширений» (extension_packages). Если мы до этого хоть раз запускали addExtensionPackage(), то в его значении уже будут все параметры в JSON — их можно исправить, что-нибудь добавить к ним… Если addExtensionPackage() не запускали, то можно там прописать ручками:
[{"Rehab": { "path":"[[++core_path]]components/rehab/model/", "tablePrefix":"modx_rehab_", "serviceName":"Rehab", "serviceClass":"Rehab" } }]
(конечно, в одну строчку)
Для разных нужных методов, которые используются в разных местах я создал отдельный класс Rehab (в папке пакета) и в нем прописываю функционал. Благодаря этому я в любом месте могу написать
$modx->Rehab->phonesToJSON($phones);
и массив обработается с нужными проверками, дефолтными значениями. И результат будет каким надо. Если потребуется изменить алгоритм обработки телефонов, я всегда знаю, где искать этот код.
А вот пути для процессоров прописывать приходится, конечно… Надо попробовать добавить в extension_packages строчку и для процессоров. Тогда вообще не придется задумываться, что у меня какие-то левые объекты или процессоры — весь код будет стандартным))))
Привет, Илья! Да, результат addExtensionPackacge() — это как раз запись в эту настройку. Так что и так, и так можно.
Надо попробовать добавить в extension_packages строчку и для процессоров. Тогда вообще не придется задумываться, что у меня какие-то левые объекты или процессоры — весь код будет стандартным))))
Не получится здесь одной строчкой обойтись. Задача того, что я делал — возможность подгружать любой процессор из любого пакета, при чем с автоформированием пути. Там слишком много тонкостей. Плюс мой метод возвращает реальное имя класса (сам знаешь, в процессорах используется return 'classname';) Все это никак не укладывается в одну строчку. Еще момент — ты вот своим методом подгрузишь мой процессор, а у меня там используется $this->loadClass(), а у тебя $this будет другим объектом. И все, код разваливается. Здесь любое решение будет костылями (и лучше использовать одни костыли). Решить это можно только тогда, когда Джейсон новый релиз выпустит, учитывающий все эти моменты. Мы вчера с ним основательно пообщались на этот счет. Уверен, он и других людей мнение учитывает, так что будет стандартное решение.
Если такая возможность появится штатно, будет очень здорово! Как всегда, Спасибо тебе за статью!

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