Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FOUR-21263 implement a general method to index cache keys #7836

Merged
merged 11 commits into from
Dec 19, 2024
62 changes: 62 additions & 0 deletions ProcessMaker/Cache/CacheManagerBase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

namespace ProcessMaker\Cache;

use Exception;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;

abstract class CacheManagerBase
{
/**
* The cache connection.
*
* @var string
*/
protected string $connection;

/**
* The cache prefix.
*
* @var string
*/
protected string $prefix;

/**
* The available cache connections.
*
* @var array
*/
protected const AVAILABLE_CONNECTIONS = ['redis', 'cache_settings'];

public function __construct()
{
$this->connection = config('cache.default');
$this->prefix = config('cache.prefix');
}

/**
* Retrieve an array of cache keys that match a specific pattern.
*
* @param string $pattern The pattern to match.
*
* @return array An array of cache keys that match the pattern.
*/
public function getKeysByPattern(string $pattern): array
{
if (!in_array($this->connection, self::AVAILABLE_CONNECTIONS)) {
throw new Exception('`getKeysByPattern` method only supports Redis connections.');
caleeli marked this conversation as resolved.
Show resolved Hide resolved
}

try {
// Get all keys
$keys = Redis::connection($this->connection)->keys($this->prefix . '*');
// Filter keys by pattern
return array_filter($keys, fn ($key) => preg_match('/' . $pattern . '/', $key));
} catch (Exception $e) {
Log::info('CacheABC' . $e->getMessage());
}

return [];
}
}
44 changes: 24 additions & 20 deletions ProcessMaker/Cache/Settings/SettingCacheManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,47 @@
namespace ProcessMaker\Cache\Settings;

use Illuminate\Cache\CacheManager;
use Illuminate\Cache\Repository;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use ProcessMaker\Cache\CacheInterface;
use ProcessMaker\Cache\CacheManagerBase;

