Кэширование в компоненте Tickets

Последнее время я занят в основном разработкой Tickets, ибо он построен на технологии Custom Resource Classes, а это неиссякаемый источник восторга от гениальности авторов. Ну и знания о системе текут просто рекой.

Серьезно, про настолько крутые возможности я и фантазировать не мог. Чем больше погружаюсь — тем больше восторг!

Итак, у меня есть 2 новых типа ресурсов: Ticket и TicketsSection, то есть — тикет и контейнер. Они оба расширяют обычный modResource, при этом изменяя его поведение. Это касается и админки (собственные контроллеры, которые загружают особые ExtJS страницы) и вообще процесса создания\изменения\вывода объектов.

Основное нарекание на MODX — это постоянная очистка кэша при любом пуке в менеджере. Понятно, что кэширование в Revolution очень крутое и невозможно иначе очистить все возможные связи обновленного ресурса. Но на сайте с парой тысяч ресурсов это становится настоящей головной болью, а Tickets предназначаются для очень большого количества ресурсов. Учитывая, что пользователи могут редактировать свои тикеты — сайт просто встанет колом при серьезных цифрах.

Проблему нужно решать и желательно, надежно. Нас волнует создание и изменение тикета, а категории — не волнует. Так как это очень редкое действие и производится только из админки. К тому же, там можно обойтись плагином.

Как известно, кэш ресурсов чистится при публикации нового документа, поэтому я придумал создавать тикет всегда в неопубликованном состоянии, а после создания — ставить ему флаг published, вручную обновляя карту документов сайта. Пришлось помучиться с сохранением\обновлением дат при редактировании, но оно того стоит.

Привожу куски из этого файла, измененные, для лучшего понимания процесса:
public function beforeSet() {
    //...
    // Убираем published и syncsite
    $this->setProperties(array(
		'class_key' => 'Ticket'
		,'show_in_tree' => 0
		,'published' => 0
		,'hidemenu' => 1
		,'syncsite' => 0
		,'isfolder' => 1
	));
	return true;
}
// Выставляем published
public function afterSave() {
	$this->object->fromArray(array(
		'alias' => $this->object->id
		,'published' => 1
		,'isfolder' => 1
                //,...
	));
	$this->object->save();
	return parent::afterSave();
}
// Генерируем карту документов контекста, иначе сразу после создания мы ресурс не увидим
public function cleanup() {
	$results = $this->modx->cacheManager->generateContext($this->object->context_key);
	$this->modx->context->resourceMap = $results['resourceMap'];
	$this->modx->context->aliasMap = $results['aliasMap'];
	return parent::cleanup();
}
При создании нового ресурса класса Ticket, хоть с фронтенда, хоть с админки кэш остальных ресурсов не трогается никак. Вы пользуетесь обычным процессором resource/create и можете даже не знать об этом.
Это прекрасно, но ведь и на странице категории новый ресурс не появится, если выводится кэшированным сниппетом, например getResources.

Поэтому, добавляем точечное удаление всего кэша родителя тикета (его секции), чтобы информация там обновлялась тоже.
public function cleanup() {
	$results = $this->modx->cacheManager->generateContext($this->object->context_key);
	$this->modx->context->resourceMap = $results['resourceMap'];
	$this->modx->context->aliasMap = $results['aliasMap'];

        // Удаление кэша страницы категории, к которой прнадлежит новый тикет
	$cache = $this->modx->cacheManager->getCacheProvider($this->modx->getOption('cache_resource_key', null, 'resource'));
	if ($section = $this->modx->getObject('TicketsSection', $this->object->parent)) {
                // Принудительно выставляем контекст ресурса, чтобы это работало и в админке
		$section->_contextKey = $section->context_key;
		$key = $section->getCacheKey();
		$cache->delete($key, array('deleteTop' => true));
		$cache->delete($key);
	}

	return parent::cleanup();
}
Та-дам! Новый тикет создан, при этом кэш очищен ровно для одного ресурса — его категории. По такому же принципу работает и обновление, только там еще очищается и кэш самого тикета.

Теперь, при обновлении ресурса класса Ticket, что через админку, что через фронтенд обновится кэш максимум двух ресурсов из наших потенциальных тысяч.

Мысль побежала дальше и я добавил новый метод сразу в классы Ticket и TicketsSection:
/**
 * Clearing cache of this resource
 * @param string $context Key of context for clearing
 * @return void
 */
public function clearCache($context = null) {
	if (empty($context)) {
		$context = $this->context_key;
	}
	$this->_contextKey = $context;

	/** @var xPDOFileCache $cache */
	$cache = $this->xpdo->cacheManager->getCacheProvider($this->xpdo->getOption('cache_resource_key', null, 'resource'));
	$key = $this->getCacheKey();
	$cache->delete($key, array('deleteTop' => true));
	$cache->delete($key);
}

По моему, это просто революция в кэшировании ресурсов! Непонятно, почему этого метода нет по-умолчанию? Отправил коммит, посмотрим, что будет.

