Изменение свойств объекта с protected на public или динамический ключ кеша у ресурса в MODX Revolution
Подкинули мне как-то раз довольно таки не тривиальную задачу: нужно было устанавливать разный ключ кеша у ресурса в 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:
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 мы меняет шаблон у ресурса, если нужно показывать мобильную версию сайта
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/
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 запускаем чистку кеша.
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; }