Fi1osof 01 июля 2013 0 2
Предыстория. (кому не интересно, пропустите)
Дело было еще почти два месяца назад, когда я только столкнулся с MODX 2.2.5 и «классными» процессорами (до этого я целый год был занят с одним из своих проектов на Рево 2.0.8, и с более новыми версиями Ревы вообще не работал. Да, много нового в Рево с тех пор появилось). И вот я экспериментирую с Рево, и первым делом именно с «классными» процессорами (как раз для modLivestreet процессор писал, который должен был расширить базовый процессор создания пользователя).
И вот тогда у нас с bezumkin вот такой немаленький диалог состоялся:

Я: Василий, привет!
Только вот сегодня пришлось покопаться с процессорами на рево 2.2.5
Вижу, что если сделать по новым стандартам, то не будет работать в старых версиях. А начиная с какой версии идут именно такие «классные» процессоры? Чтобы compare_version сделать.

ОН: С версии 2.2.0 — это основное ее изменение.

Я: Да уж, серьезное такое изменение…
Слушай, а у тебя в примерах только расширение базовых процессоров.
А расширения системных процессоров не делал? К примеру modUserCreateProcessor?
Я просто к тому, что не нашел метода их нативно подгружать. Видимо придется писать
require_once MODX_PROCESSORS_PATH.'security/user/create';
class modTestProcessor extends modObjectCreateProcessor......

Не сталкивался с такой задачей?

ОН: Именно так это и делается — все верное, надо require.

Я: ОК. Хотя считаю это не самый классный вариант. Вот раньше $modx->runSnippet() тупо выполнял нужный нам файл. Сейчас процессоры стали более умные, но получилась другая крайность — изолированность системных процессоров (для расширения). Сейчас в функции $modx->runProcessor добавилось масса проверок на тип процессора, поиск пути и т.п. (именно в плане файловой системы), но и там же сразу выполнение. Так вот я считаю, что было бы очень круто ту логику, которая отвечает за поиск файла процессора, вынести в отдельный метод, который бы вызывался методом $modx->runProcessor(), но и из других мест можно было вызывать, что-то типа $modx->getProcessor или $modx->findProcessor.

ОН: Ты бы документацию почитал, что ли.
Путь к процессору указывается — это и в моей заметке есть.

А runSnippet() выполняет нужное — ибо оно всегда лежит в одном и том же месте, в БД.

Я: Тогда можно было бы написать типа
if($modx->getProcessor('security/user/create')){
    $response = $modx->runProcessor('test');
}

или сразу в файле процессора test проверять этот процессор, и если косяк, то возвращать ошибку через
$modx->error->failure('Processor not found');


Ты бы документацию почитал, что ли.
Пытался. Что-то она кажется совсем отсталая. (я имею ввиду rtfm.modx.com). Я так понимаю, теперь только исходники на github читать.

Путь к процессору указывается — это и в моей заметке есть.
А runSnippet() выполняет нужное — ибо оно всегда лежит в одном и том же месте, в БД.
Да нет, я имею ввиду не базовые процессоры, а прочие. То есть те, классы которых просто так не подгружаются, надо именно runSnippet вызывать.
Сейчас код покажу и объясню, что имею ввиду.
Вот часть метода $modx->runSnippet();

