I’m using database cache since cakephp 2. From time to time I had trouble with caching using groups. My idea is that every table in the database has it’s own cache group, and therefore I can control when do I want to clear the cache for specific tables. I have a behavior looks like this:
protected function cacheKey(SelectQuery $query) {
$key = '';
$elements = ['select', 'where', 'group', 'order', 'limit', 'offset', 'having', 'join'];
foreach ($elements as $element) {
$elem = $query->clause($element);
if ($elem != null && !empty($elem)) {
$key .= serialize($elem);
}
}
return strtolower($this->table()->getAlias()) . '_' . md5($key);
}
public function beforeFind($event, SelectQuery $query, $options, $primary) {
if ($this->isEnabled) {
$key = $this->cacheKey($query);
// Set query cache parameter.
// If cache does not hit and this parameter exist, select results from DB store in cache.
$query->cache($key, 'find-in-cache');
$results = Cache::read($key, 'find-in-cache');
// Stop after events include find event
if (isset($results) && $results !== false) {
$query->setResult($results);
$event->stopPropagation();
// Disable _decorateResults
// Because cache is stored after decorate result sets, decorate is executed double.
$overwrite = true;
$query->mapReduce(null, null, $overwrite);
$query->formatResults(null, $overwrite);
// Disable caching for this query used again
$query->cache(false);
}
}
}
public function afterSave($event, $entity, $options) {
if ($this->isEnabled) {
$alias = strtolower($this->_table->getAlias());
Cache::clearGroup($alias, 'find-in-cache');
foreach ($this->_table->associations()->keys() as $assoc) {
Cache::clearGroup(strtolower($assoc), 'find-in-cache');
}
}
}
public function afterDelete($event, $entity, $options) {
if ($this->isEnabled) {
$alias = strtolower($this->_table->getAlias());
Cache::clearGroup($alias, 'find-in-cache');
foreach ($this->_table->associations()->keys() as $assoc) {
Cache::clearGroup(strtolower($assoc), 'find-in-cache');
}
}
}
With this behavior it should work, but every time a clearGroup being called all the cache groups are invalidated. I tried to debug what is the problem and found that in Cake\Cache\CaheEngine this
protected function _key($key): string
{
$this->ensureValidKey($key);
$prefix = '';
if ($this->_groupPrefix) {
$prefix = md5(implode('_', $this->groups()));
}
$key = preg_replace('/[\s]+/', '_', $key);
return $this->_config['prefix'] . $prefix . $key;
}
function causing the problem. Here you implode all the cacheGroups with there group cache counter and generate an md5 sum from it. So the cache key will look like this:
cacheConfigPreix_generatedMd5FromAllGroups_tableName_selectQueryUnique
But with this behavior every time any cache group counter is being incremented the md5 sum will change therefore all the cache groups are invalidated. My solution was this:
protected function _key(string $key): string
{
$this->ensureValidKey($key);
$prefix = '';
if ($this->_groupPrefix) {
$prefix = md5($this->groups()[array_search(substr($key, 0, strpos($key, '_')), $this->_config['groups'])]);
}
$key = preg_replace('/[\s]+/', '_', $key);
return $this->_config['prefix'] . $prefix . $key;
}
With this fix if groups are defined the keys will have there own group unique in them, not all the groups unique. Now keys look like this:
cacheConfigPreix_generatedMd5FromActualGroup_tableName_selectQueryUnique
With this behavior now cache is working, and after save will only invalidate the actual tables cache and the tables associated wih the actual cache.
I also define cache groups dynamically with this code:
<?php
namespace App\Cache\Engine;
use Cake\Cache\Engine\AnyEngine;
use Cake\ORM\TableRegistry;
class CustomEngine extends AnyEngine
{
public function init(array $config = []): bool
{
$groups = [];
$query = TableRegistry::getTableLocator()->get('Anytable')->getConnection()->execute("show tables")->fetchAll();
foreach ($query as $t) {
if (!in_array($t[0], $groups)) {
$groups[] = $t[0];
}
}
$config['groups'] = array_unique($groups);
return parent::init($config);
}
protected function _key(string $key): string
{
$this->ensureValidKey($key);
$prefix = '';
if ($this->_groupPrefix) {
$prefix = md5($this->groups()[array_search(substr($key, 0, strpos($key, '_')), $this->_config['groups'])]);
}
$key = preg_replace('/[\s]+/', '_', $key);
return $this->_config['prefix'] . $prefix . $key;
}
}
I’m sure that there is a more elegant solution for this, and I’m not sure if this is an actual problem or just something wrong in my logic, but I had this problem since CakePHP 3, and now finally I had the time to debug it, and I don’t understand what is the meaning of group caching with the implemented logic. I hope this could help others who have the same problem as me.