Files
michaelschiemer/src/Framework/Redis/RedisConnection.php
Michael Schiemer e68c25f004 refactor(redis): refine connection handling and pool singleton initialization
- Mark `RedisConnection::$connected` as read-only with `private(set)`.
- Simplify authentication and database selection logic in `RedisConnection`.
- Comment out DI container singleton registration in `RedisPoolInitializer`.
- Annotate `RedisConnectionPool` with `#[Singleton]` attribute for improved clarity.
2025-11-04 02:00:47 +01:00

266 lines
6.2 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\Redis;
use Redis;
use RedisException;
/**
* Managed Redis connection with health checking and reconnection using php-redis extension
*/
final class RedisConnection implements RedisConnectionInterface
{
private Redis $client;
private(set) bool $connected = false;
public function __construct(
private readonly RedisConfig $config,
private readonly string $name = 'default'
) {
$this->connect();
}
public function getClient(): Redis
{
if (! $this->isConnected()) {
$this->reconnect();
}
return $this->client;
}
public function getDatabase(): int
{
return $this->config->database;
}
public function getName(): string
{
return $this->name;
}
public function isConnected(): bool
{
if (! $this->connected) {
return false;
}
try {
return $this->client->ping() === '+PONG';
} catch (RedisException) {
$this->connected = false;
return false;
}
}
public function reconnect(): void
{
$this->connected = false;
$this->connect();
}
private function connect(): void
{
if (! extension_loaded('redis')) {
throw new RedisConnectionException(
"Redis extension is not loaded. Please install php-redis extension or use alternative cache drivers."
);
}
$this->client = new Redis();
try {
// Connect to Redis
$success = $this->client->connect(
$this->config->host,
$this->config->port,
$this->config->timeout,
null, // reserved
0, // retry_interval
$this->config->readWriteTimeout
);
if (! $success) {
throw new RedisConnectionException("Failed to connect to Redis server");
}
// Authenticate if a password is provided
if ($this->client->auth($this->config->password) === false) {
throw new RedisConnectionException("Redis authentication failed");
}
// Select database
if ($this->config->database > 0) {
if ($this->client->select($this->config->database) === false) {
throw new RedisConnectionException("Failed to select Redis database {$this->config->database}");
}
}
// Set additional options
foreach ($this->config->options as $option => $value) {
$this->client->setOption($option, $value);
}
$this->connected = true;
} catch (RedisException $e) {
$this->connected = false;
throw new RedisConnectionException(
"Failed to connect to Redis ({$this->name}): " . $e->getMessage() . " with Host: {$this->config->host} and Password: {$this->config->password}",
previous: $e
);
}
}
public function flush(): void
{
if (! $this->isConnected()) {
$this->reconnect();
}
$this->client->flushDB();
}
public function get(string $key): string|false
{
if (! $this->isConnected()) {
$this->reconnect();
}
return $this->client->get($key);
}
public function set(string $key, string $value, ?int $ttl = null): bool
{
if (! $this->isConnected()) {
$this->reconnect();
}
if ($ttl === null) {
return $this->client->set($key, $value);
}
return $this->client->setex($key, $ttl, $value);
}
public function delete(string ...$keys): int
{
if (empty($keys)) {
return 0;
}
if (! $this->isConnected()) {
$this->reconnect();
}
return $this->client->del($keys);
}
public function exists(string ...$keys): int
{
if (empty($keys)) {
return 0;
}
if (! $this->isConnected()) {
$this->reconnect();
}
return $this->client->exists(...$keys);
}
public function ttl(string $key): int
{
if (! $this->isConnected()) {
$this->reconnect();
}
return $this->client->ttl($key);
}
public function expire(string $key, int $seconds): bool
{
if (! $this->isConnected()) {
$this->reconnect();
}
return $this->client->expire($key, $seconds);
}
public function incr(string $key, int $by = 1): int
{
if (! $this->isConnected()) {
$this->reconnect();
}
return $by === 1
? $this->client->incr($key)
: $this->client->incrBy($key, $by);
}
public function decr(string $key, int $by = 1): int
{
if (! $this->isConnected()) {
$this->reconnect();
}
return $by === 1
? $this->client->decr($key)
: $this->client->decrBy($key, $by);
}
public function mGet(array $keys): array
{
if (empty($keys)) {
return [];
}
if (! $this->isConnected()) {
$this->reconnect();
}
return $this->client->mGet($keys);
}
public function mSet(array $keyValuePairs): bool
{
if (empty($keyValuePairs)) {
return true;
}
if (! $this->isConnected()) {
$this->reconnect();
}
return $this->client->mSet($keyValuePairs);
}
public function command(string $command, string|int ...$arguments): mixed
{
if (! $this->isConnected()) {
$this->reconnect();
}
return $this->client->rawCommand($command, ...$arguments);
}
/**
* Close the connection when the object is destroyed
*/
public function __destruct()
{
if ($this->connected && isset($this->client)) {
try {
$this->client->close();
} catch (RedisException) {
// Ignore disconnection errors during destruction
}
}
}
}