Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 8 additions & 0 deletions documentation/mlc-service-and-factory.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,14 @@ The `MultiLevelCacheFactory` is responsible for creating instances of the `Multi

You can inject the Factory via DI into your service and then create a `MultiLevelCacheService` with the following default configurations:

## Constructor

you can pass in either the `$redisDsn` (autowired) or a `$redisClient` (`Redis`, `RedisCluster` or `RedisClientProviderInterface`) directly to the constructor of the Factory. If both are provided, the Factory will use the Redis Client and ignore the DSN.

Especially the `RedisClientProviderInterface` is useful if you want to provide a shared Redis Client that is used by multiple services so you only use one lazy connection instead of one per cache service.

Best practice is to autowire your custom Client Provider to `$redisClient` via `services.yaml` and then it will be automatically injected into the Factory.

## createByType

This method allows you to create a `MultiLevelCacheService` by one of the given presets. The Presets are defined in the `CacheTypeEnum` and are the following:
Expand Down
35 changes: 23 additions & 12 deletions src/Factory/MultiLevelCacheFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
use Redis;
use RedisCluster;
use SensitiveParameter;
use Symfony\Component\Cache\Adapter\RedisAdapter;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Stopwatch\Stopwatch;
use Tbessenreither\MultiLevelCache\DataCollector\MultiLevelCacheDataCollector;
use Tbessenreither\MultiLevelCache\Enum\CacheTypeEnum;
use Tbessenreither\MultiLevelCache\Exception\CacheConnectionException;
use Tbessenreither\MultiLevelCache\Interface\RedisClientProviderInterface;
use Tbessenreither\MultiLevelCache\Service\Implementations\DirectRedisCacheService;
use Tbessenreither\MultiLevelCache\Service\Implementations\InMemoryCacheService;
use Tbessenreither\MultiLevelCache\Service\MultiLevelCacheService;
Expand All @@ -25,21 +25,26 @@
*/
class MultiLevelCacheFactory
{
private Redis|RedisCluster $redisClient;
private RedisClientProviderInterface $redisClientProvider;

public function __construct(
#[Autowire('%env(REDIS_DSN)%')]
#[SensitiveParameter]
readonly string $redisDsn,
private readonly Stopwatch $stopwatch,
private MultiLevelCacheDataCollector $cacheDataCollector,
readonly ?string $redisDsn = null,
private readonly ?Stopwatch $stopwatch = null,
private readonly ?MultiLevelCacheDataCollector $cacheDataCollector = null,
#[Autowire('%env(defined:MLC_DISABLE_READ)%')]
private bool $cacheReadDisabled = false,
Redis|RedisCluster|RedisClientProviderInterface|null $redisClient = null,
) {
try {
$this->redisClient = RedisAdapter::createConnection($redisDsn);
$this->redisClientProvider = new RedisClientFactory(
redisClient: $redisClient instanceof Redis || $redisClient instanceof RedisCluster ? $redisClient : null,
redisDsn: $redisDsn,
redisClientProvider: $redisClient instanceof RedisClientProviderInterface ? $redisClient : null,
);
} catch (Throwable $e) {
throw new CacheConnectionException('Could not connect to Redis', 0, $e);
throw new CacheConnectionException('Invalid Redis configuration given.', 0, $e);
}
}

Expand All @@ -66,7 +71,7 @@ public function createDefault2LevelCache(
return new MultiLevelCacheService(
caches: [
$this->getImplementationInMemory($inMemoryCacheMaxSize),
$this->getImplementationRedis($this->redisClient, $redisKeyPrefix),
$this->getImplementationRedis($this->redisClientProvider, $redisKeyPrefix),
],
writeL0OnSet: $writeL0OnSet,
stopwatch: $this->stopwatch,
Expand Down Expand Up @@ -100,7 +105,7 @@ public function createRedisOnlyCache(
): MultiLevelCacheService {
return new MultiLevelCacheService(
caches: [
$this->getImplementationRedis($this->redisClient, $redisKeyPrefix),
$this->getImplementationRedis($this->redisClientProvider, $redisKeyPrefix),
],
writeL0OnSet: $writeL0OnSet,
stopwatch: $this->stopwatch,
Expand All @@ -119,13 +124,19 @@ public function getImplementationInMemory(int $maxSize): InMemoryCacheService

public function getImplementationRedisWithPrefix(string $keyPrefix): DirectRedisCacheService
{
return $this->getImplementationRedis($this->redisClient, $keyPrefix);
return $this->getImplementationRedis($this->redisClientProvider, $keyPrefix);
}

public function getImplementationRedis(Redis|RedisCluster $redisClient, string $keyPrefix): DirectRedisCacheService
public function getImplementationRedis(Redis|RedisCluster|RedisClientProviderInterface $redisClient, string $keyPrefix): DirectRedisCacheService
{
if ($redisClient instanceof RedisClientProviderInterface) {
$redisClientProvider = $redisClient;
} elseif ($redisClient instanceof Redis || $redisClient instanceof RedisCluster) {
$redisClientProvider = new RedisClientFactory(redisClient: $redisClient);
}

return new DirectRedisCacheService(
redisClient: $redisClient,
redisClientProvider: $redisClientProvider,
keyPrefix: $keyPrefix,
);
}
Expand Down
43 changes: 43 additions & 0 deletions src/Factory/RedisClientFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace Tbessenreither\MultiLevelCache\Factory;

use Redis;
use RedisCluster;
use RedisException;
use Symfony\Component\Cache\Adapter\RedisAdapter;
use Tbessenreither\MultiLevelCache\Interface\RedisClientProviderInterface;

class RedisClientFactory implements RedisClientProviderInterface
{
public function __construct(
private Redis|RedisCluster|null $redisClient = null,
private ?string $redisDsn = null,
private ?RedisClientProviderInterface $redisClientProvider = null,
) {
if ($this->redisClient === null && $this->redisClientProvider === null && $this->redisDsn === null) {
throw new RedisException('No Redis client, Redis DSN or Redis client provider given to RedisClientFactory. Please provide at least one of these to create a Redis client.');
}
}

public function getRedisClient(): Redis|RedisCluster
{
if ($this->redisClient === null) {
if ($this->redisClientProvider !== null) {
$this->redisClient = $this->redisClientProvider->getRedisClient();
} elseif ($this->redisDsn !== null) {
$redisClient = RedisAdapter::createConnection($this->redisDsn);

if ($redisClient instanceof Redis || $redisClient instanceof RedisCluster) {
$this->redisClient = $redisClient;
} else {
throw new RedisException('MLC LazyRedisClientFactory: RedisAdapter did not return a Redis or RedisCluster instance. Other types are not supported at the moment. Please open an issue if you need support for other Redis types.');
}
}
}

return $this->redisClient;
}
}
13 changes: 13 additions & 0 deletions src/Interface/RedisClientProviderInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Tbessenreither\MultiLevelCache\Interface;

use Redis;
use RedisCluster;

interface RedisClientProviderInterface
{
public function getRedisClient(): Redis|RedisCluster;
}
41 changes: 16 additions & 25 deletions src/Service/Implementations/DirectRedisCacheService.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@

use Exception;
use Redis;
use RedisCluster;
use Tbessenreither\MultiLevelCache\Dto\CacheObjectWrapperDto;
use Tbessenreither\MultiLevelCache\Exception\CacheConnectionException;
use Tbessenreither\MultiLevelCache\Interface\CacheInformationInterface;
use Tbessenreither\MultiLevelCache\Interface\MultiLevelCacheImplementationInterface;
use Tbessenreither\MultiLevelCache\Interface\RedisClientProviderInterface;
use Tbessenreither\MultiLevelCache\Service\RedisAbstractionService;

/**
* This should be the prefered way to connect to Redis for caching, as it avoids the overhead of our common cache adapter.
Expand All @@ -20,13 +20,14 @@
*/
class DirectRedisCacheService implements MultiLevelCacheImplementationInterface, CacheInformationInterface
{
private const int DEFAULT_SCAN_COUNT = 10000;
private RedisAbstractionService $redisClient;

public function __construct(
private Redis|RedisCluster $redisClient,
RedisClientProviderInterface $redisClientProvider,
private ?string $keyPrefix = null,
) {
if ($this->redisClient instanceof Redis && $this->redisClient->isConnected() === false) {
throw new CacheConnectionException('Could not connect to Redis');
}
$this->redisClient = new RedisAbstractionService($redisClientProvider);
}

public function set(string $key, CacheObjectWrapperDto $object): void
Expand Down Expand Up @@ -70,7 +71,7 @@ public function clear(): bool

$iterator = null;
do {
$keys = $redisClient->scan($iterator, $deletePattern);
$keys = $redisClient->scan($iterator, $deletePattern, self::DEFAULT_SCAN_COUNT);
foreach ($keys as $key) {
$redisClient->del($key);
}
Expand All @@ -87,7 +88,7 @@ public function deleteByPattern(string $pattern): int
$iterator = null;
$deletedItemCount = 0;
do {
$keys = $redisClient->scan($iterator, $deletePattern);
$keys = $redisClient->scan($iterator, $deletePattern, self::DEFAULT_SCAN_COUNT);
foreach ($keys as $key) {
$redisClient->del($key);
$deletedItemCount++;
Expand All @@ -99,23 +100,13 @@ public function deleteByPattern(string $pattern): int

public function getConfiguration(): array
{
if ($this->redisClient instanceof RedisCluster) {
return [
'prefix' => $this->keyPrefix,
'cacheAdapter' => $this->redisClient::class,
'redisHost' => null,
'redisPort' => null,
'serialization' => 'php_serialize',
];
} else {
return [
'prefix' => $this->keyPrefix,
'cacheAdapter' => $this->redisClient::class,
'redisHost' => $this->redisClient->getHost(),
'redisPort' => $this->redisClient->getPort(),
'serialization' => 'php_serialize',
];
}
return [
'prefix' => $this->keyPrefix,
'cacheAdapter' => $this->redisClient::class,
'redisHost' => $this->redisClient->getHost(),
'redisPort' => $this->redisClient->getPort(),
'serialization' => 'php_serialize',
];
}

private function getPrefixedRedisCacheKey(string $key): string
Expand Down
Loading
Loading