Теперь все стало гораздо проще и симпатичнее:
public function cleanup() {
	$results = $this->modx->cacheManager->generateContext($this->object->context_key);
	$this->modx->context->resourceMap = $results['resourceMap'];
	$this->modx->context->aliasMap = $results['aliasMap'];

        // Чистим кэш
	$this->object->clearCache();
	/** @var TicketsSection $section */
	if ($section = $this->modx->getObject('TicketsSection', $this->object->parent)) {
		$section->clearCache();
	}

	return parent::cleanup();
}
Вы спросите, а зачем такие проблемы с публикацией, если можно просто переопределить метод clearCache() у modResourceUpdateProcessor, чтобы он работал как нам надо? Ответ прост: все равно будут проверяться права юзера на публикацию тикета, а нам это не нужно. Вместо переписывания всех этих проверок (или выдачи юзерам разрешения на публикацию), я просто делаю ресурс неопубликованным при сохранении, а потом ставлю нужный статус и время обновления.

Хотя, поместить очистку кэша тикета в clearCache() логичнее, чем в cleanup(), что я и сделал, в итоге.

Компонент все еще в разработке, но уже очевидно, что писать отныне нужно только так — своими типами ресурсов.

Следующая заметка
Выпустил Tickets


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

  1. Василий Краковецкий 20 ноября 2012, 13:32 # 0
    А как по времени разработки проекта на своих типах ресурсов? Уже после того как со всем этим разобрался(на это основное время обычно уходит, поэтому и опускаю)? Быстрее чем по принципу минишопа — через отдельную таблицу?
    1. Василий Наумкин 20 ноября 2012, 15:44 # 0
      В целом — так же, или быстрее, ибо многое наследуется от ресурсов, включая админку.

      Эта технология подойдет не везде, но таблицу ресурсов можно связывать со своими через getOne(), getMany() — поэтому ограничений не вижу.

      miniShop прям таки просится быть переделанным именно так.
      1. Василий Краковецкий 20 ноября 2012, 15:55 # 0
        Да, минишоп на отдельном типе ресурсов был бы просто сказкой — нажал создать контейнер каталога, и каталог создан, нажал создать ресурс корзины и корзина есть. И видео по настройке минишопа сократилось бы до минуты :)

        На самом деле могу помочь — я сейчас как раз магазин делаю Отцу, магазин понятное дело делается в свободное время на минишопе, так что смело экспериментирую, например уже почти полностью переделал систему хранения изображений, сделал аякс загрузку (взял из компонента Gallery), скоро сделаю коммит в репозиторий, надо только довести до ума совместимость с предыдущим видом таблицы. Так вот, если создадите ветку отдельную в репозитории по переделке на отдельный тип ресурсов, создадите, так сказать, платформу, то буду помогать)
        1. Василий Наумкин 20 ноября 2012, 16:00 # 0
          Это все прекрасно, только если выбор между бесплатным перфекционизмом и платной работой — приходится работать.

          Будет подходящий проект магазина — будет новая версия miniShop. Пока мне нового Tickets хватает выше крыши.
          А вы наворачивайте, наворачивайте — буду знать в чей репозиторий заглядывать потом =)
          1. Василий Краковецкий 20 ноября 2012, 16:05 # 0
            Ну ладно) Тогда пока буду продолжать допиливать то что есть) Кстати заодно делаю магазин «на заказ» и мелочи уже выкладываю, сейчас там висит pull request по поводу добавления типа ввода для тв — списком.
            1. Василий Наумкин 20 ноября 2012, 16:09 # 0
              Да я вижу, только надо ж загрузить и протестировать — а некогда =(
              1. Василий Краковецкий 20 ноября 2012, 16:14 # 0
                Изменения на выпуск новой версии не тянут, так что не к спеху. Чтоб проще смотреть было, выглядит вот так — pix.am/FqKz/
                1. Василий Наумкин 20 ноября 2012, 16:24 # 0
                  Зачотно!

                  Щас глазами проверю и добавлю.
                  1. Василий Краковецкий 20 ноября 2012, 16:27 # 0
                    Удивило очень что тв параметры списка не выводились, вот и исправил, на будущее учту насчет проверки, если что большое — на modx-test заливать буду что быстрее было. А в коммите пароль давать буду к сайту.
                    1. Василий Наумкин 20 ноября 2012, 16:28 # 0
                      Хороший вариант, спасибо.
                      1. Василий Краковецкий 20 ноября 2012, 16:30 # 0
                        Незачто) Над такими компонентами работать приятно, так что будут еще коммиты)
        2. Иван Брежнев 21 ноября 2012, 10:58 # 0
          miniShop прям таки просится быть переделанным именно так.

          Вот Василий ты пришел к такому выводу и это радует.
          Только вот почему когда только минишоп появился, и я у тебя спрашивал почему ты не сделал по подобию Артиклес (а у него как раз сделано CRC), ты просто ничего не ответил))
          1. Василий Наумкин 21 ноября 2012, 11:03 # 0
            miniShop начинался год назад, когда я знал гораздо меньше.

            Если думаешь, что это очень просто — делать на CRC — просто попробуй.
            1. Иван Брежнев 21 ноября 2012, 11:14 # 0
              Без обид, я не думаю что просто и не думаю что сложно. Потому что я еще не пробовал)

              И не упрекаю тебя что ты сделал плохо, ты сделал замечательно, я учился внося правки в твой код.
      2. Комментарий был удален.
        Добавление новых комментариев отключено.