docs: consolidate documentation into organized structure

- Move 12 markdown files from root to docs/ subdirectories
- Organize documentation by category:
  • docs/troubleshooting/ (1 file)  - Technical troubleshooting guides
  • docs/deployment/      (4 files) - Deployment and security documentation
  • docs/guides/          (3 files) - Feature-specific guides
  • docs/planning/        (4 files) - Planning and improvement proposals

Root directory cleanup:
- Reduced from 16 to 4 markdown files in root
- Only essential project files remain:
  • CLAUDE.md (AI instructions)
  • README.md (Main project readme)
  • CLEANUP_PLAN.md (Current cleanup plan)
  • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements)

This improves:
 Documentation discoverability
 Logical organization by purpose
 Clean root directory
 Better maintainability
This commit is contained in:
2025-10-05 11:05:04 +02:00
parent 887847dde6
commit 5050c7d73a
36686 changed files with 196456 additions and 12398919 deletions

View File

@@ -40,31 +40,34 @@ final readonly class CacheHealthCheck implements HealthCheckInterface
if ($cachedValue->isHit && $cachedValue->value === $testValue) {
return HealthCheckResult::healthy(
'Cache system is working properly',
'Cache',
[
'operations' => ['set', 'get', 'delete'],
'response_time_ms' => round($responseTime * 1000, 2),
],
$responseTime
$responseTime * 1000
);
}
return HealthCheckResult::warning(
'Cache operations partially failed',
'Cache',
'Operations partially failed',
[
'hit' => $cachedValue->isHit,
'value_match' => $cachedValue->value === $testValue,
],
$responseTime
$responseTime * 1000
);
} catch (\Throwable $e) {
$responseTime = microtime(true) - $startTime;
return HealthCheckResult::unhealthy(
'Cache system failed: ' . $e->getMessage(),
responseTime: $responseTime,
exception: $e
'Cache',
'System failed: ' . $e->getMessage(),
[],
$responseTime * 1000,
$e
);
}
}

View File

@@ -6,6 +6,7 @@ namespace App\Framework\Health\Checks;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Database\DatabaseManager;
use App\Framework\Database\ValueObjects\SqlQuery;
use App\Framework\Health\HealthCheckCategory;
use App\Framework\Health\HealthCheckInterface;
use App\Framework\Health\HealthCheckResult;
@@ -25,7 +26,8 @@ final readonly class DatabaseHealthCheck implements HealthCheckInterface
$connection = $this->databaseManager->getConnection();
// Simple query to test connection
$result = $connection->query('SELECT 1 as health_check');
$query = SqlQuery::create('SELECT 1 as health_check');
$result = $connection->query($query);
$responseTime = Duration::fromSeconds(microtime(true) - $startTime);
// Get first row and extract the value
@@ -34,17 +36,18 @@ final readonly class DatabaseHealthCheck implements HealthCheckInterface
if ($value == 1 || $value === '1') {
return HealthCheckResult::healthy(
'Database connection is working',
'Database',
[
'query_time' => $responseTime->toHumanReadable(),
'query_time_ms' => $responseTime->toMilliseconds(),
],
$responseTime->toSeconds()
$responseTime->toMilliseconds()
);
}
return HealthCheckResult::unhealthy(
'Database query returned unexpected result',
'Database',
'Query returned unexpected result',
[
'expected' => 1,
'actual' => $value,
@@ -52,16 +55,18 @@ final readonly class DatabaseHealthCheck implements HealthCheckInterface
'query_time' => $responseTime->toHumanReadable(),
'query_time_ms' => $responseTime->toMilliseconds(),
],
responseTime: $responseTime->toSeconds()
$responseTime->toMilliseconds()
);
} catch (\Throwable $e) {
$responseTime = Duration::fromSeconds(microtime(true) - $startTime);
return HealthCheckResult::unhealthy(
'Database connection failed: ' . $e->getMessage(),
responseTime: $responseTime->toSeconds(),
exception: $e
'Database',
'Connection failed: ' . $e->getMessage(),
[],
$responseTime->toMilliseconds(),
$e
);
}
}

View File

