Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
76
src/Framework/Redis/RedisConfig.php
Normal file
76
src/Framework/Redis/RedisConfig.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Redis;
|
||||
|
||||
/**
|
||||
* Configuration for Redis connections
|
||||
*/
|
||||
final readonly class RedisConfig
|
||||
{
|
||||
public function __construct(
|
||||
public string $host = 'redis',
|
||||
public int $port = 6379,
|
||||
public ?string $password = null,
|
||||
public int $database = 0,
|
||||
public float $timeout = 1.0,
|
||||
public float $readWriteTimeout = 1.0,
|
||||
public string $scheme = 'tcp',
|
||||
public array $options = []
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create configuration from environment variables or defaults
|
||||
*/
|
||||
public static function fromEnvironment(string $prefix = 'REDIS_'): self
|
||||
{
|
||||
return new self(
|
||||
host: $_ENV[$prefix . 'HOST'] ?? 'redis',
|
||||
port: (int) ($_ENV[$prefix . 'PORT'] ?? 6379),
|
||||
password: $_ENV[$prefix . 'PASSWORD'] ?? null,
|
||||
database: (int) ($_ENV[$prefix . 'DB'] ?? 0),
|
||||
timeout: (float) ($_ENV[$prefix . 'TIMEOUT'] ?? 1.0),
|
||||
readWriteTimeout: (float) ($_ENV[$prefix . 'READ_WRITE_TIMEOUT'] ?? 1.0)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new config with different database
|
||||
*/
|
||||
public function withDatabase(int $database): self
|
||||
{
|
||||
return new self(
|
||||
host: $this->host,
|
||||
port: $this->port,
|
||||
password: $this->password,
|
||||
database: $database,
|
||||
timeout: $this->timeout,
|
||||
readWriteTimeout: $this->readWriteTimeout,
|
||||
scheme: $this->scheme,
|
||||
options: $this->options
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert to Predis connection parameters
|
||||
*/
|
||||
public function toConnectionParameters(): array
|
||||
{
|
||||
$params = [
|
||||
'scheme' => $this->scheme,
|
||||
'host' => $this->host,
|
||||
'port' => $this->port,
|
||||
'database' => $this->database,
|
||||
'timeout' => $this->timeout,
|
||||
'read_write_timeout' => $this->readWriteTimeout,
|
||||
];
|
||||
|
||||
if ($this->password) {
|
||||
$params['password'] = $this->password;
|
||||
}
|
||||
|
||||
return array_merge($params, $this->options);
|
||||
}
|
||||
}
|
||||
134
src/Framework/Redis/RedisConnection.php
Normal file
134
src/Framework/Redis/RedisConnection.php
Normal file
@@ -0,0 +1,134 @@
|
||||
<?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 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 password is provided
|
||||
if ($this->config->password) {
|
||||
if (! $this->client->auth($this->config->password)) {
|
||||
throw new RedisConnectionException("Redis authentication failed");
|
||||
}
|
||||
}
|
||||
|
||||
// Select database
|
||||
if ($this->config->database > 0) {
|
||||
if (! $this->client->select($this->config->database)) {
|
||||
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(),
|
||||
previous: $e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the connection when the object is destroyed
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->connected && $this->client) {
|
||||
try {
|
||||
$this->client->close();
|
||||
} catch (RedisException) {
|
||||
// Ignore disconnection errors during destruction
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/Framework/Redis/RedisConnectionException.php
Normal file
14
src/Framework/Redis/RedisConnectionException.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Redis;
|
||||
|
||||
use App\Framework\Exception\FrameworkException;
|
||||
|
||||
/**
|
||||
* Exception thrown when Redis connection fails
|
||||
*/
|
||||
final class RedisConnectionException extends FrameworkException
|
||||
{
|
||||
}
|
||||
38
src/Framework/Redis/RedisConnectionInterface.php
Normal file
38
src/Framework/Redis/RedisConnectionInterface.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Redis;
|
||||
|
||||
use Redis;
|
||||
|
||||
/**
|
||||
* Interface for Redis connections with different purposes
|
||||
*/
|
||||
interface RedisConnectionInterface
|
||||
{
|
||||
/**
|
||||
* Get the underlying Redis client
|
||||
*/
|
||||
public function getClient(): Redis;
|
||||
|
||||
/**
|
||||
* Get the database number this connection uses
|
||||
*/
|
||||
public function getDatabase(): int;
|
||||
|
||||
/**
|
||||
* Get a descriptive name for this connection
|
||||
*/
|
||||
public function getName(): string;
|
||||
|
||||
/**
|
||||
* Check if the connection is alive
|
||||
*/
|
||||
public function isConnected(): bool;
|
||||
|
||||
/**
|
||||
* Reconnect if connection is lost
|
||||
*/
|
||||
public function reconnect(): void;
|
||||
}
|
||||
121
src/Framework/Redis/RedisConnectionPool.php
Normal file
121
src/Framework/Redis/RedisConnectionPool.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Redis;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Redis connection pool that manages multiple named connections
|
||||
* for different purposes (cache, queue, sessions, etc.)
|
||||
*/
|
||||
final class RedisConnectionPool
|
||||
{
|
||||
/** @var array<string, RedisConnectionInterface> */
|
||||
private array $connections = [];
|
||||
|
||||
/** @var array<string, RedisConfig> */
|
||||
private array $configs = [];
|
||||
|
||||
/**
|
||||
* Register a connection configuration
|
||||
*/
|
||||
public function registerConnection(string $name, RedisConfig $config): void
|
||||
{
|
||||
$this->configs[$name] = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a connection by name, creating it if it doesn't exist
|
||||
*/
|
||||
public function getConnection(string $name = 'default'): RedisConnectionInterface
|
||||
{
|
||||
if (! isset($this->connections[$name])) {
|
||||
if (! isset($this->configs[$name])) {
|
||||
throw new InvalidArgumentException("Redis connection '{$name}' is not configured");
|
||||
}
|
||||
|
||||
$this->connections[$name] = new RedisConnection($this->configs[$name], $name);
|
||||
}
|
||||
|
||||
return $this->connections[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection specifically for caching
|
||||
*/
|
||||
public function getCacheConnection(): RedisConnectionInterface
|
||||
{
|
||||
return $this->getConnection('cache');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection specifically for queues
|
||||
*/
|
||||
public function getQueueConnection(): RedisConnectionInterface
|
||||
{
|
||||
return $this->getConnection('queue');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection specifically for sessions
|
||||
*/
|
||||
public function getSessionConnection(): RedisConnectionInterface
|
||||
{
|
||||
return $this->getConnection('session');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a connection is registered
|
||||
*/
|
||||
public function hasConnection(string $name): bool
|
||||
{
|
||||
return isset($this->configs[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered connection names
|
||||
* @return string[]
|
||||
*/
|
||||
public function getConnectionNames(): array
|
||||
{
|
||||
return array_keys($this->configs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a connection (will be recreated on next access)
|
||||
*/
|
||||
public function removeConnection(string $name): void
|
||||
{
|
||||
unset($this->connections[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all connections
|
||||
*/
|
||||
public function closeAll(): void
|
||||
{
|
||||
$this->connections = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection health status for all connections
|
||||
* @return array<string, bool>
|
||||
*/
|
||||
public function getHealthStatus(): array
|
||||
{
|
||||
$status = [];
|
||||
|
||||
foreach ($this->configs as $name => $config) {
|
||||
try {
|
||||
$connection = $this->getConnection($name);
|
||||
$status[$name] = $connection->isConnected();
|
||||
} catch (RedisConnectionException) {
|
||||
$status[$name] = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
}
|
||||
61
src/Framework/Redis/RedisFactory.php
Normal file
61
src/Framework/Redis/RedisFactory.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Redis;
|
||||
|
||||
use App\Framework\Cache\Driver\RedisCache;
|
||||
use App\Framework\Http\Session\RedisSessionStorage;
|
||||
use App\Framework\Queue\RedisQueue;
|
||||
|
||||
/**
|
||||
* Factory for creating Redis-based services using the connection pool
|
||||
*/
|
||||
final readonly class RedisFactory
|
||||
{
|
||||
public function __construct(
|
||||
private RedisConnectionPool $pool
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create RedisCache using the cache connection
|
||||
*/
|
||||
public function createCache(string $prefix = 'cache:'): RedisCache
|
||||
{
|
||||
return new RedisCache(
|
||||
connection: $this->pool->getCacheConnection(),
|
||||
prefix: $prefix
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create RedisQueue using the queue connection
|
||||
*/
|
||||
public function createQueue(string $queueName = 'commands'): RedisQueue
|
||||
{
|
||||
return new RedisQueue(
|
||||
connection: $this->pool->getQueueConnection(),
|
||||
queueName: $queueName
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create RedisSessionStorage using the session connection
|
||||
*/
|
||||
public function createSessionStorage(int $ttl = 3600): RedisSessionStorage
|
||||
{
|
||||
return new RedisSessionStorage(
|
||||
connection: $this->pool->getSessionConnection(),
|
||||
ttl: $ttl
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection pool for advanced usage
|
||||
*/
|
||||
public function getPool(): RedisConnectionPool
|
||||
{
|
||||
return $this->pool;
|
||||
}
|
||||
}
|
||||
51
src/Framework/Redis/RedisPoolInitializer.php
Normal file
51
src/Framework/Redis/RedisPoolInitializer.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Redis;
|
||||
|
||||
use App\Framework\DI\Container;
|
||||
use App\Framework\DI\Initializer;
|
||||
|
||||
/**
|
||||
* Initializes Redis connection pool with configuration
|
||||
*/
|
||||
final class RedisPoolInitializer
|
||||
{
|
||||
public function __construct(
|
||||
private Container $container
|
||||
) {
|
||||
}
|
||||
|
||||
#[Initializer]
|
||||
public function initialize(): RedisConnectionPool
|
||||
{
|
||||
$pool = new RedisConnectionPool();
|
||||
|
||||
// Load base Redis configuration
|
||||
$baseConfig = RedisConfig::fromEnvironment();
|
||||
|
||||
// Register default connection
|
||||
$pool->registerConnection('default', $baseConfig);
|
||||
|
||||
// Register cache connection (separate database)
|
||||
$cacheConfig = $baseConfig->withDatabase(1);
|
||||
$pool->registerConnection('cache', $cacheConfig);
|
||||
|
||||
// Register queue connection (separate database)
|
||||
$queueConfig = $baseConfig->withDatabase(2);
|
||||
$pool->registerConnection('queue', $queueConfig);
|
||||
|
||||
// Register session connection (separate database)
|
||||
$sessionConfig = $baseConfig->withDatabase(3);
|
||||
$pool->registerConnection('session', $sessionConfig);
|
||||
|
||||
// Additional connections can be registered manually if needed
|
||||
// Remove dynamic configuration loading since we're removing Configuration.php
|
||||
|
||||
// Register pool as singleton in DI container
|
||||
$this->container->singleton(RedisConnectionPool::class, $pool);
|
||||
|
||||
return $pool;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user