public function runProcessor($action = '',$scriptProperties = array(),$options = array()) {
        // ......................................... 
        /* calculate processor file path from options and action */
        $isClass = true;
        // !!! Вот здесь формируется путь к процессору
        $processorsPath = isset($options['processors_path']) && 
!empty($options['processors_path']) ? $options['processors_path'] : $this->config['processors_path'];
        if (isset($options['location']) && !empty($options['location']))
 $processorsPath .= ltrim($options['location'],'/') . '/';
        $processorFile = $processorsPath.ltrim(str_replace('../', '', $action . 
'.class.php'),'/');
        if (!file_exists($processorFile)) {
            $processorFile = $processorsPath.ltrim(str_replace('../', '', $action . 
'.php'),'/');
            $isClass = false;
        }

        $response = '';
        
        // !!! Если файл есть, то вызываем  его
        if (file_exists($processorFile)) { 
            // ..................................
            if (empty($processor)) {
                $processor = new modDeprecatedProcessor($this);
            }
            $processor->setPath($processorFile);
            $processor->setProperties($scriptProperties);
            $response = $processor->run();
            
        // !!! Иначе ошибка
        } else {
            $this->log(modX::LOG_LEVEL_ERROR, "Processor {$processorFile} does not 
exist; " . print_r($options, true));
        }
        return $response;
}


Я же говорю о том, что правильней было бы типа
public function getProcessorFile($action = '',$scriptProperties = array(),$options = 
array()){ 
    // !!! Вот здесь формируется путь к процессору
        $processorsPath = isset($options['processors_path']) &&
 !empty($options['processors_path']) ? $options['processors_path'] : 
$this->config['processors_path'];
        if (isset($options['location']) &&
 !empty($options['location'])) $processorsPath .= ltrim($options['location'],'/') . '/';
        $processorFile = $processorsPath.ltrim(str_replace('../', '', $action . 
'.class.php'),'/');
        if (!file_exists($processorFile)) {
            $processorFile = $processorsPath.ltrim(str_replace('../', '', $action . 
'.php'),'/');
            $isClass = false;
        }
        
        // !!! Если файл есть, то вызываем  его
        if (file_exists($processorFile)) { 
            // ..................................
            return $processorFile;
            
        // !!! Иначе ошибка
        } else {
            $this->log(modX::LOG_LEVEL_ERROR, "Processor {$processorFile} does not 
exist; " . 
print_r($options, true));
            return false;
        } 
}

public function runProcessor($action = '',$scriptProperties = 
array(),$options = array()) {
        // ......................................... 
        /* calculate processor file path from options and action */
        $isClass = true;
        
        $response = '';
        // !!! Если файл есть, то вызываем  его
        if ($processorFile = $this->getProcessorFile($action = '',$scriptProperties = 
array(),$options = array())) { 
            // ..................................
            $className = include_once $processorFile;
            //....................................
            if (!empty($className)) {
                $c = new $className($this,$scriptProperties);
                $processor = 
call_user_func_array(array($c,'getInstance'),array($this,$className,$scriptProperties));
            }
            //....................................
            if (empty($processor)) {
                $processor = new modDeprecatedProcessor($this);
            }
            $processor->setPath($processorFile);
            $processor->setProperties($scriptProperties);
            $response = $processor->run();
            
        // !!! Иначе ошибка
        } else {
            $this->log(modX::LOG_LEVEL_ERROR, "Processor {$processorFile} does not 
exist; " . print_r($options, true));
        }
        return $response;
}


Вот тогда я бы мог попытаться подгрузить процессор, и если не получилось бы, вернул ошибку.
global $modx;
if(!$processorFile = $modx->getProcessorFile('security/user/create')){
    return false;
}
include $processorFile;
class modTestProcessor extends modUserCreateProcessor{
    // ......................
}

return 'modTestProcessor';


Он: Нахер все усложнять?

У метода runProcessor указывается любой путь к любым процессорам. Если по этому пути он процессор не найдет — вернет ошибку «processor not found».
Я просто не понимаю, зачем ты выдумываешь новые сущности, если уже все есть.

Посмотри любой кастомный компонент — там процессоры лежат в своих директориях. Вот я в miniShop обращаюсь к процессорам из сниппета.

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

Я: Ты меня явно не слышишь. Я не говорю про случай, когда мне надо подгрузить свой процессор. Безусловно проверка на нахождение моего файла прописана в методе $modx->runSnippet(), и если не найдет его, то вернет ошибку.
Но я говорю, что хочу, чтобы мой найденный процессор расширял другой объект процессора, и я хочу провести проверку этого процессора.
Вот у тебя процессор в твоем Loginza
require MODX_CORE_PATH.'model/modx/processors/security/user/update.class.php';
class LoginzaUpdateProcessor extends modUserUpdateProcessor {

Где у тебя тут проверка на наличие процессора modUserUpdateProcessor?
Если твой пакет поставить на чей-то отсталый Рево, у тебя не просто не выполнится процессор, выполнение MODX вообще развалится, потому что require — это критическая операция, и если файл не будет найден, то все, бабах.
С другой стороны проверять if(!file_exists(MODX_CORE_PATH.'model/modx/processors/security/user/update.class.php')) тоже не круто, потому что MODX до сих пор поддерживает modDeprecatedProcessor.
Потому хотелось бы вызывать такой метод, который бы проверял наличие файла процессора как современного, так и deprecated

Он: Для этого в репозитории указывается минимальная версия для работы компонента.
Для более старых компонент не покажется.

Я: Хорошо. Аргумент.
А если глупый юзверь удалил этот процессор, или тебе подгрузить надо какой-нибудь кастомный процессор?
Понятно дело, что на это можно придумать много чего, но с чем спорить невозможно, так это с тем, что проверки и отладка должны быть всегда, так вот разработчики MODX запихнув все проверки в метод runProcessor оставили эти проверки чисто для себя, и извне подобные проверки уже придется писать самим, а это не круто.

Он: У тебя видно дохера времени свободного.

Я: Нет, не дохера, но во-первых, привык до конца понимать то, с чем имею дело, а во-вторых, привык писать так, чтобы потом было легко обслуживать.
Какой смысл писать что-то, не понимая до конца что и как работает. Это как у меня один программист ответил на вопрос «тебе не кажется, что у тебя как-то все долго работает?»: «ну я там написал, а что именно выполняется, не знаю».

Итог
Переношу сегодня сайт на Рево и столкнулся с такой проблемой: открываю документ для редактирования, а сайт разваливается, ошибка 500.
В логах ошибка:
PHP Fatal error:  Cannot redeclare class modTemplateVarInputRenderText in /var/www
/..../public_html/core/model/modx/processors/element/tv/renders/mgr/input
/text.class.php on line 10

Ясно-понятно, что проблема с повторной подгрузкой одного и того же файла процессора с описанным классом. Но почему? Кстати, как раз вчера задумался, а как MODX проверяет подгружать файл процессора или нет, чтобы не произошло ошибки? Оказывается, проверка-то совсем не надежная. В этом плане MODX ориентируется только на путь до предполагаемого файла, при чем даже без учета realpath. То есть если симлинки или типа того, и будут переданы разные пути к одному и тому же файлу, то MODX не разберется, и подгрузит второй раз файл, и сайт развалится.
Ну да ладно, это к слову. А в данном случае критическая ошибка как раз из-за того, о чем я так много писал выше, то есть из-за отсутствия стандартного метода подгрузки файла процессора до его выполнения.

Из-за того, что нет нормального метода подгрузки файла процессора (с целью объявления нужного нам класса процессора), в объекте modTemplateVar есть вот такой хитрый код:
if (empty($output)) {
    $p = $this->xpdo->getOption('processors_path').'element/tv/renders
/mgr/'.$method.'/';
    $className = $method == 'output' ? 'modTemplateVarOutputRenderText' : 
'modTemplateVarInputRenderText';
    if (!class_exists($className) && file_exists($p.'text.class.php')) {
        $className = include $p.'text.class.php';
    }
    if (class_exists($className)) {
        $render = new $className($this);
        $output = $render->render($value,$params);
    }
}

То есть, давным давно, когда процессоры еще не были объектами, а были простыми php-файлами, и которые имело смысл только вызывать через метод $modx->runProcessor(), с их последующей подгрузкой там же, то ничего и выдумывать не надо было. Когда же процессоры стали «классными» (и не только потому что они превратились в классы, а еще и потому что это реально классная штука получилась), логично стало, что эти объекты захочется подгружать и использовать в различных целях.
В данном случае, этот хитрый перец (которые дописывал этот код), решил «А нафига я буду писать лишние строки? Это же все есть вооон в том процессоре. Давай ка я его использую для себя. Но я обязательно пропишу проверку, а вдруг этот класс уже инициализирован, и его подгружать уже нельзя». Да, проверку-то он прописал. А в самом $modx->runProcessor() эту проверку прописали? НЕТ. А главное — они и не могли ее прописать. Проблема в том, что файлы этих классных процессоров возвращают строку с названием выполняемого класса, но совершенно нет стандартов, как этот класс должен называться, что лично мне вообще не понятно. Я обязательно поинтересуюсь, кто был носителем идеи и внедрением «классных» процессоров, но думаю, что не Jason. В xPDO есть система имен файлов и классов в них, а здесь ее нет. В итоге MODX просто не может знать имя вызываемого объекта и решать подгружать файл или нет.

Резюмирую: на самом деле «классные» процессоры — это огромный прорыв. Это реально классная штука. Но они «выросли», они стали больше, чем просто файлы, подгружаемые в $modx->runProcessor(). Они превратились в очень мощные и самостоятельные объекты. О обязательно надо вводить функцию подгрузки этих классов а-ля $modx->loadClass(); А пока что это плюс ко всему, еще и источник головной боли.

P.S. Для тех, кто ждал, как же это фиксится: я решил, что раз уж так все не по религии сделано, то и я не буду из кожи лезть. Тупо в файл процессора прописал так:
if(!class_exists('modTemplateVarInputRenderText')){
    class modTemplateVarInputRenderText extends modTemplateVarInputRender {
        public function getTemplate() {
            return 'element/tv/renders/input/textbox.tpl';
        }
    }
}
return 'modTemplateVarInputRenderText';
2 комментария
Д
ДмитриЧ 24 июля 2016г в 17:24 #
Спасибо из 2016!
Fi1osof1
Fi1osof 24 июля 2016г в 17:29 #
Да, пуллреквест закрыли, не приняв, так что спасибки скорее всего будут и из какого-нибудь 2020-го года…
Авторизуйтесь или зарегистрируйтесь (можно через соцсети ), чтобы оставлять комментарии.