• Блог
  • Проблема с кэшированием

Многие разработчики сталкивались с проблемой кэширования при использовании шаблонизатора Fenom. И я в том числе. Эта проблема не раз поднималась в сообществе — раз, два, три и ещё много комментариев в других постах. Об этом писал даже Василий (автор pdoTools). Вот анализ проблемы Николая Ланца. Но решение так и не было найдено, хотя попытки были. Многие просто отключали кэш ресурсов или отказывались от кэширования сниппетов. Кто-то правил исходники проблемных пакетов. А так как эта хрень с кэшем меня достала (постоянно отваливается редактор markItUp для комментариев), то я решил таки потратить пару часов и вникнуть, почему это происходит и как это решить.

Налил себе большой бокал чая и начал погружаться. В итоге ситуация оказалась ровно такой, как и написал Николай. Но и он не стал до конца вникать в проблему (оно и понятно, он использует Smarty). И поэтому решение, которое он предложил, не совсем корректное. Он оптимизировал задачу, которую описал во втором абзаце своего комментария. Я полностью согласен с его выводом. Но проблему с кэшированием, ссылки на которую я давал в самом начале, это не решает. Сразу скажу, что эта проблема напрямую никак не связана с Fenom. Просто вот эти строчки в парсере pdoParser нарушают логику кэширования MODX.

Лично у меня два раза выводился скрипт с настройками TicketConfig. И получалось, что второй скрипт перебивал первый, заново переопределял переменную TicketConfig и затирал настройки редактора markItUp.

Опущу рассказ о заваривании второй чашки и дальнейшем погружении в логику кэширования и перейду сразу к её описанию. В MODX контент ресурса кэшируется отдельно от скриптов и стилей. Т.е. кэшируется не готовая страница, а массив параметров, в котором отдельно указан контент, стили, скрипты, кэшируемые элементы и ещё куча других параметров. Так вот, логика следующая — если на странице вызывается кэшированный сниппет, в котором подключается скрипт или стили, то эти файлы сохраняются в массиве в отдельных ключиках — _sjscripts, _jscripts и _loadedjscripts. Т.е. в этом случае эти файлы сохраняются в ресурсе:

// Класс modResponse (строка 63)
$this->modx->resource->_jscripts= $this->modx->jscripts;
$this->modx->resource->_sjscripts= $this->modx->sjscripts;
$this->modx->resource->_loadedjscripts= $this->modx->loadedjscripts;

А если сниппет некэшированный, то в ресурс ничего сохраняться не будет. Это логично. Сниппет выполнится и сам подключит файлы. А вот если он вызывается без знака !, то выполнится он только первый раз, и парсер заменит его плейсхолдер на результат его работы, который попадёт в кэш ресурса. Но так как этот сниппет подключал файлы, то MODX их указывает в соответствующих ключах массива закэшированного ресурса. При обращении к ресурсу, MODX будет искать файл кэша, получит контент и динамически подключит указанные файлы. Наверно, было бы проще кэшировать страницу уже с подключёнными скриптами и стилями. Но разработчики MODX таким образом борются с дублями, чтобы один и тот же скрипт не подключался 2 раза. Им видней, но мне кажется эту задачу нужно переложить на разработчиков.

Для информации!

Свойство класса modX sjscripts используется для хранения скриптов, стилей и HTML кода, которые должны подключаться в секции head HTML страницы. Скрипты и стили, указанные в свойстве jscripts, вставляются перед закрывающим тегом body. А в loadedjscripts указываются все скрипты, стили и HTML блоки, подключенные на странице. Последнее свойство используется только для логирования уже подключённых скриптов — перед подключением нового скрипта MODX проверяет, нет ли такого в списке.

Таким образом, если коротко, то процесс подготовки контента выглядит так:

// Обработка запроса (класс modRequest)
1. Определяется запрашиваемый ресурс.
2. Если он загружается из кэша, то MODX запоминает сохранённые скрипты.

// Подготовка ответа (modResponse::outputContent())
1. Парсятся кэшируемые элементы, если ресурс не из кэша.
2. Сохраняются в ресурс все стили и скрипты, вызываемые кэшируемыми сниппетами и сохраненные в MODX для последующего сохранения в кэш ресурса (см. предыдущий блок кода).
3. Парсятся некэшируемые элементы. Нераспарсенные не удаляются. Скрипты в ресурс не пишутся.
4. Парсятся некэшируемые элементы. Нераспарсенные удаляются. Скрипты в ресурс не пишутся.
5. Выводится готовый контент.
6. Сохраняется кэш ресурса.

Как видите, в кэш ресурса сохраняются файлы только для кэшированных сниппетов. А если мы посмотрим в pdoParser, то увидим, что подключенные скрипты и стили сохраняются в кэше ресурса и для кэшированных и для некэшированных сниппетов и на 3-ем шаге и на 4-ом, когда, согласно логике MODX, их сохранять уже нельзя. Николай предлагает сохранить в кэш все скрипты/стили (и из кэша ресурса и подключённые) только один раз на событие OnBeforeSaveWebPageCache на 6-ом шаге. Но проблема остаётся — в кэш попадают скрипты, которые туда попадать не должны.

Вообще, Василий об этой проблеме писал в статье про pdoTools 2.7.0.

Например, вывод в консоли случайного числа:
{var $rand = rand()}
{$_modx->regClientScript('
<script type="text/javascript">
    console.log(' ~ $rand ~ ');
</script>', true)}
При первом вызове, когда кэша у документа еще нет, этот вызов попадёт к нему в свойство _jscripts, и будет вставлен на страницу при следующей загрузке.Но так как вызов скрипта из кэша не совпадает с тем, что будет в шаблоне во второй вызов (число-то случайное!), то при последующих загрузках мы получим 2 вызова этого скрипта с 2мя разными числами. Первое будет получаться из кэша, а второе — запускаться динамически из шаблона.Можно представить массу ситуаций, когда такой кэш будет мешать разрабатывать динамические сайты.Поэтому я добавил сохранение скриптов и стилей, подключаемых через Fenom, в отдельные временные массивы и удаление их из кэша ресурса. Таким образом всё, что подключается через {$_modx->regClientScript()} и другие подобные функции, в кэш больше не сохраняется.

Но это решение только для регистрации скриптов через феном и для обычной регистрации оно не работает. Как видите, задача нетривиальная и ещё есть над чем подумать.

Update 06.10.2017. Свой вариант решения этой проблемы отправил Василию (автору pdoTools), так как заинтересован, чтобы всё работало из коробки.

Update 22.10.2017. Мой PR принят. Теперь о проблеме с кэшированием сниппетов можно забыть.

0   5215

Комментарии ()

  1. Vladimir 04 октября 2017 # 0
    Тааак.., в закладки. Спасибо!
    1. Сергей Шлоков 06 октября 2017 # +1
      Проблема оказалось гораздо сложнее, поэтому пришлось потратить ещё пару часов на поиск решения. Но теперь точно работает во всех случаях (в тех, которые описывали). Тема для отдельной статьи.

    Вы должны авторизоваться, чтобы оставлять комментарии.

    Выделите опечатку и нажмите Ctrl + Enter, чтобы отправить сообщение об ошибке.