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:
2025-08-11 20:13:26 +02:00
parent 59fd3dd3b1
commit 55a330b223
3683 changed files with 2956207 additions and 16948 deletions

View 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);
}
}

View 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
}
}
}
}

View 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
{
}

View 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;
}

View 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;
}
}

View 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;
}
}

View 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;
}
}