Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.');
Comment thread
caleeli marked this conversation as resolved.
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);
Comment thread
caleeli marked this conversation as resolved.
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')
Comment thread
caleeli marked this conversation as resolved.
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);
Comment thread
caleeli marked this conversation as resolved.
Resolved
$this->expectExceptionMessage('The cache driver must be Redis.');

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