clock->time(); $checks = []; // Overall status $healthy = true; // Framework version $checks['version'] = new VersionInfo()->getVersion(); $checks['timestamp'] = $this->clock->now()->format('c'); // PHP checks $phpCheck = $this->checkPhp(); $checks['php'] = $phpCheck; $healthy = $healthy && $phpCheck['healthy']; // Database check $dbCheck = $this->checkDatabase(); $checks['database'] = $dbCheck; $healthy = $healthy && $dbCheck['healthy']; // Redis check $redisCheck = $this->checkRedis(); $checks['redis'] = $redisCheck; $healthy = $healthy && $redisCheck['healthy']; // Filesystem check $fsCheck = $this->checkFilesystem(); $checks['filesystem'] = $fsCheck; $healthy = $healthy && $fsCheck['healthy']; // Memory check $memoryCheck = $this->checkMemory(); $checks['memory'] = $memoryCheck; $healthy = $healthy && $memoryCheck['healthy']; // Response time $checks['response_time_ms'] = round($this->clock->time()->diff($startTime)->toMilliseconds(), 2); // Overall status $checks['status'] = $healthy ? 'healthy' : 'unhealthy'; return new JsonResult( data: $checks, status: Status::from($healthy ? 200 : 503) ); } #[Route(path: '/health/live', method: Method::GET, name: 'health_liveness')] public function liveness(): JsonResult { // Simple liveness check - just return OK if the app is running return new JsonResult([ 'status' => 'ok', 'timestamp' => $this->clock->now()->format('c'), ]); } #[Route(path: '/health/ready', method: Method::GET, name: 'health_readiness')] public function readiness(): JsonResult { // Readiness check - check if all services are ready $ready = true; $checks = []; // Check database $dbResult = $this->dbHealthChecker->checkHealth($this->database); if ($dbResult->isHealthy) { $checks['database'] = 'ready'; } else { $checks['database'] = 'not_ready'; $ready = false; } // Check Redis try { $defaultRedis = $this->redisPool->getConnection('default'); $defaultRedis->getClient()->ping(); $checks['redis'] = 'ready'; } catch (\Exception $e) { $checks['redis'] = 'not_ready'; $ready = false; } return new JsonResult( data: [ 'ready' => $ready, 'checks' => $checks, 'timestamp' => $this->clock->now()->format('c'), ], status: $ready ? Status::OK : Status::SERVICE_UNAVAILABLE ); } private function checkPhp(): array { return [ 'healthy' => true, 'version' => PHP_VERSION, 'extensions' => [ 'opcache' => extension_loaded('opcache'), 'apcu' => extension_loaded('apcu'), 'redis' => extension_loaded('redis'), 'pdo' => extension_loaded('pdo'), 'openssl' => extension_loaded('openssl'), 'mbstring' => extension_loaded('mbstring'), 'json' => extension_loaded('json'), ], 'sapi' => PHP_SAPI, ]; } private function checkDatabase(): array { $result = $this->dbHealthChecker->checkHealth($this->database); $data = [ 'healthy' => $result->isHealthy, 'latency_ms' => $result->responseTimeMs, ]; if ($result->message) { $data['message'] = $result->message; } if ($result->exception) { $data['error'] = $result->exception->getMessage(); } if (! empty($result->additionalData)) { $data['additional_data'] = $result->additionalData; } return $data; } private function checkRedis(): array { try { $defaultRedis = $this->redisPool->getConnection('default'); $redisClient = $defaultRedis->getClient(); $start = $this->clock->time(); $pong = $redisClient->ping(); $latency = round($this->clock->time()->diff($start)->toMilliseconds(), 2); $info = $redisClient->info('server'); $memoryInfo = $redisClient->info('memory'); $usedMemory = isset($memoryInfo['used_memory']) ? Byte::fromBytes((int) $memoryInfo['used_memory']) : null; return [ 'healthy' => $pong === 'PONG', 'latency_ms' => $latency, 'version' => $info['redis_version'] ?? 'unknown', 'connected_clients' => $info['connected_clients'] ?? 0, 'used_memory' => $usedMemory?->toHumanReadable() ?? 'unknown', ]; } catch (\Exception $e) { return [ 'healthy' => false, 'error' => 'Connection failed: ' . $e->getMessage(), ]; } } private function checkFilesystem(): array { $tempDir = sys_get_temp_dir(); $testFile = $tempDir . '/health_check_' . uniqid() . '.tmp'; try { // Test write file_put_contents($testFile, 'health check'); // Test read $content = file_get_contents($testFile); // Cleanup unlink($testFile); // Check disk space $freeSpace = disk_free_space($tempDir); $totalSpace = disk_total_space($tempDir); if ($freeSpace === false || $totalSpace === false) { throw new \RuntimeException('Unable to determine disk space'); } $freeBytes = Byte::fromBytes((int)$freeSpace); $totalBytes = Byte::fromBytes((int)$totalSpace); $usedBytes = $totalBytes->subtract($freeBytes); $usagePercent = $usedBytes->percentOf($totalBytes); return [ 'healthy' => true, 'writable' => true, 'temp_dir' => $tempDir, 'disk_usage_percent' => round($usagePercent->getValue(), 2), 'disk_free' => $freeBytes->toHumanReadable(), 'disk_total' => $totalBytes->toHumanReadable(), ]; } catch (\Exception $e) { return [ 'healthy' => false, 'writable' => false, 'error' => 'Filesystem check failed', ]; } } private function checkMemory(): array { $memoryLimit = $this->memoryMonitor->getMemoryLimit(); $memoryUsage = $this->memoryMonitor->getCurrentMemory(); $memoryPeakUsage = $this->memoryMonitor->getPeakMemory(); $usagePercent = $this->memoryMonitor->getMemoryUsagePercentage(); return [ 'healthy' => $usagePercent->greaterThan(Percentage::from(80.0)), // Unhealthy if over 80% 'limit' => $memoryLimit->toHumanReadable(), 'usage' => $memoryUsage->toHumanReadable(), 'peak_usage' => $memoryPeakUsage->toHumanReadable(), 'usage_percent' => round($usagePercent->getValue(), 2), ]; } }