class SettingCacheManager implements CacheInterface
class SettingCacheManager extends CacheManagerBase implements CacheInterface
{
const DEFAULT_CACHE_DRIVER = 'cache_settings';

protected CacheManager $cacheManager;
protected Repository $cacheManager;

public function __construct(CacheManager $cacheManager)
{
$driver = $this->determineCacheDriver();
parent::__construct();

$this->cacheManager = $cacheManager;
$this->cacheManager->store($driver);
$this->setCacheDriver($cacheManager);
}

/**
* Determine the cache driver to use.
* Determine and set the cache driver to use.
*
* @return string
* @param CacheManager $cacheManager
*
* @return void
*/
private function determineCacheDriver(): string
private function setCacheDriver(CacheManager $cacheManager): void
{
$defaultCache = config('cache.default');
if (in_array($defaultCache, ['redis', 'cache_settings'])) {
return self::DEFAULT_CACHE_DRIVER;
$isAvailableConnection = in_array($defaultCache, self::AVAILABLE_CONNECTIONS);

if ($isAvailableConnection) {
$defaultCache = self::DEFAULT_CACHE_DRIVER;
}

return $defaultCache;
$this->cacheManager = $cacheManager->store($defaultCache);

if ($isAvailableConnection) {
$this->connection = $this->cacheManager->connection()->getName();
$this->prefix = $this->cacheManager->getPrefix();
}
}

/**
Expand Down Expand Up @@ -140,22 +150,16 @@ public function clear(): bool
*/
public function clearBy(string $pattern): void
{
$defaultDriver = $this->cacheManager->getDefaultDriver();

if ($defaultDriver !== 'cache_settings') {
if ($this->connection !== 'cache_settings') {
throw new SettingCacheException('The cache driver must be Redis.');
}

try {
// get the connection name from the cache manager
$connection = $this->cacheManager->connection()->getName();
// Get all keys
$keys = Redis::connection($connection)->keys($this->cacheManager->getPrefix() . '*');
// Filter keys by pattern
$matchedKeys = array_filter($keys, fn ($key) => preg_match('/' . $pattern . '/', $key));
$matchedKeys = $this->getKeysByPattern($pattern);

if (!empty($matchedKeys)) {
Redis::connection($connection)->del($matchedKeys);
Redis::connection($this->connection)->del($matchedKeys);
}
} catch (\Exception $e) {
Log::error('SettingCacheException' . $e->getMessage());
Expand Down
30 changes: 30 additions & 0 deletions ProcessMaker/Console/Commands/CacheSettingClear.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace ProcessMaker\Console\Commands;

use Illuminate\Console\Command;

class CacheSettingClear extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'cache:settings-clear';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Remove all of items from the settings cache';

/**
* Execute the console command.
*/
public function handle()
{
\SettingCache::clear();
}
}
107 changes: 107 additions & 0 deletions tests/Feature/Cache/CacheManagerBaseTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?php

namespace Tests\Feature\Cache;

use Exception;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use ProcessMaker\Cache\CacheManagerBase;
use Tests\TestCase;

class CacheManagerBaseTest extends TestCase
{
protected $cacheManagerBase;

protected function setUp(): void
{
parent::setUp();

config()->set('cache.default', 'redis');
}

protected function tearDown(): void
{
config()->set('cache.default', 'array');

parent::tearDown();
}

public function testGetKeysByPatternWithValidConnectionAndMatchingKeys()
{
$this->cacheManagerBase = $this->getMockForAbstractClass(CacheManagerBase::class);

$pattern = 'test-pattern';
$prefix = config('cache.prefix');
$keys = [$prefix . ':test-pattern:1', $prefix . ':test-pattern:2'];

Redis::shouldReceive('connection')
->with('redis')
->andReturnSelf();

Redis::shouldReceive('keys')
->with($prefix . '*')
->andReturn($keys);

$result = $this->cacheManagerBase->getKeysByPattern($pattern);

$this->assertCount(2, $result);
$this->assertEquals($keys, $result);
}

public function testGetKeysByPatternWithValidConnectionAndNoMatchingKeys()
{
$this->cacheManagerBase = $this->getMockForAbstractClass(CacheManagerBase::class);

$pattern = 'non-matching-pattern';
$prefix = config('cache.prefix');
$keys = [$prefix . ':test-pattern:1', $prefix . ':test-pattern:2'];

Redis::shouldReceive('connection')
->with('redis')
->andReturnSelf();

Redis::shouldReceive('keys')
->with($prefix . '*')
->andReturn($keys);

$result = $this->cacheManagerBase->getKeysByPattern($pattern);

$this->assertCount(0, $result);
}

public function testGetKeysByPatternWithInvalidConnection()
{
config()->set('cache.default', 'array');

$this->cacheManagerBase = $this->getMockForAbstractClass(CacheManagerBase::class);

$this->expectException(Exception::class);
caleeli marked this conversation as resolved.
Show resolved Hide resolved
$this->expectExceptionMessage('`getKeysByPattern` method only supports Redis connections.');

$this->cacheManagerBase->getKeysByPattern('pattern');
}

public function testGetKeysByPatternWithExceptionDuringKeyRetrieval()
{
$this->cacheManagerBase = $this->getMockForAbstractClass(CacheManagerBase::class);

$pattern = 'test-pattern';
$prefix = config('cache.prefix');

Redis::shouldReceive('connection')
->with('redis')
->andReturnSelf();

Redis::shouldReceive('keys')
->with($prefix . '*')
->andThrow(new Exception('Redis error'));

Log::shouldReceive('info')
->with('CacheABC' . 'Redis error')
caleeli marked this conversation as resolved.
Show resolved Hide resolved
->once();

$result = $this->cacheManagerBase->getKeysByPattern($pattern);

$this->assertCount(0, $result);
}
}
9 changes: 7 additions & 2 deletions tests/Feature/Cache/SettingCacheTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,13 @@ public function testClearByPatternWithFailedDeletion()
\SettingCache::set('test_pattern:1', 1);
\SettingCache::set('test_pattern:2', 2);

// Set up the expectation for the connection method
Redis::shouldReceive('connection')
->with('cache_settings')
->andReturnSelf();

Redis::shouldReceive('keys')
->with('*settings:*')
->with('settings:*')
->andReturn($keys);

Redis::shouldReceive('del')
Expand All @@ -210,7 +215,7 @@ public function testTryClearByPatternWithNonRedisDriver()
{
config()->set('cache.default', 'array');

$this->expectException(SettingCacheException::class);
$this->expectException(\Exception::class);
caleeli marked this conversation as resolved.
Show resolved Hide resolved
$this->expectExceptionMessage('The cache driver must be Redis.');

\SettingCache::clearBy('pattern');
Expand Down
Loading