Да, это я в примере не прописал путь к пакету. Вместо
$modx->addPackage('Geoip');
надо
$modx->addPackage('Geoip', $modx->getOption('core_path').'components/geoip/model/');
скачал, установил...
[2013-05-07 13:26:37] (ERROR @ /index.php) Invalid path specified for package: Geoip; using default xpdo model path: /paas/c0100/www/core/xpdo/om/ [2013-05-07 13:26:37] (ERROR @ /index.php) Could not load class: Geoip from mysql.geoip.
что за беда?
Наверняка много кто использовал на своих сайтах пакет phpThumbOf — отличная штука, чтобы сделать превьюшки. Простой пример в использовании:
[[phpthumbof?input=`[[+source]]`&options=`w=350&h=350&zc=1`]]
На лету создаст превьюшку 350 на 350.
Но с этим пакетом не все так просто.
Проблема первая. Чтение кеша
Важно понимать, что это не самостоятельный пакет. Он использует имеющийся в MODX класс phpthumb. А тот в свою очередь уже был ранее замечен, как источник проблем с производительностью. Дело в том, что phpthumb перед созданием кеша файла, подсчитывает общий объем кеша, и это процедура совсем не слабая, если файлов довольно много. Чтобы этого избежать, идем в настройки MODX-а и устанавливаем значение 0 для следующих настроек: phpthumb_cache_maxage, phpthumb_cache_maxfiles и phpthumb_cache_maxsize. Тогда phpthumb не будет подсчитывать объем использованного кеша.
Проблема вторая. Низкая производительность.
Сейчас делаю каталог недвижимости, и там на одну страницу выводится 30 карточек. Так вот, на генерацию 30-ти превьюшек уходит 3-4 секунды. Конечно в дальнейшем эти превьюшки уже будут, и повторной генерации не будет, но от этого все равно не легче, так как на сайте более 4000 активных объектов, и будет очень много тормознутых страниц. Да и сниппету каждый раз проверять наличие файла превьюшки — тоже лишняя работа.
В общем, эту проблему я решил исправить радикально — просто заранее сгенерировать превьюшки и сохранить их для документа в отдельное TV-поле. Итак, все по порядку:
1. Создаем TV-шку для превьюхи. С этим все просто и понятно. Единственное — если на сайте будут работать менеджеры, то настройте права доступа на нее, чтобы они даже не видели это поле (это прям в редакторе TV-шки есть вкладка Права доступа).
2. Пишем плагин на сохранение документа. Замысел прост — при сохранении документа, плагин должен проверять значение TV-шки с необрезанной фоткой, и если получает, то выполняет сниппет phpThumbOf и записывает полученный путь до превьюшки в эту специальную TV-шку. Код плагина:
<?php
switch ($modx->event->name) {
case 'OnDocFormSave':
// На всякий случай меняем контекст, если MODX сам не поменял на контекст документа
$origin_context = null;
if ($modx->context->key == 'mgr') {
$origin_context = $modx->context->key;
$modx->switchContext('web');
}
// Создаем самбнейл превьюшки
$TV_source = 2; // Устанавливаете значения своих TV-шек
$TV_target = 17;
$resource = &$scriptProperties['resource'];
if ($photo = $resource->getTVValue($TV_source) and $thumb = $modx->runSnippet('phpthumbof', array(
'input' => $photo,
'options' => 'w=113&h=113&q=100&zc=1',
))
and $photo != $thumb
) {
$resource->setTVValue($TV_target, $thumb);
}
// Если контекст меняли, то меняем его на изначальный
if ($origin_context) {
$modx->switchContext($origin_context);
}
break;
default:;
}
Все. Теперь при сохранении документа сразу будет генериться и сохраняться превьюшка. Но что делать, если уже документы есть, и их много? Ведь надо каждый документ пересохранить… Но с этим тоже все довольно просто — не обязательно каждый документ открывать, можно документ пересохранить программно, через API. Лучше всего это делать через Console. Пишем и выполняем вот такой код:
<?php
ini_set('max_execution_time', 0);
ignore_user_abort(1);
$TV_source = 1; // Устанавливаете значения своих TV-шек
$TV_target = 2;
$q = $modx->newQuery('modResource');
$q->leftJoin('modTemplateVarResource', 'tv', "tv.tmplvarid={$TV_source} AND tv.contentid=modResource.id");
$q->leftJoin('modTemplateVarResource', 'tv2', "tv2.tmplvarid={$TV_target} AND tv2.contentid=modResource.id");
$q->where(array(
'tv.id:!=' => null,
'tv.value:!=' => '',
'tv2.id' => null,
'published' => 1,
));
print "Total: " . $modx->getCount('modResource', $q); // Общее число оставшихся документов
// return;
$q->limit(10);
if ($docs = $modx->getCollection('modResource', $q)) {
foreach ($docs as $doc) {
$modx->error->reset();
$modx->runProcessor('resource/update', $doc->toArray());
}
}
Здесь мы получаем документы с картинками, у которых есть картинки, но нет еще превьюшек, и обновляем их через процессор. А в процессоре уже вызывается событие OnDocFormSave и само собой и наш процессор, срабатывающий на это событие.
Вот теперь, после того, как у нас уже есть все превьюшки в специальном TV, нам не надо уже в коде страницы вызывать сниппет phpThumbOf, мы просто вставляем значение этой TV-шки. Нагрузка падает, мы радуемся :-)
UPD: Чуть-чуть дописал плагин. Добавил смену контекста, если текущий контекст — mgr. Дело в том, что для разных контекстов могут быть указаны различные источники файлов, а для mgr этот параметр в TV почему-то не задается, и если источник файлов TV-параметра не базовый (корневой), а контекст mgr, то TV-шка просто вернет неверный результат.
UPD: Добавил еще проверку AND $photo != $thumb. Причина — pThumb, если не смог получить картинку или еще что (ошибка в общем) возвращает оригинальное изображение. Если мы запишем оригинальный путь, то у нас и TV-поле будет заполнено (и мы не будем знать, что для этого ресурса не была создана превьюшка), и значение будет не правильным. Будет потом много гемора.
Справедливости ради стоит отметить, что этот баг пофиксен месяца два назад. Вот последнее упоминание этой причины. Так что версии старше 2.0.0 можно удалять без особых.
Как-то ставил я minishop2, чтобы посмотреть что и как. А сегодня решил удалить его (не нужен). Удаление прошло довольно успешно (не считая кучи ошибок в логах), но что интересно — папка assets/images/ с кучей картинок самого сайта исчезла полностью. Не сложно было увязать между собой эти два события. Откатился назад (благо бекапы на modxcloud.com стабильно создаются), запустил деинсталлятор minishop-а опять и смотрю трассировку. А там картина маслом:
?
То есть папка assets/images/ удаляется начисто. И вот зачем такое надо? Как показывают исходники, MS2 действительно для себя пытается создавать папку assets/images/, видимо думая, что эта папка никогда никому больше не может пригодиться, а стандарты assets/components/{package} вообще зря придумали.
Вот интересно, как скоро багфикс появится, или это не считается багом? Спасибо автору расширения, что ядро не удаляет.
Опытным путем выяснил, что можно, к примеру, и вот такое творить: {assign var=cost value=array('500000 руб.', '3000000 руб.', '5000000 руб.')}
А ещё непонятно, какой тег надо указывать, иначе не отправляется сообщение?
Вы уже настолько хорошо знаете HTML-теги, что любой тег воспринимаете как HTML :) Нет, здесь тег — это просто метка, то есть какое-то ключевое слово (или несколько). Можете писать что угодно, подходящее по смыслу.
По вопросу: в том топике говорилось о том, как залить картинку в принципе, и как ее загрузить в содержимое страницы. Но в содержимое страницы (в поле Контент) можно залить сколько угодно картинок. А в каталогах как правило картинки выводятся из специальных полей (чаще всего это дополнительные TV-поля). Вот и у вас эти картинки так же в TV-полях. Просто такие поля часто разбиваются по категориям и не сразу их все видно. Вот ваш случай:
?
Я думаю дальше вы уже разберетесь, там привычный файловый менеджер.
Раньше было поле «картинка к товару», через которое можно было закачать фото к товару. Тот пример, который дан в разделе для пользователей, показывает, как закачать фотку в текст документа, а не к товару. Не могу найти такое поле. Или теперь это делается совсем по-другому? Нужно сменить фото к товару Слингобусы Радуга (1011) на busi2_2. В ФАЙЛЫ его закачала.
А ещё непонятно, какой тег надо указывать, иначе не отправляется сообщение?
Я думаю, много уже кто выполнял SQL-запросы через $modx->newQuery(), ->prepare(), ->execute(); Но далеко не все знают, что таким образом можно выполнять не только SELECT-запросы, но и UPDATE и DELETE. Для этого существуют методы xPDOQuery::command() и xPDOQuery::set().
Метод xPDOQuery::command() по умолчанию устанавливает тип запроса SELECT, но как видите, может и другие типы устанавливать.
public function command($command= 'SELECT') {
$command= strtoupper(trim($command));
if (preg_match('/(SELECT|UPDATE|DELETE)/', $command)) {
$this->query['command']= $command;
if (in_array($command, array('DELETE','UPDATE'))) $this->_alias= $this->xpdo->getTableName($this->_class);
}
return $this;
}
Приведу небольшой пример запроса:
$c = $modx->newQuery('modResource');
$c->command('update');
$c->set(array(
'hidemenu' => 1
));
$c->where(array(
'parent' => 11,
));
$c->prepare();
// print $c->toSQL();
$c->stmt->execute();
Конечный SQL-запрос этого скрипта:
UPDATE `modx_site_content` SET `hidemenu` = 1 WHERE `modx_site_content`.`parent` = 11
Как видите, у нас здесь и условия поиска, и установка новых значений есть. Само собой можно и более сложные запросы набросать.
Второй вопрос — отладка таких запросов. Ведь при выполнении таких запросов при ошибках на уровне базы данных вы просто ничего не увидите. Здесь требуются дополнительные движения.
В первую очередь надо учесть, что при выполнении метода $c->prepare() мы получаем объект PDOStatement (как результат выполнения метода xPDO::prepare())
/**
* @see http://php.net/manual/en/function.pdo-prepare.php
*/
public function prepare($statement, $driver_options= array ()) {
if (!$this->connect()) {
return false;
}
return $this->pdo->prepare($statement, $driver_options= array ());
}
Соответственно и работать надо именно с этим объектом и на уровне его методов. (собственно, это и происходит, когда мы выполняем $c->stmt->execute(), $c->stmt->fetchAll() и т.п.). И для вывода ошибок нам и нужно работать с этим объектом. К примеру, чтобы получить информацию о возникших SQL-ошибках, можно выполнить print_r($c->stmt->errorInfo());
Тематика этого топика будет довольно обширная, но опишу все в одном топике, так как все связано. Постараюсь кратко.
Для начала о пакетах
Любой, кто хоть что-то пытался разработать на MODX-е, знаком с менеджером пакетов (ставил Wayfinder, getResource и т.п.). При этом далеко не все, кто уже что-то пишет под MODX самостоятельно (сниппеты, плагины и т.п.), оформляет свой код в пакеты. А вот это очень и очень зря. Во-первых, часто такой код плохо локализован, разбросан по сайту и мало с чем совместим, он просто есть на сайте и все, но его сложно отделить от сайта и перенести на другой сайт. А это уже и риски того, что при обновлении сайта что-то может сломаться, да еще и потеря собственных наработок. Так что, учитесь оформлять все свои наработки в пакеты. И это совершенно не сложно. Если у вас простой пакет, без каких-то выполняемых при установке скриптов (создания таблиц в базе данных и т.п.), то вы можете пакеты собираться packMan-ом. Там все просто и понятно. Упаковать можно шаблоны, сниппеты, чанки, плагины и другие пакеты, а так же любые директории сайта.
Пара рекомендаций.
1. Если что-то дорабатываете на своем сайте, всегда создавайте тематический неймспейс (namespace) и категорию. Чанки, сниппеты и т.п. обязательно по категориям распределяйте.
2. Скрипты и т.п. размещайте в папки в соответствии со стандартами (core/components/[namespace], assets/components/[namespace], manager/components/[namespace]).
3. Этот процесс может вам значительно упростить CMPgenerator. Вообще его прямое предназначение — генерировать модели и объекты из таблиц базы данных (и вы конечно же можете использовать его по назначению), но еще он умеет создавать правильно папки компонентов (останется только свои файлы по этим папкам раскидать).
Об обратной совместимости
Давайте рассмотрим простую ситуацию: вы написали для себя простой и полезный пакетик myPack-1.0.0-beta, и вы его установили на 5 своих сайтов. Все замечательно работает. А потом раз, и появилась необходимость этот компонент доработать (функционал чуть-чуть переделать). Вы делаете это на одном из этих сайтов, дорабатываете пакет и выпускаете новую версию myPack-2.0.0-beta. Затем вы этот пакет устанавливаете но другом сайте поверх пакета 1.0.0, и он перестает работать, ошибки лезут…
Да, такое бывает часто. Основные причины:
1. Использование в компоненте функционала сторонних пакетов (не ядра системы). Здесь все просто. Представьте, что в своем компоненте вы использовали выборку документов через getResources. А есть ли гарантия, что на любом сайте, куда будет устанавливаться пакет, будет установлен getResources? Само собой нет. И это надо учитывать при разработке компонента.
2. Модификация кода компонента после установки. Очень распространенная ошибка. Часто, после установки каких-то компонентов, программисту что-то да не хватает в них. Он берет, и подправляет пару строчек. А потом устанавливается обновление пакета, и все затирается… И эти пара строк теряются.
Вот эта проблема чаще всего висит на совести разработчика компонента. Надо компонент разрабатывать так, чтобы можно было переопределять параметры, системные настройки и т.п. Особенно когда есть чанки для оформления вывода, надо обеспечивать возможность переопределения этих чанков (в сниппетах использовать переменные $tpl и т.п.).
3. Смена имен переменных, методов и т.п. Вот у вас был сниппет, который принимал параметр $tpl. Вы решили, что это не достаточно гибко, надо разбить на $outerTpl и $innerTpl. Сказано — сделано. У нас есть новые две переменные, и убили старую переменную. Но там, где этот пакет уже использовался, сниппет вызывался с параметром $tpl. Как вы думаете, каков будет результат после установки новой версии пакета? Результат вполне предсказуемый.
Вот эта третья причина как раз и имеет самое непосредственное отношение к обратной совместимости. Когда вы разрабатываете новую версию компонента, нельзя забывать про то API, что имелось в предыдущих версиях пакета. К примеру, тот же MODX Revolution очень длительное время поддерживал API MODX Evolution (теги не берем во внимание).
К слову, я вчера на одном сайте обновлял сразу три своих пакета phpTemplates, modxSmarty и shopModx. Версии были установлены уже довольно старые. После обновления всех трех пакетов, сайт продолжил работать как ни в чем не бывало. Просто появился новый функционал, который позволил раз в 10 сократить объем кода компонентов самого сайта, использующих эти пакеты (я переписал функционал сайта). Вот к этому как раз и надо стремиться — желая улучшить свой компонент, не убивайте жестко имеющийся в нем функционал. А если убиваете, делайте это постепенно, с предупреждениями.
О стандартах и процессорах на замену сниппетам
Внимание! Если вы еще не сильны в ООП, дальнейший материал будет крайне сложным, так что читать можно скорее всего только из любопытства, но польза его для вас будет крайне сомнительна, разве что как пища для размышлений
В последнее время я очень активно использую процессоры вместо сниппетов, и несколько раз писал об этом. В частности и в shopModx были включены особые процессоры для выполнения различных выборок. И некоторые люди выражали великое сомнение на этот счет, мол, процессоры — это совсем для другого, и нет в них необходимости и т.д. и т.п. А вот я скажу, что процессоры по нескольким параметрам значительно выигрывают по сравнению со сниппетами. Но отдельно выделю только два момента.
Момент первый. Расширяемость.
Вот этого нет в сниппетах, и не уверен, что будет (хотя не буду утверждать обратного). Каждый сниппет — это отдельная функция, а как известно в PHP нельзя переопределять функции, расширять их и т.п. Можно только написать новую функцию. А процессоры — это классы (старые не «классные» процессоры во внимание не беру). Вот классы уже можно расширять, переопределять и т.п. Это дает очень серьезную гибкость в разработке, а так же значительно упрощает сопровождение кода. Приведу очень большой, но довольно наглядный пример, состоящий из четырех расширяющих друг друга процессоров.
Процессор первый. Получает товары.
<?php if($this instanceof Modxsite){ $modxsite = & $this; } else{ $modxsite = & $this->modxsite; } $modxsite->loadProcessor('web.resourceproduct.getdata', 'shopmodx'); class modWebCatalogProductsGetdataProcessor extends modWebResourceproductGetDataProcessor{ public function initialize() { if(!$this->getProperty('sort')){ $this->setProperty('sort', "{$this->classKey}.menuindex"); $this->setProperty('dir', "ASC"); } return parent::initialize(); } protected function prepareCountQuery(xPDOQuery &$query) { $query = parent::prepareCountQuery($query); $query->innerJoin('modTemplateVarResource', 'image', "image.contentid = {$this->classKey}.id and image.tmplvarid=8 and image.value != ''"); $query->where(array( 'deleted' => 0, 'hidemenu' => 0, 'published' => 1, )); return $query; } /* * Подготавливаем данные товара */ public function iterate(array $data) { $data = parent::iterate($data); // УРЛ источника картинок: $imagesUrl = $this->modx->runSnippet('getSourcePath'); foreach($data as & $d){ // Получаем картинку $d['image'] = $imagesUrl.$d['tvs']['sm_image']['value']; // Набиваем иконки $icons = array(); if(!empty($d['tvs']['sm_new']['value'])){$icons[] = 'i-new';} if(!empty($d['tvs']['sm_stock']['value'])){$icons[] = 'i-stock';} if(!empty($d['tvs']['sm_discount']['value'])){$icons[] = 'i-discount';} $d['icons'] = $icons; } return $data; } } return 'modWebCatalogProductsGetdataProcessor';
Конечно этот процессор не самый корневой, так как он расширяет shopModx-овый modWebResourceproductGetDataProcessor, а тот в свою очередь расширяет еще несколько процессоров, но это не принципиально, так как для нас это процессор из другого компонента, а в рамках нашего сайта представленный процессор является корневым. Но для полной управляемости нам само собой необходимо это знать и изучить shopModx-процессоры отдельно.
Итак, shopModx-овый процессор делает выборку документов товаров, но без каких-то дополнительных условий, то есть не учитывает опубликован документ или нет, удален или нет и т.п. Плюс к этому оформляет все в виде массива данных со всеми TV-шками (довольно подробно об этом писал здесь). Так что же я делаю в своем пользовательском процессоре, расширяющем базовый?
1. Добавляю условие поиска только опубликованных документов товаров, не удаленных и не скрытых в меню.
$query->where(array( 'deleted' => 0, 'hidemenu' => 0, 'published' => 1, ));
2. Добавляю условие поиска только тех товаров, у которых есть картинки.
$query->innerJoin('modTemplateVarResource', 'image', "image.contentid = {$this->classKey}.id and image.tmplvarid=8 and image.value != ''");
3. В конечный вывод сразу добавляю переменную полного пути до картинки.
$d['image'] = $imagesUrl.$d['tvs']['sm_image']['value'];
Все. Теперь этот процессор будет мне возвращать данные о товарах только соответствующим этим параметрам. К примеру я могу вызывать процессор так:
<?php if($response = $modx->runProcessor('web/catalog/products/getdata', array( where => array( 'parent:in' => array(177,178,179), ), 'limit' => 2, ), array( 'processors_path' => $path, ))){ $products = $response->getObject(); }
В данном случае процессор вернет только два товара, имеющихся в моделях-разделах с ID 177,178,179. Поменяю limit — вернет другое количество. Поменяю условие where — само собой будет поиск с другими условиями.
Но на этом сайте есть особенность: товары — это как бы различные вариации моделей. То есть есть модель такая-то, а у нее может быть неопределенное количество вариаций (разные цвета, исполнения и т.п.). Так вот, чаще всего надо делать не просто выборку товаров, а именно по одной вариации на одну модель (сделал поиск товаров по условиям, но в выборку попадает только одна вариация для одной модели). Для этого есть расширяющий процессор, получающий модели товаров.
<?php /* * Получаем данные только моделей товаров */ require_once dirname(__FILE__).'/getdata.class.php'; class modWebCatalogProductsGetmodeldataProcessor extends modWebCatalogProductsGetdataProcessor{ protected $modelsIDs = array(); public function initialize() { if(!$this->getProperty('sort')){ $this->setProperty('sort', "Models.menuindex"); $this->setProperty('dir', "ASC"); } return parent::initialize(); } // Готовим запрос на выборку уникальных объектов protected function PrepareUniqObjectsQuery(xPDOQuery &$query) { // Группируем по родителям $query->groupby("{$this->classKey}.parent"); return parent::PrepareUniqObjectsQuery($query); } /* * Подсчитываем количество товаров именно по ID-шникам их предков (то есть именно моделей), * так как нам нужны уникальные модели */ protected function countTotal($className, xPDOQuery &$criteria) { $count= 0; if ($query= $this->modx->newQuery($className, $criteria)) { if (isset($query->query['columns'])) $query->query['columns'] = array(); $query->select(array ("COUNT(DISTINCT {$this->classKey}.parent)")); if ($stmt= $query->prepare()) { // print $query->toSQL(); if ($stmt->execute()) { if ($results= $stmt->fetchAll(PDO::FETCH_COLUMN)) { $count= reset($results); $count= intval($count); } } } } return $count; } public function prepareQueryBeforeCount(xPDOQuery $c){ $c = parent::prepareQueryBeforeCount($c); $c->innerJoin('modResource', 'Models', "Models.id = {$this->classKey}.parent"); return $c; } public function setSelection(xPDOQuery $c) { $c = parent::setSelection($c); $c->select(array( 'Models.id as model_id', 'Models.pagetitle as model_title', )); return $c; } } return 'modWebCatalogProductsGetmodeldataProcessor';
Здесь происходит все то же самое, что и в родительском процессоре (то есть те же проверки на опубликованность, наличие картинки и т.п.), но плюс к этому выполняется выборка именно уникальных товаров для уникальных моделей. Конечный массив данных почти не отличается от результата выполнения предыдущего процессора, только в массив данных товара попадут еще ID модели и заголовок модели.
Далее у нас задача еще усложняется. Нам надо сделать выборку всех моделей товаров из целого раздела на два три вложенности. И вот еще один процессор:
<?php /* * Делаем выборку товаров полностью по категории, то есть Входные двери, Межкомнатные или фурнитура */ require_once dirname(dirname(__FILE__)).'/getmodeldata.class.php'; class modWebCatalogProductsBycatalogsectionGetdataProcessor extends modWebCatalogProductsGetmodeldataProcessor{ protected $modelsCount = 0; // Общее число моделей protected $productsCount = 0; // Общее число товаров /* * Проверяем наличие переменной ID категории */ public function initialize() { if(!((int)$this->getProperty('categoryid', false))){ return 'Не был указан ID категории'; } $this->setDefaultProperties(array( 'limit' => 10, )); return parent::initialize(); } /* * Делаем поиск моделей товаров, чтобы в дальнейшем искать конечные товары */ public function beforeQuery() { // Собираем ID-шники моделей только в одной категории $c = $this->modx->newQuery('modResource'); $c->innerJoin('modResource', 'Parent'); $c->where(array( 'Parent.parent' => (int)$this->getProperty('categoryid'), 'Parent.deleted' => 0, 'Parent.hidemenu' => 0, 'Parent.published' => 1, 'deleted' => 0, 'hidemenu' => 0, 'published' => 1, 'class_key' => 'ShopmodxResourceProductModel', )); $c->select(array( "DISTINCT modResource.id" )); if($c->prepare() AND $c->stmt->execute() AND $rows = $c->stmt->fetchAll(PDO::FETCH_ASSOC)){ foreach($rows as $row){ $this->modelsIDs[] = $row['id']; } } else{ $this->modx->log(xPDO::LOG_LEVEL_ERROR, "Не удалось получить ID-шники моделей товаров"); $this->modx->log(xPDO::LOG_LEVEL_ERROR, print_r($c->stmt->errorInfo(), true)); } /* * categoryid */ return parent::beforeQuery(); } protected function prepareCountQuery(xPDOQuery &$query) { $query = parent::prepareCountQuery($query); $query->select(array( 'parent' )); $query->where(array( 'parent:IN' => $this->modelsIDs, )); // Подсчитываем общее количество моделей и вариантов $this->countModelsAndVariables($query); return $query; } // Подсчитываем общее количество моделей и вариантов protected function countModelsAndVariables(xPDOQuery $query){ $c = clone $query; if (isset($c->query['columns'])) $c->query['columns'] = array(); $c->select(array( "COUNT(DISTINCT {$this->classKey}.id) as productsCount", "COUNT(DISTINCT {$this->classKey}.parent) as modelsCount", )); if ($stmt= $c->prepare()) { if ($stmt->execute()) { if ($results= $stmt->fetchAll(PDO::FETCH_ASSOC)) { if($result = current($results)){ $this->modelsCount = $result['modelsCount']; $this->productsCount = $result['productsCount']; } } } } return; } public function prepareQueryAfterCount(xPDOQuery $c) { $c = parent::prepareQueryAfterCount($c); $c->innerJoin('modResource', 'vendor', "Models.parent=vendor.id"); return $c; } public function setSelection(xPDOQuery $c) { $c = parent::setSelection($c); $c->select(array( 'vendor.id as vendor_id', 'vendor.pagetitle as vendor_title', )); return $c; } /* * Готовим окончательный вывод */ public function outputArray(array $array, $count = false) { $output = parent::outputArray($array, $count); $output['modelsCount'] = $this->modelsCount; // Выводим общее кол-во моделей $output['productsCount'] = $this->productsCount; // Выводим общее кол-во товаров return $output; } } return 'modWebCatalogProductsBycatalogsectionGetdataProcessor';
И последняя задачка — выборка из всего раздела, но только ТОПовых товаров. Но это совсем маленький процессор:-)
<?php /* * Делаем выборку топовых товаров */ require_once dirname(__FILE__).'/getdata.class.php'; class modWebCatalogProductsBycatalogsectionGetdatatopProcessor extends modWebCatalogProductsBycatalogsectionGetdataProcessor{ protected function prepareCountQuery(xPDOQuery &$query) { $query = parent::prepareCountQuery($query); $query->innerJoin('modTemplateVarResource', 'top', "top.contentid = {$this->classKey}.id and top.tmplvarid=4 and top.value != ''"); return $query; } } return 'modWebCatalogProductsBycatalogsectionGetdatatopProcessor';
Да, это совсем не мало кода. Но если все это писать на сниппетах, то получится еще больше, и на много. Но главное — если, скажем, мне надо будет выводить уже все товары, без учетов наличия в них картинок, я изменю только один (базовый) процессор, и логика поменяется во всех дочерних процессорах. Или если мне надо будет добавить какие-то новые данные в объект товара, то мне тоже достаточно будет сделать это в одном единственном процессоре, а не переписывать каждый в отдельности. Плюс эти процессоры можно вызвать вообще из любого положения, хоть в плагине, хоть в сниппете, хоть в Смарти-шаблоне. Можно вообще на лету получить класс процессора и расширить его, не создавая для этого отдельного файла.
Момент второй. Стандарты.
Вот это тоже очень важный момент. Сниппеты не имеют никаких стандартов. Там пишется произвольный PHP-код и все (хотя многие туда еще HTML пишет, а еще бывает javascript, SQL и т.п.)) ). Так вот, процессоры — это классы со своими стандартами и со своим набором методов. И shopModx-овые процессоры поддерживают эти стандарты. Так что если вы изучите и поймете базовые процессоры самого MODX-а (и особенно Object-процессоры), то вам будет многое понятно и в сторонних процессорах. Там есть уже прописанные методы проверки прав, подгрузки языковых топиков, обработчики ошибок и т.д. и т.п. Так что процессор на 10 строк в себе может иметь очень и очень большой функционал. А главное — стандартизированный, который к тому же и обратную совместимость обеспечивает.