Fi1osof 01 апреля 2013 1 8
Сразу уточню, что материал рассчитан в основном на тех, что уже использует процессоры, знает что это такое и как их готовить. И вообще этот материал многим может показаться сухим и не интересным. Но кто программирует (Илья, это тебя особенно касается;-)), обязательно надо изучить материал. Поверьте на слово, что это очень мощный инструментарий.

Я не раз уже поднимал тему подгрузки файлов-процессоров средствами 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;
8 комментариев
abaddon651
abaddon65 01 апреля 2013г в 23:33 #
Честно признаться использовал метод loadClass, но только совместно с методом setPackage, а вот про addExtensionPackage услышал впервые.
В очередной раз спасибо за новую информацию.
Fi1osof1
Fi1osof 01 апреля 2013г в 23:35 #
Пожалуйста.
Я про это еще здесь писал: community.modx-cms.ru/blog/documentation/9226.html
abaddon651
abaddon65 01 апреля 2013г в 23:41 #
Да как только увидел её здесь и всемогущий google помог мне отыскать статью в которой и был он описан, сейчас знакомлюсь)
Fi1osof1
Fi1osof 02 апреля 2013г в 04:45 #
Топик дополнил новым материалом.
ilyautkin1
ilyautkin 02 апреля 2013г в 11:23 #
Кстати, есть второй способ прописать пакет, чтобы он подключался при инициализации 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 строчку и для процессоров. Тогда вообще не придется задумываться, что у меня какие-то левые объекты или процессоры — весь код будет стандартным))))
Fi1osof1
Fi1osof 02 апреля 2013г в 20:22 #
Привет, Илья!
Да, результат addExtensionPackacge() — это как раз запись в эту настройку. Так что и так, и так можно.
Надо попробовать добавить в extension_packages строчку и для процессоров. Тогда вообще не придется задумываться, что у меня какие-то левые объекты или процессоры — весь код будет стандартным))))
Не получится здесь одной строчкой обойтись. Задача того, что я делал — возможность подгружать любой процессор из любого пакета, при чем с автоформированием пути. Там слишком много тонкостей. Плюс мой метод возвращает реальное имя класса (сам знаешь, в процессорах используется return 'classname';) Все это никак не укладывается в одну строчку. Еще момент — ты вот своим методом подгрузишь мой процессор, а у меня там используется $this->loadClass(), а у тебя $this будет другим объектом. И все, код разваливается.
Здесь любое решение будет костылями (и лучше использовать одни костыли). Решить это можно только тогда, когда Джейсон новый релиз выпустит, учитывающий все эти моменты. Мы вчера с ним основательно пообщались на этот счет. Уверен, он и других людей мнение учитывает, так что будет стандартное решение.
vanchelo1
vanchelo 03 апреля 2013г в 02:11 #
Если такая возможность появится штатно, будет очень здорово! Как всегда, Спасибо тебе за статью!
Fi1osof1
Fi1osof 03 апреля 2013г в 02:15 #
Пожалуйста:-)
Авторизуйтесь или зарегистрируйтесь (можно через соцсети ), чтобы оставлять комментарии.