Зажигаешь. Даже не успеваю всё поставить и проверить, но я попробую =)
Николай, здравствуйте!
Как раз зарегистрировался на сайте чтобы узнать у Вас про форум для MODX, и тут такая интересная новость. Однозначно нужно вынести на главную.
Установлю и попробую запустить. Есть ли какие-либо обязательные расширения для того, чтобы все заработало? (GetResources и.т.п?)
Для начала смотрим видео, а потом милости просим под кат.
В общем, как я и обещал, я таки сел писать свою социалку… Но хотя я думал выпустить первый релиз уже через пару дней, первый релиз я все-таки публикую только сегодня, не смотря на то, что за дело я взялся в тот же самый день. А дело в том, что первые дни практически полностью ушли на обдумывание модели. Я все время был в поиске такой золотой середины, чтобы и просто все было (раз-два, все встало, и работает), и при этом не имело каких-то жестких ограничений. Этот момент я разъясню подробней: вот у нас есть отдельно MODX, и отдельно Livestreet. MODX мы все любим за то, что на нем можно сделать что угодно. Это отличная платформа для проектов любого уровня. А вот Livestreet хорош тем, что на нем можно за несколько минут развернуть собственный аналог Хабрахабра. У MODX минус — отсутствие готовых решений, которые можно было бы кучу накидать как в Livestreet, и все они дружно бы работали без лишних хаков (не будем о багах). А в LS минус — узкопрофильность. Много хороших готовых компонентов, дружных и не очень, но когда дело доходит до тюнинга, вот тут начинаются серьезные проблемы (попробуйте на LS магазин замутить).
Так вот, вот эту золотую середину я и хотел найти, чтобы и логика готовая была, и гибкость. И здесь я пришел к выводу, что нельзя делать один компонент. Здесь должно быть минимум два. Этот момент тоже разъясню: MODX отлично подходит для создания сайтов-визиток, корпоративных сайтов, новостных лент и магазинов. Почему? Потому что элементарная информационная сущность этих ресурсов — документ (страница). Документ имеет поля (заголовок, контент, дату создания и т.п.). Просто документ позволяет реализовать бОльшую часть структуры и логики. А там, где не хватает какого-то поля, на помощь приходят TV-шки. Но давайте взглянем на тот же LS: у нас есть блоги, топики и пользователи. Блоги и топики имеют много статистической информации (просмотры, голоса, рейтинги и т.п.), и пользователи (кто за него голосовал, за кого голосовал и т.п.). В целом все это можно было бы и на TV-шках и прочих MODX-плюшках сделать, но сами знаете, что это и большая нагрузка, и много элементов, и целостность обеспечить надо… В общем тут много всего, и MODX-а как-то не хватает (именно каких-то базовых сущностей). Если еще и на форумы посмотреть, то там тоже много общего с сущностями ливстрита есть: разделы — это блоги, топики — они и в Африке топики, и в свойствах пользователей есть общие моменты.
В общем, я думал-думал, и придумал: один пакет будет устанавливать именно недостающие MODX-у сущности: Блог, Топик, Юзер. Блог и Топик — это модифицированные объекты modResource (подробнее о CRC читаем здесь, если с буржуйским нормально). А Юзер — это модифицированный modUser (об этом даже почитать нечего, разве что только мои эксперименты Поправка. Оффмануала: rtfm.modx.com/display/revolution20/Extending+modUser В свое время неправильно перевел некоторые части статьи и думал, что там пример только с данными пользователей в отдельной таблице, а не в modx_users.).
Эти объекты и их дополнительные таблицы легли в основу базового пакета modSociety. Устанавливая этот пакет, вы получаете эти сущности в своей системе, но их наличия практически вообще не ощутите. Разве что появятся новые типы документов. ?
По задумке объекты этого пакета должны обеспечить основу для любых типов социалок, форумов и т.п.
А вот второй пакет (modBlog) — вот это уже модель готового сайта со своей уникальной логикой. Хотя пакет еще очень сырой, и это пока только заготовка, наверняка у многих, кто посмотрел видео, ассоциации возникли с Хабром или Ливстритом. Да, я хочу сделать свой вариант хабра-движка. И это будет готовое решение, которое будет устанавливаться в пару кликов, со всей структурой, готовым шаблоном и т.п. Но так же планируется, что будут появляться и другие пакеты, устанавливающие свои специализированные сайты. При этом для всех этих сайтов основа будет — modSociety. То есть если появляется какая-то сущность, которая может понадобиться большинству таких типовых движков, эта сущность переходит в modSociety. Если сущность специфическая для конкретного пакета, то там она и развивается, и устанавливается с конкретным пакетом. Таким образом будем держать такой баланс, чтобы и основа была богатая, и при этом ядро не сильно разрасталось.
А теперь перечислю некоторые фишки своих пакетов. Сразу скажу, что эти пакеты — сосредоточение всех моих знаний о MODX Revolution, и пока я их писал, в поисках оптимальных решений я такие фишки Ревы ковырял, что просто прозревал. Уверен, что освоив хотя бы малую часть этого, многие, кто до сих пор еще не решился перейти на Реву, всерьез об этом задумаются…
1. Пользовательские типы ресурсов (производные от modResource). Я об этом уже говорил выше, но хочу отдельно об этом рассказать. Пользовательские ресурсы не только позволяют просто добавить какие-то свойства базовым объектам, а позволяют в принципе создавать совершенно другие объекты, и полностью менять логику. К примеру в моем пакете modBlog переопределяется папка контроллеров документов SocietyBlog и SocietyTopic, и при создании или обновлении этих ресурсов через админку как обычных документов, запросы отправляются не на системный коннектор самого MODX-а, а на коннекторы моего пакета, и там я уже дополнительно творю, что хочу. (Конечно и без этого можно обойтись, но очень яркий пример).
2. Переопределяемые контроллеры базовых классов SocietyBlog и SocietyTopic. Это вообще ураган! Хотя, как я и говорил выше, SocietyBlog и SocietyTopic — это базовые объекты, которые будут фигурировать в разных специализированных сайтах, логика этих объектов может отличаться. К примеру в своем Хабра-движке у меня будет какое-то ограничение на количество символов в топике (к примеру), а в другом типовом сайте у меня этого ограничения не будет, или оно просто будет отличаться. Так вот, используя то, что нам дает MODX, мы можем переопределять контроллеры этих объектов. К примеру в одном контексте одна папка контроллеров будет, а в другом другая. И на уровне контроллеров и процессоров я уже могу рулить логику при сохранении объектов, обновлении и т.п. При этом логика может быть или общая и для бэкэнда и для фронтэнда, или отличаться. К примеру, я хочу, чтобы ограничения на количество символов учитывались и в паблике, и в админке. Я могу это сделать, и даже в админке через редактор документов пользователь не сможет сохранить документ с нарушением правил, и получит соответствующие сообщения об ошибках. При этом мы не плодим объекты или таблицы, у нас все единое, просто логика различается в разных пакетах.
3. Шаблонизация. Я много раз говорил, что MODX-шаблоны — это не шаблоны, а чанки-контроллеры. Реальной же шаблонизации нет в MODX. Я сразу заложил основу под шаблонизации в пакете modBlog, потому можно будет легко менять скины сайта.
4. Инсталяха с менюшкой. Вот эта часть наверно самая сильная здесь. Я наверно выжал возможности менеджера пакетов MODX-а на 120%. У меня не только пакет устанавливает все политики безопасности, группы пользователей, контексты, структуру, элементы и т.п., но еще и позволяет на этапе установки выбрать какие-то особые действия, к примеру создать новый контекст, или обновить существующий. То есть даже имея несколько разных сайтов на разных контекстах с разной логикой, можно накатить новый пакет с обновлением уже существующих отдельных контекстов. В общем слов нет, одни слюни. А 120% — это потому что сам по себе менеджер пакетов не предусматривает исполнение javascript-а на этапе установки пакетов, но я использовал одну хитрость, которая позволила это.
5. 100% MODX-технологии. Да, все это — 100% функционал Рево из коробки. Да, свои объекты я дописал, но они используют таблицы, контроллеры, процессоры и т.п. самой Ревы. То есть ядро я не зацепил ни на байт, и ничего со стороны не взял.
А теперь самое главное: Исходники обоих пакетов я выложил на гитхабе: github.com/Fi1osof/modSociety github.com/Fi1osof/modBlog
Я рассчитываю на то, что заинтересованные члены сообщества так же подключатся к доработке этих пакетов. Самое главное — основу — я уже заложил и ее и буду развивать. Но есть полно задач, которые можно разбить. К приму скины для него нужны и т.п. То есть все мы сможем создать отличный движок, который наверняка сможет стать ключом для MODX в мир блого-социальных движков.
Гарантирую каждому участнику огромный опыт на выходе.
Просто в этом процессоре мне количество и не нужно — пользователь один
Так а что же ты тогда используешь modObjectGetListProcessor? Используй modObjectGetProcessor он делает выборку только одного процессора.
Еще надо будет посмотреть, можно ли после Join'ов ограничивать количество и сортировать присоединенные записи…
Все эти вещи выполняются в list-процессорах пакета shopModx. Посмотри как это там выполняется. Лично я исползую их практически везде, и еще не было задачи, на которую мне не хватало бы getdata-процессора.
P.S. если у тебя суперузкопрофильная задача, тогда вообще используй просто modPropcessor, и основную логику пиши в методе process.
Я туда и смотрю) Просто в этом процессоре мне количество и не нужно — пользователь один, а уже присоединенных объектов много. Еще надо будет посмотреть, можно ли после Join'ов ограничивать количество и сортировать присоединенные записи…
Пожалуйста. Можешь скачать магазин и посмотреть как там сделано.
А, все, понял, спасибо)))
Илья, привет! 1. Ты используешь базовый процессор modObjectGetListProcessor. Переопределять в нем метод getData надо очень осторожно. Вот ты не очень удачно это сделал, так как ты из метода вынес очень полезные методы, такие как prepareQueryBeforeCount и prepareQueryAfterCount. Вот сравни:
public function getData() { $data = array(); $limit = intval($this->getProperty('limit')); $start = intval($this->getProperty('start')); /* query for chunks */ $c = $this->modx->newQuery($this->classKey); $c = $this->prepareQueryBeforeCount($c); $data['total'] = $this->modx->getCount($this->classKey,$c); $c = $this->prepareQueryAfterCount($c); $sortClassKey = $this->getSortClassKey(); $sortKey = $this->modx->getSelectColumns($sortClassKey, $this->getProperty('sortAlias',$sortClassKey),'',array($this->getProperty('sort'))); if (empty($sortKey)) $sortKey = $this->getProperty('sort'); $c->sortby($sortKey,$this->getProperty('dir')); if ($limit > 0) { $c->limit($limit,$start); } $data['results'] = $this->modx->getCollection($this->classKey,$c); return $data; }
Так вот как раз на уровне этих методов и происходит вклинивание в формирование запроса. То есть в расширяющем процессоре ты пишешь примерно следующее:
public function prepareQueryBeforeCount(xPDOQuery $c) { $c = parent::prepareQueryBeforeCount($c); // Добавляешь условия к запросу return $c; }
Это тебе позволит не лезть в общую логику.
2. По поводу самой логики: я сам чаще всего использую только два метода: а) Расширение классов (переопределяя методы типа prepareQueryBeforeCount) б) Передачей параметров. К примеру вот часть кода процессора:
class ....{ public function initialize(){ $this->setDefaultProperties(array( 'hot' => false, // Флаг, что делать только выборку новинок )); return parent::initialize(); } public function prepareQueryBeforeCount(xPDOQuery $c) { $c = parent::prepareQueryBeforeCount($c); // Если указано, что нужны только новинки if($this->getProperty('hot')){ // Добавляем условие только новинок } return $c; } }
И вот уже при вызове этого процессора ты просто передаешь параметр 'hot' => true и все. По умолчаниюу тебя стоит 'hot' => false.
На сайте есть страница пользователя. На ней выводится его профиль, созданные им объявления, его последние заявки и пр. Это все отдельные объекты. Кроме того есть страницы со списками этих объектов «Мои объявления», «Мои заявки». Хотелось бы на любой из страниц делать поменьше запросов. Соответственно, нужны JOIN.
Как я представляю реализацию таких выборок.
Для страницы пользователя есть процессор xpectaProfileGetProcessor, в который передается id пользователя и он выбирает все его данные:
<?php class xpectaProfileGetProcessor extends modObjectGetListProcessor{ public $classKey = 'modUser'; public function getData() { $c = $this->modx->newQuery($this->classKey); $c->where(array('id' => $this->getProperty('id'))); $c->select(array( "{$this->classKey}.*", )); $data = array(); if($c->prepare() && $c->stmt->execute()){ $data = $c->stmt->fetchAll(PDO::FETCH_ASSOC); } return $data; } } return 'xpectaProfileGetProcessor';
В него я хочу передавать список «модулей», которые нужно запустить. Каждый из них будет выглядеть как-то так:
$c->leftJoin('xpectaAd', 'xpectaAd'); $c->select(array( "xpectaAd.id as ad_id", 'xpectaAd.title as ad_title', //... )); return $c;
И отсюда вопрос, где эти методы лучше хранить? Создать отдельный класс с ними? Или в код класса того или иного объекта вставлять как метод — но тогда их из процессора просто так не вызовешь… А можно прямо в этом процессоре и хранить. Какой вариант предпочтительнее? Или, может это все совсем не так делается?
Печально новый год начинать с такого нехорошего топика, но ничего не поделаешь…
Конечно много уже кто бросал камни в MODX из-за проблем с кешированием, но сегодня сделаю это и я. Я конечно же очень люблю MODX, но некоторые вещи меня прямо-таки вымораживают! Сразу оговорюсь, что описываемые здесь проблемы касаются только тех случаев, когда предполагается большое количество документов в одном контексте (более 10 000).
Сегодня мы рассмотрим процесс генерации кеша контекстов и на что и как мы можем влиять.
Для начала немного теории: каждый раз, когда мы обновляем кеш сайта, MODX полностью перегенерирует и сохраняет настройки всех контекстов. То же самое он делает и с каждым контекстом в отдельности, когда, к примеру, сохраняется какой-либо документ контекста.
А в чем проблема? А проблема в том, что это как минимум накладывает очень серьезные ограничения на максимальное кол-во документов в контексте. Почти два года назад я уже писал о своих исследованиях по этому поводу еще на версии Revo 2.0.8, так вот — с тех пор практически ничего не поменялось…
Сразу определим основную проблему: при обновлении кеша контекста, MODX перебирает все документы этого контекста (читай: делает много-много запросов к базе данных и получает и обрабатывает очень большой объем информации) и формирует карты ресурсов и алиасов. При этом он хранит эти карты не в отдельном кеш-файле, а именно в кеше настроек контекста.
Есть проблема — сразу же можно предположить парочку вариантов ее решения: 1. Запретить MODX-у делать выборку всех документов контекста. (Это был бы идеальный вариант — частично закешировать только важные документы, участвующие в формировании менюшек Wayfinder-ом и т.п., а те документы, которые мы получаем динамически нашими собственными специфическими скриптами, пропустить). 2. Вообще отключить кеширование контекста. (Почему это оказывается очень плохой вариант, мы рассмотрим и поймем позже).
Для начала немного теории: каждый раз при генерации настроек контекста, MODX собирает не только его настройки как таковые, но и собирает все его документы и набивает в карты ресурсов, алиасов и т.п. Плюс к этому, если используются ЧПУ, он еще и проверяет их на уникальность.
Выполняется это все в одном методе modCacheManager::generateContext(). Давайте посмотрим на исходник:

<?php public function generateContext($key, array $options = array()) { $results = array(); if (!$this->getOption('transient_context', $options, false)) { /** @var modContext $obj */ $obj = $this ->modx ->getObject('modContext', $key, true); if (is_object($obj) && $obj instanceof modContext && $obj->get('key')) { $cacheKey = $obj->getCacheKey(); $contextKey = is_object($this ->modx ->context) ? $this ->modx ->context ->get('key') : $key; $contextConfig = array_merge($this ->modx->_systemConfig, $options); /* generate the ContextSettings */ $results['config'] = array(); if ($settings = $obj->getMany('ContextSettings')) { /** @var modContextSetting $setting */ foreach ($settings as $setting) { $k = $setting->get('key'); $v = $setting->get('value'); $matches = array(); if (preg_match_all('~\{(.*?)\}~', $v, $matches, PREG_SET_ORDER)) { foreach ($matches as $match) { if (array_key_exists("{$match[1]}", $contextConfig)) { $matchValue = $contextConfig["{$match[1]}"]; } else { $matchValue = ''; } $v = str_replace($match[0], $matchValue, $v); } } $results['config'][$k] = $v; $contextConfig[$k] = $v; } } $results['config'] = array_merge($results['config'], $options); /* generate the aliasMap and resourceMap */ $collResources = $obj->getResourceCacheMap(); $results['resourceMap'] = array(); $results['aliasMap'] = array(); if ($collResources) { /** @var Object $r */ while ($r = $collResources->fetch(PDO::FETCH_OBJ)) { $results['resourceMap'][(string)$r->parent][] = (string)$r->id; if ($this ->modx ->getOption('friendly_urls', $contextConfig, false)) { if (array_key_exists($r->uri, $results['aliasMap'])) { $this ->modx ->log(xPDO::LOG_LEVEL_ERROR, "Resource URI {$r->uri} already exists for resource id = {$results['aliasMap'][$r ->uri]}; skipping duplicate resource URI for resource id = {$r->id}"); continue; } $results['aliasMap'][$r ->uri] = $r->id; } } } /* generate the webLinkMap */ $collWebLinks = $obj->getWebLinkCacheMap(); $results['webLinkMap'] = array(); if ($collWebLinks) { while ($wl = $collWebLinks->fetch(PDO::FETCH_OBJ)) { $results['webLinkMap'][$wl ->id] = $wl->content; } } $this ->modx ->log(modX::LOG_LEVEL_ERROR, $key); /* generate the eventMap and pluginCache */ $results['eventMap'] = array(); $results['pluginCache'] = array(); $eventMap = $this ->modx ->getEventMap($obj->get('key')); if (is_array($eventMap) && !empty($eventMap)) { $results['eventMap'] = $eventMap; $pluginIds = array(); $plugins = array(); $this ->modx ->loadClass('modScript'); foreach ($eventMap as $pluginKeys) { foreach ($pluginKeys as $pluginKey) { if (isset($pluginIds[$pluginKey])) { continue; } $pluginIds[$pluginKey] = $pluginKey; } } if (!empty($pluginIds)) { $pluginQuery = $this ->modx ->newQuery('modPlugin', array( 'id:IN' => array_keys($pluginIds) ) , true); $pluginQuery->select($this ->modx ->getSelectColumns('modPlugin', 'modPlugin')); if ($pluginQuery->prepare() && $pluginQuery ->stmt ->execute()) { $plugins = $pluginQuery ->stmt ->fetchAll(PDO::FETCH_ASSOC); } } if (!empty($plugins)) { foreach ($plugins as $plugin) { $results['pluginCache'][(string)$plugin['id']] = $plugin; } } } /* cache the Context ACL policies */ $results['policies'] = $obj->findPolicy($contextKey); } } else { $results = $this->getOption("{$key}_results", $options, array()); $cacheKey = "{$key}/context"; $options['cache_context_settings'] = array_key_exists('cache_context_settings', $results) ? (boolean)$results : false; } if ($this->getOption('cache_context_settings', $options, true) && is_array($results) && !empty($results)) { $options[xPDO::OPT_CACHE_KEY] = $this->getOption('cache_context_settings_key', $options, 'context_settings'); $options[xPDO::OPT_CACHE_HANDLER] = $this->getOption('cache_context_settings_handler', $options, $this->getOption(xPDO::OPT_CACHE_HANDLER, $options)); $options[xPDO::OPT_CACHE_FORMAT] = (integer)$this->getOption('cache_context_settings_format', $options, $this->getOption(xPDO::OPT_CACHE_FORMAT, $options, xPDOCacheManager::CACHE_PHP)); $options[xPDO::OPT_CACHE_ATTEMPTS] = (integer)$this->getOption('cache_context_settings_attempts', $options, $this->getOption(xPDO::OPT_CACHE_ATTEMPTS, $options, 10)); $options[xPDO::OPT_CACHE_ATTEMPT_DELAY] = (integer)$this->getOption('cache_context_settings_attempt_delay', $options, $this->getOption(xPDO::OPT_CACHE_ATTEMPT_DELAY, $options, 1000)); $lifetime = (integer)$this->getOption('cache_context_settings_expires', $options, $this->getOption(xPDO::OPT_CACHE_EXPIRES, $options, 0)); if (!$this->set($cacheKey, $results, $lifetime, $options)) { $this ->modx ->log(modX::LOG_LEVEL_ERROR, 'Could not cache context settings for ' . $key . '.'); } } return $results; }
Первое, на что сразу следует обратить внимание — откуда происходит выборка настроек, к примеру вот здесь:
$this->getOption('transient_context', $options, false)
$this — это не объект контекста, а сам modCacheManager, то есть выборка настроек происходит не из настроек контекста, а из переменной $options, переданной в метод generateContext. Посмотрим, какой параметр передается сюда при генерации кеша контекстов:
$contextResults[$context] = ($this->generateContext($context) ? true : false);
Ответ — никакой. То есть все настройки при генерации кеша берутся из самой системы. На что это влияет? А влияет это на то, что когда мы работаем в бэкенде, то при обновлении кеша мы не можем указать какие-то индивидуальные параметры кеширования для отдельных контекстов. К примеру есть системная настройка cache_context_settings, которая указывает кешировать настройки контекста или нет. По сути должно быть так: какому-то контексту мы установили эту настройку в false, и для этого контекста настройки не должны были бы кешироваться. Ан нет. Здесь или общая системная настройка установлена в true, и все контексты кешируются, или false, и тогда ни один контекст не кешируется, независимо от их настроек. Но следует отметить, что параметр $options передается самим контекстом в методе modContext::prepare();
$context = $this->xpdo->cacheManager->generateContext($this->get('key'), $options);
То есть можно отключить кеширование контекстов в принципе, а для отдельных контекстов кеширование указать. Тогда при заходе на сайт, когда MODX выполнит $this->context->prepare(), тогда если контекст кешируемый, то кеш для этого контекста запишется. Но если при этом для контекста mgr кеширование будет установлено, то опять-таки по описанной выше причине, будут кешироваться все контексты в момент очистки кеша всего сайта.
Кстати, $options можно передать как второй параметр в метод $modx->initialize(). К примеру так:
$options = array( 'site_start' => 3, 'site_name' => 'New sitename' ); $modx->initialize('web', $options)

Но эта фишка вообще бесполезная, так как могла бы иметь смысл только для динамической подмены каких-либо кешируемых настроек, так как само собой выполнение было бы быстрее, чем на уровне плагина, но переданные таким образом настройки тоже кешируются, так что единственный уместный момент — это только ручная очистка кеша контекста и опять-таки ручная инициализация его. Но это полный изврат, к тому же вообще не оправданный. Хотя нет, один момент есть: через интерфейс в настройки нельзя сохранять массивы. А так можно было бы передавать массивы, чтобы они сохранялись в кеш настроек. Кстати, если глобально кеширование настроек контекстов отключено, а локально для конкретного контекста включено, то первичная инициализация контекста будет выполнена дважды, так как хотя для контекста кеширование указано, мы знаем, что оно не берется в расчет, и при первой инициализации настройки не будут сохранены. И вот при такой инициализации контекста с переданными в параметре настройками, эти настройки не будут сохранены в кеше контекста, и при последующих обращениях к страницам контекста этих настроек уже не будет, так что с этими параметрами следует сразу передавать и настройку cache_context_settings => 1.
Ладно, это было лирическое отступление, вернемся к нашей функции.генерации кеша контекста. Опять обратим внимание на эту строчку практически в самом начале функции: if (!$this->getOption('transient_context', $options, false)) { То есть если для контекста указана эта настройка в true, то весь блок, в котором происходит выборка документов и настроек, пропускается. НО: как было написано выше, нельзя указать этот параметр отдельно для выбранных контекстов так, чтобы в админке для них эта настройка имела смысл, а для других нет. То есть если и устанавливать, то для всего сайта. А что происходит, если установить эту настройку для всего сайта? Забавная неприятность — 404 для всей админки после обновления кеша :-) К слову, все контексты тоже окажутся неработающими, так как в их настройках не будет карты ресурсов, и даже если указать site_start, MODX все-равно не будет искать стартовый документ, не указанный в карте ресурсов. В итоге еще одна по сути не работающая фишка.
И все-таки, хоть на что-то мы можем воздействовать или нет? Можем. На ЧПУ. Единственное, что проверяется для каждого конкретного контекста в отдельности, это использование ЧПУ, и если не используется, то просто карта алиасов не будет набиваться. Все.
Вывод: cache_context_settings никогда не стоит устанавливать в false, так как это только увеличит нагрузку на систему, и никак вообще нам не поможет.
Еще одна забавная вещь: системная настройки cache_disabled. В официальной документации написано
If true, disables all MODx caching features.
И жирное предупреждение:
// This feature is experimental. MODx recommends not turning off caching site-wide, as it can significantly slow down your site.
А в чем фишка? А в том, что эта настройка вообще нигде не используется. Вообще. Только в конфиге прописано
if (!defined('MODX_CACHE_DISABLED')) { $modx_cache_disabled= false; define('MODX_CACHE_DISABLED', $modx_cache_disabled); }
То есть можете сколько угодно переключать ее в true, это вообще ни на что не влияет. Честно сказать, вообще грустно от такого бардака в системе кеширования. Получается хочешь ты этого, или нет, но если у тебя в контексте много документов, то проблем тебе не избежать…
Но отчаиваться не будем, а постараемся все-таки найти хоть какое-то решение. И для себя я такое решение нашел. В общем так как узкое место во всем этом деле — это выборка документов для генерации карты ресурсов, я решил это дело и прикрыть.
Выборка ресурсов для генерации карты ресурсов выполняется в методе modContext_mysql::getResourceCacheMapStmt(). Исходник:
public static function getResourceCacheMapStmt(&$context) { $stmt = false; if ($context instanceof modContext) { $tblResource= $context->xpdo->getTableName('modResource'); $tblContextResource= $context->xpdo->getTableName('modContextResource'); $resourceFields= array('id','parent','uri'); $resourceCols= $context->xpdo->getSelectColumns('modResource', 'r', '', $resourceFields); $bindings = array($context->get('key'), $context->get('key')); $sql = "SELECT {$resourceCols} FROM {$tblResource} `r` FORCE INDEX (`cache_refresh_idx`) LEFT JOIN {$tblContextResource} `cr` ON `cr`.`context_key` = ? AND `r`.`id` = `cr`.`resource` WHERE `r`.`id` != `r`.`parent` AND (`r`.`context_key` = ? OR `cr`.`context_key` IS NOT NULL) AND `r`.`deleted` = 0 GROUP BY `r`.`parent`, `r`.`menuindex`, `r`.`id`"; $criteria = new xPDOCriteria($context->xpdo, $sql, $bindings, false); if ($criteria && $criteria->stmt && $criteria->stmt->execute()) { $stmt =& $criteria->stmt; } } return $stmt; }

Как видно, выборка ресурсов происходит практически без разбору, и это следует исправить. Модифицированный код выглядит вот так:
public static function getResourceCacheMapStmt(&$context) { $stmt = false; if ($context instanceof modContext) { // Get context setting $settings = array(); if($result = $context->getMany('ContextSettings')){ foreach($result as $r){ $settings[$r->get('key')] = $r->get('value'); } } // If resource map disabled, skip it if($context->xpdo->getOption('cacheoptimizer.resource_map_disabled', $settings , false)){ return false; } $tblResource= $context->xpdo->getTableName('modResource'); $tblContextResource= $context->xpdo->getTableName('modContextResource'); $resourceFields= array('id','parent','uri'); $resourceCols= $context->xpdo->getSelectColumns('modResource', 'r', '', $resourceFields); $bindings = array($context->get('key'), $context->get('key')); $sql = "SELECT {$resourceCols} FROM {$tblResource} `r` FORCE INDEX (`cache_refresh_idx`) LEFT JOIN {$tblContextResource} `cr` ON `cr`.`context_key` = ? AND `r`.`id` = `cr`.`resource` WHERE `r`.`id` != `r`.`parent` AND (`r`.`context_key` = ? OR `cr`.`context_key` IS NOT NULL) AND `r`.`deleted` = 0 GROUP BY `r`.`parent`, `r`.`menuindex`, `r`.`id`"; $criteria = new xPDOCriteria($context->xpdo, $sql, $bindings, false); if ($criteria && $criteria->stmt && $criteria->stmt->execute()) { $stmt =& $criteria->stmt; } } return $stmt; }
То есть если глобальная настройка cacheoptimizer.resource_map_disabled или настройка конкретно для этого контекста установлена в true, то для контекста выборка документов не выполняется.
Вообще можно было бы еще более хитро поступить (к примеру добавить условие пропускать ресурсы с замороженным URI, и все ресурсы, которые не следует в карту подбирать, пропускать, а остальные брать, но это мне кажется уже лишнее). А так получается, что если у нас предполагается большое кол-во ресурсов на сайте, то мы создаем один контекст основной (рабочий), для которого все будет кешироваться, где будут все положенные проверки доступов и т.п., и создаем один (или несколько) контекстов, которые не будут кешироваться, и из которых мы будем делать выборку документов своими скриптами. Кстати, второй контекст (catalog) часто используется в shopKeeper. Надо поэкспериментировать, наверняка эта фишка там будет работать.
И да, если кто обратил внимание на то, что настройка имеет префикс cacheoptimizer: да, это оформленно в готовый пакетик :-) Этакий патч. И доступен он в моем репозитории rest.modxstore.ru/extras/
А исходник лежит на гитхабе: github.com/Fi1osof/cacheOptimizer Советую его всем скачать и изучить. Он совсем не большой, но это отличный прототип для патчей, так как не просто что-то устанавливает, а делает резервное копирование исходных файлов, и в дальнейшем при деинсталяции восстанавливает их.