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

Мобильная версия сайта — это отдельный шаблон. Шаблон переключается в зависимости от юзерагента простым плагином, на событие OnLoadWebDocument.

Всё бы ничего, но встала проблема с кешированием(на время разработки сайта кеш я полностью отключаю). В кеш попадала версия ресурса, которая первая загрузится: обычная десктопная или мобильная.
После изучения исходников класса modResource стало всё понятно. Ключ кеша ресурса зависит только от контекста, и, конечно id самого ресурса. Ключ у кеша хранится в защищенном свойстве _cacheKey и выглядит так:

[contextKey]/resources/[id]

Я решил долго не возится. Написал вопрос в MODX-сообщество и непосредственно самим разработчикам MODX на GitHub. Но даже спустя неделю ответа ни там, ни там не последовало. Что ж, полез искать решение сам.

Так как свойство _cacheKey типа protected, то тут особо не разгуляешься: либо править ядро(чего делать категорически не хотелось), либо воспользоваться классом из семейства Reflection. Конечно, я выбрал второе.

Про классы Reflection* я читал, но никогда не использовал раньше. Reflection API предоставляет продвинутые методы для работы с ООП, вплоть до управления доступом.

Идея разного ключа кеша проста: делаем плагин на событие OnWebPageInit. В нем проверяем по юзерагенту тип устройства и перезаписываем свойство _cacheKey у $modx->resource.

Для смены на лету типа свойства объекта и установки нужного значения нам понадобится класс ReflectionProperty и пара методов: setAccessible() и setValue().

Код плагина на событие OnWebPageInit:

<?php
if(class_exists('ReflectionProperty') 
    && is_object($modx->resource) && $modx->resource instanceof modResource) {
    $cacheKey = new ReflectionProperty($modx->resource, '_cacheKey');
    $cacheKey->setAccessible(true);
    $cacheKey->setValue(
        $modx->resource,
        sprintf('[contextKey]/resources/%s/[id]',
            ($mobile_browser ? 'mobile' : 'desktop')
        )
    );
}

Переменнную $mobile_browser я определяю выше по коду. В ней хранится булево значение детекта мобильной версии.

После моих манипуляций папка /core/cache/resource/ пеперь выглядит так:


Обновлено 24.09.2014

Погорячился я с выводами, что такой способ работает. При таком способе кеш-то создается, но при загрузке ресурса MODX ищет его по ключу кеша, который зашит ещё в одном месте. В методе \modRequest::getResource:

$cacheKey = $this->modx->context->get('key') . "/resources/{$resourceId}";

Ну не говно ли?

Придумал ещё один способ. Но в нём намного больше телодвижений. Опишу в кратце принцип и приведу код.

1. Плагином на событие OnLoadWebDocument мы меняет шаблон у ресурса, если нужно показывать мобильную версию сайта

<?php
if ($modx->loadClass('Olegpro', MODX_CORE_PATH.'components/olegpro/model/olegpro/', false, true)) {
    if (Olegpro::mobileDetect() > 0) {
        if ($modx->resource->get('template') != 2) {
            $modx->resource->set('template', 2);
        }
    }
}

2. Плагином на событие OnWebPageInit мы подменяем настройку cache_resource_key. Тут-то и фокус. Именно так мы и меняем путь к разным версиям кеша. Таки образом папки с кешом будут по путям: core/cache/resource-desktop/ и core/cache/resource-mobile/

<?php
if ($modx->loadClass('Olegpro', MODX_CORE_PATH.'components/olegpro/model/olegpro/', false, true)) {
    $modx->config['cache_resource_key'] = (Olegpro::mobileDetect() > 0 ? 'resource-mobile' : 'resource-desktop');
}

3. Осталось только почистить кеш для кастомных путей кеша. Плагином на события OnDocFormSave и OnSiteRefresh запускаем чистку кеша.

<?php
switch($modx->event->name) {
 
    case 'OnDocFormSave':
        if ($modx->event->params['mode'] != 'upd') {return;}
 
        $contexts = array($resource->get('context_key'));
 
        $modx->cacheManager->refresh(array(
            'resource-mobile' => array('contexts' => $contexts),
            'resource-desktop' => array('contexts' => $contexts),
        ));
 
    break;
 
    case 'OnSiteRefresh':
        $contexts = array();
        $query = $modx->newQuery('modContext');
        $query->select($modx->escape('key'));
        if ($query->prepare() && $query->stmt->execute()) {
            $contexts = $query->stmt->fetchAll(PDO::FETCH_COLUMN);
        }
 
        $contexts = array_diff($contexts, array('mgr'));
 
        $modx->cacheManager->refresh(array(
            'resource-mobile' => array('contexts' => $contexts),
            'resource-desktop' => array('contexts' => $contexts),
        ));
 
    break;
}