@@ -29,6 +29,7 @@ final readonly class DiskSpaceHealthCheck implements HealthCheckInterface
if ($freeBytes === false || $totalBytes === false) {
return HealthCheckResult::unhealthy(
'Disk Space',
'Could not retrieve disk space information',
['path' => $this->path]
);
@@ -52,33 +53,36 @@ final readonly class DiskSpaceHealthCheck implements HealthCheckInterface
if ($usagePercentage->getValue() >= $this->criticalThreshold) {
return HealthCheckResult::unhealthy(
"Disk space critically low: {$usagePercentage->format()} used",
'Disk Space',
"Critically low: {$usagePercentage->format()} used",
$details,
$responseTime->toSeconds()
$responseTime->toMilliseconds()
);
}
if ($usagePercentage->getValue() >= $this->warningThreshold) {
return HealthCheckResult::warning(
"Disk space warning: {$usagePercentage->format()} used",
'Disk Space',
"Warning: {$usagePercentage->format()} used",
$details,
$responseTime->toSeconds()
$responseTime->toMilliseconds()
);
}
return HealthCheckResult::healthy(
"Disk space healthy: {$usagePercentage->format()} used",
'Disk Space',
$details,
$responseTime->toSeconds()
$responseTime->toMilliseconds()
);
} catch (\Throwable $e) {
$responseTime = Duration::fromSeconds(microtime(true) - $startTime);
return HealthCheckResult::unhealthy(
'Disk space check failed: ' . $e->getMessage(),
'Disk Space',
'Check failed: ' . $e->getMessage(),
['path' => $this->path],
$responseTime->toSeconds(),
$responseTime->toMilliseconds(),
$e
);
}

View File

@@ -52,17 +52,19 @@ final readonly class SystemHealthCheck implements HealthCheckInterface
if (! $memoryLimit->isEmpty()) {
if ($memoryUsagePercentage->getValue() >= $this->memoryCriticalThreshold) {
return HealthCheckResult::unhealthy(
'System',
"Critical memory usage: {$memoryUsagePercentage->format()}",
$details,
$responseTime->toSeconds()
$responseTime->toMilliseconds()
);
}
if ($memoryUsagePercentage->getValue() >= $this->memoryWarningThreshold) {
return HealthCheckResult::warning(
'System',
"High memory usage: {$memoryUsagePercentage->format()}",
$details,
$responseTime->toSeconds()
$responseTime->toMilliseconds()
);
}
}
@@ -71,25 +73,28 @@ final readonly class SystemHealthCheck implements HealthCheckInterface
$missingExtensions = array_diff(['json', 'mbstring'], $phpExtensions);
if (! empty($missingExtensions)) {
return HealthCheckResult::unhealthy(
'System',
'Critical PHP extensions missing: ' . implode(', ', $missingExtensions),
$details,
$responseTime->toSeconds()
$responseTime->toMilliseconds()
);
}
return HealthCheckResult::healthy(
'System is running normally',
'System',
$details,
$responseTime->toSeconds()
$responseTime->toMilliseconds()
);
} catch (\Throwable $e) {
$responseTime = Duration::fromSeconds(microtime(true) - $startTime);
return HealthCheckResult::unhealthy(
'System health check failed: ' . $e->getMessage(),
responseTime: $responseTime->toSeconds(),
exception: $e
'System',
'Health check failed: ' . $e->getMessage(),
[],
$responseTime->toMilliseconds(),
$e
);
}
}

View File

@@ -4,13 +4,17 @@ declare(strict_types=1);
namespace App\Framework\Health;
use App\Framework\Health\ValueObjects\HealthDetails;
use App\Framework\Health\ValueObjects\HealthMessage;
use App\Framework\Health\ValueObjects\ResponseTime;
final readonly class HealthCheckResult
{
public function __construct(
public HealthStatus $status,
public string $message,
public array $details = [],
public ?float $responseTime = null,
public HealthMessage $message,
public HealthDetails $details,
public ?ResponseTime $responseTime = null,
public ?\Throwable $exception = null
) {
}
@@ -34,9 +38,13 @@ final readonly class HealthCheckResult
{
return [
'status' => $this->status->value,
'message' => $this->message,
'details' => $this->details,
'response_time_ms' => $this->responseTime ? round($this->responseTime * 1000, 2) : null,
'message' => $this->message->toString(),
'message_formatted' => $this->message->toFormattedString(),
'message_type' => $this->message->type->value,
'details' => $this->details->toArray(),
'response_time_ms' => $this->responseTime?->duration->toMilliseconds(),
'response_time_formatted' => $this->responseTime?->toString(),
'response_time_level' => $this->responseTime?->getPerformanceLevel()->value,
'error' => $this->exception ? [
'message' => $this->exception->getMessage(),
'code' => $this->exception->getCode(),
@@ -45,18 +53,70 @@ final readonly class HealthCheckResult
];
}
public static function healthy(string $message, array $details = [], ?float $responseTime = null): self
public static function healthy(string $operation, array $details = [], ?float $responseTimeMs = null): self
{
return new self(HealthStatus::HEALTHY, $message, $details, $responseTime);
return new self(
HealthStatus::HEALTHY,
HealthMessage::success($operation),
HealthDetails::fromArray($details),
$responseTimeMs !== null ? ResponseTime::fromMilliseconds($responseTimeMs) : null
);
}
public static function warning(string $message, array $details = [], ?float $responseTime = null): self
public static function warning(string $operation, string $reason, array $details = [], ?float $responseTimeMs = null): self
{
return new self(HealthStatus::WARNING, $message, $details, $responseTime);
return new self(
HealthStatus::WARNING,
HealthMessage::warning($operation, $reason),
HealthDetails::fromArray($details),
$responseTimeMs !== null ? ResponseTime::fromMilliseconds($responseTimeMs) : null
);
}
public static function unhealthy(string $message, array $details = [], ?float $responseTime = null, ?\Throwable $exception = null): self
public static function unhealthy(string $operation, string $reason, array $details = [], ?float $responseTimeMs = null, ?\Throwable $exception = null): self
{
return new self(HealthStatus::UNHEALTHY, $message, $details, $responseTime, $exception);
$detailsObj = $exception !== null
? HealthDetails::fromArray($details)->merge(HealthDetails::forError($exception))
: HealthDetails::fromArray($details);
return new self(
HealthStatus::UNHEALTHY,
HealthMessage::failure($operation, $reason),
$detailsObj,
$responseTimeMs !== null ? ResponseTime::fromMilliseconds($responseTimeMs) : null,
$exception
);
}
// Additional factory methods for common cases
public static function timeout(string $operation, float $timeoutMs, array $details = []): self
{
return new self(
HealthStatus::UNHEALTHY,
HealthMessage::timeout($operation, $timeoutMs),
HealthDetails::fromArray($details),
ResponseTime::fromMilliseconds($timeoutMs)
);
}
public static function degraded(string $operation, string $reason, array $details = [], ?float $responseTimeMs = null): self
{
return new self(
HealthStatus::WARNING,
HealthMessage::degraded($operation, $reason),
HealthDetails::fromArray($details),
$responseTimeMs !== null ? ResponseTime::fromMilliseconds($responseTimeMs) : null
);
}
// Advanced factory method with Value Objects
public static function create(
HealthStatus $status,
HealthMessage $message,
HealthDetails $details,
?ResponseTime $responseTime = null,
?\Throwable $exception = null
): self {
return new self($status, $message, $details, $responseTime, $exception);
}
}

View File

@@ -0,0 +1,229 @@
<?php
declare(strict_types=1);
namespace App\Framework\Health\ValueObjects;
use InvalidArgumentException;
/**
* Value Object for health check details
* Provides type-safe storage and manipulation of health check metadata
*/
final readonly class HealthDetails
{
/**
* @param array<string, mixed> $details
*/
public function __construct(
private array $details = []
) {
$this->validateDetails($details);
}
public static function empty(): self
{
return new self([]);
}
/**
* @param array<string, mixed> $details
*/
public static function fromArray(array $details): self
{
return new self($details);
}
public function with(string $key, mixed $value): self
{
$details = $this->details;
$details[$key] = $value;
return new self($details);
}
public function without(string $key): self
{
$details = $this->details;
unset($details[$key]);
return new self($details);
}
public function get(string $key, mixed $default = null): mixed
{
return $this->details[$key] ?? $default;
}
public function has(string $key): bool
{
return array_key_exists($key, $this->details);
}
public function isEmpty(): bool
{
return empty($this->details);
}
public function count(): int
{
return count($this->details);
}
/**
* @return array<string, mixed>
*/
public function toArray(): array
{
return $this->details;
}
/**
* Common factory methods for typical health check details
*/
public static function forDatabase(
string $host,
int $port,
string $database,
?float $connectionTime = null,
?int $activeConnections = null
): self {
return new self([
'host' => $host,
'port' => $port,
'database' => $database,
'connection_time_ms' => $connectionTime,
'active_connections' => $activeConnections,
]);
}
public static function forCache(
string $driver,
?int $hitRate = null,
?int $totalKeys = null,
?string $memoryUsage = null
): self {
return new self([
'driver' => $driver,
'hit_rate_percent' => $hitRate,
'total_keys' => $totalKeys,
'memory_usage' => $memoryUsage,
]);
}
public static function forDisk(
string $path,
string $totalSpace,
string $freeSpace,
float $usagePercent
): self {
return new self([
'path' => $path,
'total_space' => $totalSpace,
'free_space' => $freeSpace,
'usage_percent' => $usagePercent,
]);
}
public static function forSystem(
float $cpuUsage,
string $memoryUsage,
float $loadAverage,
?string $uptime = null
): self {
return new self([
'cpu_usage_percent' => $cpuUsage,
'memory_usage' => $memoryUsage,
'load_average' => $loadAverage,
'uptime' => $uptime,
]);
}
public static function forApi(
string $endpoint,
int $statusCode,
?float $responseTime = null,
?string $version = null
): self {
return new self([
'endpoint' => $endpoint,
'status_code' => $statusCode,
'response_time_ms' => $responseTime,
'version' => $version,
]);
}
public static function forError(\Throwable $exception): self
{
return new self([
'error_type' => get_class($exception),
'error_message' => $exception->getMessage(),
'error_code' => $exception->getCode(),
'error_file' => $exception->getFile(),
'error_line' => $exception->getLine(),
]);
}
public function merge(self $other): self
{
return new self(array_merge($this->details, $other->details));
}
/**
* Get only the keys that contain sensitive information
* @return array<string>
*/
public function getSensitiveKeys(): array
{
$sensitivePatterns = ['password', 'secret', 'key', 'token', 'credential'];
return array_filter(
array_keys($this->details),
fn ($key) => str_contains(strtolower($key), 'password') ||
str_contains(strtolower($key), 'secret') ||
str_contains(strtolower($key), 'key') ||
str_contains(strtolower($key), 'token') ||
str_contains(strtolower($key), 'credential')
);
}
/**
* Get sanitized details for logging (removes sensitive data)
* @return array<string, mixed>
*/
public function toSanitizedArray(): array
{
$sanitized = $this->details;
foreach ($this->getSensitiveKeys() as $key) {
$sanitized[$key] = '[REDACTED]';
}
return $sanitized;
}
/**
* @param array<string, mixed> $details
*/
private function validateDetails(array $details): void
{
foreach ($details as $key => $value) {
if (! is_string($key)) {
throw new InvalidArgumentException('Detail keys must be strings');
}
if (empty(trim($key))) {
throw new InvalidArgumentException('Detail keys cannot be empty');
}
// Validate that values are serializable
if (is_resource($value)) {
throw new InvalidArgumentException("Detail value for key '{$key}' cannot be a resource");
}
if (is_object($value) && ! method_exists($value, '__toString') && ! $value instanceof \JsonSerializable) {
throw new InvalidArgumentException("Detail value for key '{$key}' must be serializable");
}
}
}
}

View File

@@ -0,0 +1,126 @@
<?php
declare(strict_types=1);
namespace App\Framework\Health\ValueObjects;
use InvalidArgumentException;
/**
* Value Object for health check messages
* Provides validation and formatting for health status messages with type safety
*/
final readonly class HealthMessage
{
public function __construct(
public string $message,
public HealthMessageType $type = HealthMessageType::PLAIN
) {
if (empty(trim($message))) {
throw new InvalidArgumentException('Health message cannot be empty');
}
if (strlen($message) > 500) {
throw new InvalidArgumentException('Health message cannot exceed 500 characters');
}
}
public static function success(string $operation): self
{
return new self("{$operation} is working correctly", HealthMessageType::SUCCESS);
}
public static function warning(string $operation, string $reason): self
{
return new self("{$operation} has issues: {$reason}", HealthMessageType::WARNING);
}
public static function failure(string $operation, string $reason): self
{
return new self("{$operation} failed: {$reason}", HealthMessageType::FAILURE);
}
public static function timeout(string $operation, float $timeoutMs): self
{
return new self("{$operation} timed out after {$timeoutMs}ms", HealthMessageType::TIMEOUT);
}
public static function degraded(string $operation, string $details): self
{
return new self("{$operation} is degraded: {$details}", HealthMessageType::DEGRADED);
}
public static function plain(string $message): self
{
return new self($message, HealthMessageType::PLAIN);
}
public function toString(): string
{
return $this->message;
}
public function __toString(): string
{
return $this->toString();
}
public function toFormattedString(): string
{
$emoji = $this->type->getEmoji();
return $emoji ? "{$emoji} {$this->message}" : $this->message;
}
public function getPlainMessage(): string
{
return $this->message;
}
public function withPrefix(string $prefix): self
{
return new self("{$prefix}: {$this->message}", $this->type);
}
public function withSuffix(string $suffix): self
{
return new self("{$this->message} - {$suffix}", $this->type);
}
public function withType(HealthMessageType $type): self
{
return new self($this->message, $type);
}
public function isError(): bool
{
return $this->type->isError();
}
public function isWarning(): bool
{
return $this->type->isWarningLevel();
}
public function isSuccess(): bool
{
return $this->type->isSuccess();
}
public function getSeverityLevel(): int
{
return $this->type->getSeverityLevel();
}
public function toArray(): array
{
return [
'message' => $this->message,
'type' => $this->type->value,
'formatted' => $this->toFormattedString(),
'severity' => $this->getSeverityLevel(),
'emoji' => $this->type->getEmoji(),
'color_class' => $this->type->getColorClass(),
];
}
}

View File

@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
namespace App\Framework\Health\ValueObjects;
/**
* Enum for health message types
* Provides type safety for different kinds of health messages
*/
enum HealthMessageType: string
{
case SUCCESS = 'success';
case WARNING = 'warning';
case FAILURE = 'failure';
case TIMEOUT = 'timeout';
case DEGRADED = 'degraded';
case PLAIN = 'plain';
public function getEmoji(): string
{
return match ($this) {
self::SUCCESS => '✓',
self::WARNING => '⚠',
self::FAILURE => '✗',
self::TIMEOUT => '⏱',
self::DEGRADED => '⚡',
self::PLAIN => ''
};
}
public function getColorClass(): string
{
return match ($this) {
self::SUCCESS => 'text-green-600',
self::WARNING => 'text-yellow-600',
self::FAILURE => 'text-red-600',
self::TIMEOUT => 'text-orange-600',
self::DEGRADED => 'text-blue-600',
self::PLAIN => 'text-gray-600'
};
}
public function getSeverityLevel(): int
{
return match ($this) {
self::SUCCESS => 0,
self::DEGRADED => 1,
self::WARNING => 2,
self::TIMEOUT => 3,
self::FAILURE => 4,
self::PLAIN => 0
};
}
public function isError(): bool
{
return in_array($this, [self::FAILURE, self::TIMEOUT]);
}
public function isWarningLevel(): bool
{
return in_array($this, [self::WARNING, self::DEGRADED]);
}
public function isSuccess(): bool
{
return $this === self::SUCCESS;
}
}

View File

@@ -0,0 +1,144 @@
<?php
declare(strict_types=1);
namespace App\Framework\Health\ValueObjects;
use App\Framework\Core\ValueObjects\Duration;
use InvalidArgumentException;
/**
* Value Object for response time measurements
* Uses Duration as foundation and adds health-check specific functionality
*/
final readonly class ResponseTime
{
public function __construct(
public Duration $duration
) {
// Duration already validates non-negative values
if ($duration->toSeconds() > 300) { // 5 minutes max for health checks
throw new InvalidArgumentException('Response time cannot exceed 5 minutes for health checks');
}
}
public static function fromDuration(Duration $duration): self
{
return new self($duration);
}
public static function fromSeconds(float $seconds): self
{
return new self(Duration::fromSeconds($seconds));
}
public static function fromMilliseconds(float $milliseconds): self
{
return new self(Duration::fromMilliseconds($milliseconds));
}
public static function fromStartTime(float $startTime): self
{
$endTime = microtime(true);
$duration = Duration::fromSeconds($endTime - $startTime);
return new self($duration);
}
public static function measure(callable $operation): self
{
$startTime = microtime(true);
$operation();
$endTime = microtime(true);
return new self(Duration::fromSeconds($endTime - $startTime));
}
public static function zero(): self
{
return new self(Duration::zero());
}
// Performance level assessment for health checks
public function isFast(): bool
{
return $this->duration->toMilliseconds() < 100; // < 100ms
}
public function isAcceptable(): bool
{
return $this->duration->toMilliseconds() < 500; // < 500ms
}
public function isSlow(): bool
{
$ms = $this->duration->toMilliseconds();
return $ms >= 500 && $ms < 2000; // 500ms-2s
}
public function isVerySlow(): bool
{
return $this->duration->toMilliseconds() >= 2000; // >= 2s
}
public function getPerformanceLevel(): ResponseTimeLevel
{
return match (true) {
$this->isFast() => ResponseTimeLevel::FAST,
$this->isAcceptable() => ResponseTimeLevel::ACCEPTABLE,
$this->isSlow() => ResponseTimeLevel::SLOW,
default => ResponseTimeLevel::VERY_SLOW
};
}
public function exceedsThreshold(Duration $threshold): bool
{
return $this->duration->greaterThan($threshold);
}
public function isWithinThreshold(Duration $threshold): bool
{
return ! $this->exceedsThreshold($threshold);
}
// Delegate common operations to Duration
public function toString(): string
{
return $this->duration->toHumanReadable();
}
public function __toString(): string
{
return $this->toString();
}
public function toArray(): array
{
return [
'milliseconds' => $this->duration->toMilliseconds(),
'seconds' => $this->duration->toSeconds(),
'formatted' => $this->toString(),
'level' => $this->getPerformanceLevel()->value,
'is_fast' => $this->isFast(),
'is_acceptable' => $this->isAcceptable(),
'is_slow' => $this->isSlow(),
'is_very_slow' => $this->isVerySlow(),
];
}
public function equals(self $other): bool
{
return $this->duration->equals($other->duration);
}
public function isSlowerThan(self $other): bool
{
return $this->duration->greaterThan($other->duration);
}
public function isFasterThan(self $other): bool
{
return $this->duration->lessThan($other->duration);
}
}

View File

@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace App\Framework\Health\ValueObjects;
/**
* Enum for response time performance levels
* Provides standardized performance assessment for health checks
*/
enum ResponseTimeLevel: string
{
case FAST = 'fast';
case ACCEPTABLE = 'acceptable';
case SLOW = 'slow';
case VERY_SLOW = 'very_slow';
public function getThresholdMs(): float
{
return match ($this) {
self::FAST => 100.0,
self::ACCEPTABLE => 500.0,
self::SLOW => 2000.0,
self::VERY_SLOW => PHP_FLOAT_MAX
};
}
public function getIcon(): string
{
return match ($this) {
self::FAST => '🚀',
self::ACCEPTABLE => '✅',
self::SLOW => '⚠️',
self::VERY_SLOW => '🐌'
};
}
public function getSeverityScore(): int
{
return match ($this) {
self::FAST => 0,
self::ACCEPTABLE => 1,
self::SLOW => 2,
self::VERY_SLOW => 3
};
}
public function getLabel(): string
{
return match ($this) {
self::FAST => 'Fast',
self::ACCEPTABLE => 'Acceptable',
self::SLOW => 'Slow',
self::VERY_SLOW => 'Very Slow'
};
}
public function isAcceptablePerformance(): bool
{
return in_array($this, [self::FAST, self::ACCEPTABLE]);
}
public function isPoorPerformance(): bool
{
return in_array($this, [self::SLOW, self::VERY_SLOW]);
}
public function requiresAttention(): bool
{
return $this === self::VERY_SLOW;
}
}