feat(Production): Complete production deployment infrastructure

- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
This commit is contained in:
2025-10-25 19:18:37 +02:00
parent caa85db796
commit fc3d7e6357
83016 changed files with 378904 additions and 20919 deletions

View File

@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace App\Framework\Process\ValueObjects\Backup;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Filesystem\ValueObjects\FilePath;
/**
* Backup-Datei Information.
*/
final readonly class BackupFile
{
public function __construct(
public FilePath $path,
public Byte $size,
public \DateTimeImmutable $createdAt,
public string $name
) {
}
/**
* Gibt das Alter des Backups zurück.
*/
public function getAge(): Duration
{
$now = new \DateTimeImmutable();
$diff = $now->getTimestamp() - $this->createdAt->getTimestamp();
return Duration::fromSeconds($diff);
}
/**
* Prüft, ob das Backup älter als X Tage ist.
*/
public function isOlderThan(Duration $duration): bool
{
return $this->getAge()->greaterThan($duration);
}
/**
* Prüft, ob das Backup frisch ist (< 24 Stunden).
*/
public function isFresh(): bool
{
return ! $this->isOlderThan(Duration::fromDays(1));
}
/**
* Konvertiert zu Array.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'name' => $this->name,
'path' => $this->path->toString(),
'size_bytes' => $this->size->toBytes(),
'size_human' => $this->size->toHumanReadable(),
'created_at' => $this->createdAt->format('Y-m-d H:i:s'),
'age_human' => $this->getAge()->toHumanReadable(),
'is_fresh' => $this->isFresh(),
];
}
}

View File

@@ -0,0 +1,131 @@
<?php
declare(strict_types=1);
namespace App\Framework\Process\ValueObjects\Backup;
use App\Framework\Core\ValueObjects\Duration;
/**
* Ergebnis einer Backup-Verifikation.
*/
final readonly class BackupVerificationResult
{
/**
* @param BackupFile[] $backups
*/
public function __construct(
public array $backups,
public int $totalCount,
public ?\DateTimeImmutable $latestBackupDate,
public \DateTimeImmutable $verifiedAt
) {
}
/**
* Erstellt Ergebnis aus Backup-Dateien.
*
* @param BackupFile[] $backups
*/
public static function fromBackups(array $backups): self
{
$latestDate = null;
foreach ($backups as $backup) {
if ($latestDate === null || $backup->createdAt > $latestDate) {
$latestDate = $backup->createdAt;
}
}
return new self(
backups: $backups,
totalCount: count($backups),
latestBackupDate: $latestDate,
verifiedAt: new \DateTimeImmutable()
);
}
/**
* Prüft, ob es frische Backups gibt (< 24h).
*/
public function hasFreshBackup(): bool
{
return $this->latestBackupDate !== null
&& ! $this->isLatestBackupOlderThan(Duration::fromDays(1));
}
/**
* Prüft, ob das letzte Backup älter als X ist.
*/
public function isLatestBackupOlderThan(Duration $duration): bool
{
if ($this->latestBackupDate === null) {
return true;
}
$now = new \DateTimeImmutable();
$diff = $now->getTimestamp() - $this->latestBackupDate->getTimestamp();
return $diff > $duration->toSeconds();
}
/**
* Gibt die Anzahl frischer Backups zurück.
*/
public function getFreshBackupCount(): int
{
return count(array_filter(
$this->backups,
fn (BackupFile $backup) => $backup->isFresh()
));
}
/**
* Gibt die Anzahl alter Backups zurück (> 7 Tage).
*/
public function getOldBackupCount(): int
{
return count(array_filter(
$this->backups,
fn (BackupFile $backup) => $backup->isOlderThan(Duration::fromDays(7))
));
}
/**
* Gibt das neueste Backup zurück.
*/
public function getLatestBackup(): ?BackupFile
{
if (empty($this->backups)) {
return null;
}
$latest = null;
foreach ($this->backups as $backup) {
if ($latest === null || $backup->createdAt > $latest->createdAt) {
$latest = $backup;
}
}
return $latest;
}
/**
* Konvertiert zu Array.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'total_count' => $this->totalCount,
'fresh_count' => $this->getFreshBackupCount(),
'old_count' => $this->getOldBackupCount(),
'latest_backup_date' => $this->latestBackupDate?->format('Y-m-d H:i:s'),
'has_fresh_backup' => $this->hasFreshBackup(),
'verified_at' => $this->verifiedAt->format('Y-m-d H:i:s'),
'backups' => array_map(fn (BackupFile $b) => $b->toArray(), $this->backups),
];
}
}

View File

@@ -0,0 +1,101 @@
<?php
declare(strict_types=1);
namespace App\Framework\Process\ValueObjects;
/**
* Repräsentiert ein Kommando zur Prozess-Ausführung.
*
* Verwendung:
* - new Command('docker', 'ps', '-a') // Variadic constructor (bypass shell)
* - Command::fromString('cat /proc/uptime') // Shell-Kommando String (uses shell)
*/
final readonly class Command
{
/** @var array<int, string> */
public array $parts;
public function __construct(string ...$values)
{
if (empty($values)) {
throw new \InvalidArgumentException('Command cannot be empty');
}
// Validate all parts are non-empty strings
foreach ($values as $value) {
if (trim($value) === '') {
throw new \InvalidArgumentException('Command parts cannot contain empty strings');
}
}
$this->parts = array_values($values);
}
/**
* Konvertiert das Kommando zu einem String mit escaped arguments.
*/
public function toString(): string
{
return implode(' ', array_map('escapeshellarg', $this->parts));
}
/**
* Gibt die rohen Kommando-Teile zurück.
*
* Für proc_open:
* - String wenn fromString (bypass_shell=false)
* - Array wenn variadic/fromArray (bypass_shell=true)
*
* @return string|array<int, string>
*/
public function getValue(): string|array
{
// If only one part (fromString), return as string for shell execution
if (count($this->parts) === 1) {
return $this->parts[0];
}
// Multiple parts, return as array for bypass_shell
return $this->parts;
}
/**
* Prüft, ob das Kommando als Array erstellt wurde (mehr als 1 Element).
*
* Ein Element = fromString (Shell verwenden)
* Mehrere Elemente = fromArray oder variadic constructor (Shell umgehen)
*/
public function isArray(): bool
{
return count($this->parts) > 1;
}
/**
* Erstellt ein Kommando aus einem Shell-String.
* Nutze dies nur für einfache Shell-Kommandos.
*/
public static function fromString(string $command): self
{
if (trim($command) === '') {
throw new \InvalidArgumentException('Command string cannot be empty');
}
// Store as single part - will be executed as shell command
return new self($command);
}
/**
* Erstellt ein Kommando aus einem Array.
*
* @param array<int, string> $parts
*/
public static function fromArray(array $parts): self
{
if (empty($parts)) {
throw new \InvalidArgumentException('Command array cannot be empty');
}
return new self(...$parts);
}
}

View File

@@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
namespace App\Framework\Process\ValueObjects\Composer;
/**
* Composer Diagnostic Result.
*/
final readonly class ComposerDiagnosticResult
{
/**
* @param string[] $warnings
* @param string[] $errors
*/
public function __construct(
public bool $isHealthy,
public array $warnings = [],
public array $errors = [],
public ?string $composerVersion = null
) {
}
/**
* Erstellt ein gesundes Ergebnis.
*/
public static function healthy(string $composerVersion): self
{
return new self(
isHealthy: true,
composerVersion: $composerVersion
);
}
/**
* Erstellt ein Ergebnis mit Problemen.
*
* @param string[] $warnings
* @param string[] $errors
*/
public static function withIssues(array $warnings, array $errors, ?string $composerVersion = null): self
{
return new self(
isHealthy: empty($errors),
warnings: $warnings,
errors: $errors,
composerVersion: $composerVersion
);
}
/**
* Prüft, ob es Warnungen gibt.
*/
public function hasWarnings(): bool
{
return count($this->warnings) > 0;
}
/**
* Prüft, ob es Fehler gibt.
*/
public function hasErrors(): bool
{
return count($this->errors) > 0;
}
/**
* Konvertiert zu Array.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'is_healthy' => $this->isHealthy,
'composer_version' => $this->composerVersion,
'warnings' => $this->warnings,
'errors' => $this->errors,
'has_warnings' => $this->hasWarnings(),
'has_errors' => $this->hasErrors(),
];
}
}

View File

@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace App\Framework\Process\ValueObjects\Composer;
/**
* Composer Package Information.
*/
final readonly class ComposerPackage
{
public function __construct(
public string $name,
public string $currentVersion,
public ?string $latestVersion = null,
public ?string $description = null,
public bool $isOutdated = false
) {
}
/**
* Erstellt Package aus Composer-Output.
*/
public static function fromComposerOutput(string $name, string $currentVersion, ?string $latestVersion = null): self
{
return new self(
name: $name,
currentVersion: $currentVersion,
latestVersion: $latestVersion,
isOutdated: $latestVersion !== null && $latestVersion !== $currentVersion
);
}
/**
* Konvertiert zu Array.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'name' => $this->name,
'current_version' => $this->currentVersion,
'latest_version' => $this->latestVersion,
'description' => $this->description,
'is_outdated' => $this->isOutdated,
];
}
}

View File

@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace App\Framework\Process\ValueObjects\Docker;
/**
* Docker Container Information.
*/
final readonly class DockerContainer
{
public function __construct(
public string $id,
public string $name,
public string $image,
public string $status,
public bool $isRunning,
public ?string $created = null,
public array $ports = []
) {
}
/**
* Erstellt Container aus Docker-Output.
*/
public static function fromDockerPs(string $id, string $name, string $image, string $status): self
{
$isRunning = str_starts_with(strtolower($status), 'up');
return new self(
id: $id,
name: $name,
image: $image,
status: $status,
isRunning: $isRunning
);
}
/**
* Konvertiert zu Array.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'id' => $this->id,
'name' => $this->name,
'image' => $this->image,
'status' => $this->status,
'is_running' => $this->isRunning,
'created' => $this->created,
'ports' => $this->ports,
];
}
}

View File

@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace App\Framework\Process\ValueObjects\Docker;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Percentage;
/**
* Docker Container Statistics.
*/
final readonly class DockerContainerStats
{
public function __construct(
public string $containerId,
public string $containerName,
public Percentage $cpuPercent,
public Byte $memoryUsage,
public Byte $memoryLimit,
public Percentage $memoryPercent,
public Byte $networkIn,
public Byte $networkOut,
public Byte $blockIn,
public Byte $blockOut
) {
}
/**
* Erstellt Stats aus Docker-Output.
*/
public static function fromDockerStats(
string $containerId,
string $containerName,
float $cpuPercent,
int $memoryUsageBytes,
int $memoryLimitBytes,
int $networkInBytes,
int $networkOutBytes,
int $blockInBytes,
int $blockOutBytes
): self {
$memoryPercent = $memoryLimitBytes > 0
? ($memoryUsageBytes / $memoryLimitBytes) * 100
: 0.0;
return new self(
containerId: $containerId,
containerName: $containerName,
cpuPercent: Percentage::from($cpuPercent),
memoryUsage: Byte::fromBytes($memoryUsageBytes),
memoryLimit: Byte::fromBytes($memoryLimitBytes),
memoryPercent: Percentage::from($memoryPercent),
networkIn: Byte::fromBytes($networkInBytes),
networkOut: Byte::fromBytes($networkOutBytes),
blockIn: Byte::fromBytes($blockInBytes),
blockOut: Byte::fromBytes($blockOutBytes)
);
}
/**
* Konvertiert zu Array.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'container_id' => $this->containerId,
'container_name' => $this->containerName,
'cpu_percent' => $this->cpuPercent->format(2),
'memory_usage' => $this->memoryUsage->toHumanReadable(),
'memory_limit' => $this->memoryLimit->toHumanReadable(),
'memory_percent' => $this->memoryPercent->format(2),
'network_in' => $this->networkIn->toHumanReadable(),
'network_out' => $this->networkOut->toHumanReadable(),
'block_in' => $this->blockIn->toHumanReadable(),
'block_out' => $this->blockOut->toHumanReadable(),
];
}
}

View File

@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace App\Framework\Process\ValueObjects;
/**
* Repräsentiert Umgebungsvariablen für Prozess-Ausführung.
*/
final readonly class EnvironmentVariables
{
/**
* @param array<string, string> $variables
*/
public function __construct(
public array $variables = []
) {
}
/**
* Erstellt eine leere EnvironmentVariables Instanz.
*/
public static function empty(): self
{
return new self([]);
}
/**
* Erstellt EnvironmentVariables aus einem Array.
*
* @param array<string, string> $env
*/
public static function fromArray(array $env): self
{
return new self($env);
}
/**
* Fügt zusätzliche Variablen hinzu und gibt eine neue Instanz zurück.
*
* @param array<string, string> $additional
*/
public function merge(array $additional): self
{
return new self(array_merge($this->variables, $additional));
}
/**
* Fügt eine einzelne Variable hinzu.
*/
public function with(string $key, string $value): self
{
return new self(array_merge($this->variables, [$key => $value]));
}
/**
* Entfernt eine Variable.
*/
public function without(string $key): self
{
$variables = $this->variables;
unset($variables[$key]);
return new self($variables);
}
/**
* Prüft, ob eine Variable existiert.
*/
public function has(string $key): bool
{
return isset($this->variables[$key]);
}
/**
* Gibt eine Variable zurück.
*/
public function get(string $key, ?string $default = null): ?string
{
return $this->variables[$key] ?? $default;
}
/**
* Gibt alle Variablen als Array zurück.
*
* @return array<string, string>
*/
public function toArray(): array
{
return $this->variables;
}
/**
* Prüft, ob keine Variablen gesetzt sind.
*/
public function isEmpty(): bool
{
return empty($this->variables);
}
}

View File

@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace App\Framework\Process\ValueObjects\Health;
/**
* Einzelner Health Check.
*/
final readonly class HealthCheck
{
public function __construct(
public string $name,
public HealthStatus $status,
public string $message,
public float $value,
public float $threshold,
public string $unit = ''
) {
}
/**
* Erstellt einen erfolgreichen Health Check.
*/
public static function healthy(
string $name,
string $message,
float $value = 0.0,
float $threshold = 0.0,
string $unit = ''
): self {
return new self($name, HealthStatus::HEALTHY, $message, $value, $threshold, $unit);
}
/**
* Erstellt einen degraded Health Check.
*/
public static function degraded(
string $name,
string $message,
float $value = 0.0,
float $threshold = 0.0,
string $unit = ''
): self {
return new self($name, HealthStatus::DEGRADED, $message, $value, $threshold, $unit);
}
/**
* Erstellt einen unhealthy Health Check.
*/
public static function unhealthy(
string $name,
string $message,
float $value = 0.0,
float $threshold = 0.0,
string $unit = ''
): self {
return new self($name, HealthStatus::UNHEALTHY, $message, $value, $threshold, $unit);
}
/**
* Konvertiert zu Array.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'name' => $this->name,
'status' => $this->status->value,
'message' => $this->message,
'value' => $this->value,
'threshold' => $this->threshold,
'unit' => $this->unit,
];
}
}

View File

@@ -0,0 +1,128 @@
<?php
declare(strict_types=1);
namespace App\Framework\Process\ValueObjects\Health;
/**
* Aggregierter Health Report.
*/
final readonly class HealthReport
{
/**
* @param HealthCheck[] $checks
*/
public function __construct(
public HealthStatus $overallStatus,
public array $checks,
public \DateTimeImmutable $timestamp
) {
}
/**
* Erstellt einen Health Report aus Checks.
*
* @param HealthCheck[] $checks
*/
public static function fromChecks(array $checks): self
{
$overallStatus = self::determineOverallStatus($checks);
return new self(
overallStatus: $overallStatus,
checks: $checks,
timestamp: new \DateTimeImmutable()
);
}
/**
* Bestimmt den Overall Status basierend auf den Checks.
*
* @param HealthCheck[] $checks
*/
private static function determineOverallStatus(array $checks): HealthStatus
{
$hasUnhealthy = false;
$hasDegraded = false;
foreach ($checks as $check) {
if ($check->status === HealthStatus::UNHEALTHY) {
$hasUnhealthy = true;
} elseif ($check->status === HealthStatus::DEGRADED) {
$hasDegraded = true;
}
}
if ($hasUnhealthy) {
return HealthStatus::UNHEALTHY;
}
if ($hasDegraded) {
return HealthStatus::DEGRADED;
}
return HealthStatus::HEALTHY;
}
/**
* Gibt die Anzahl der Checks pro Status zurück.
*
* @return array{healthy: int, degraded: int, unhealthy: int}
*/
public function getStatusCounts(): array
{
$counts = [
'healthy' => 0,
'degraded' => 0,
'unhealthy' => 0,
];
foreach ($this->checks as $check) {
$counts[$check->status->value]++;
}
return $counts;
}
/**
* Gibt nur unhealthy Checks zurück.
*
* @return HealthCheck[]
*/
public function getUnhealthyChecks(): array
{
return array_filter(
$this->checks,
fn (HealthCheck $check) => $check->status === HealthStatus::UNHEALTHY
);
}
/**
* Gibt nur degraded Checks zurück.
*
* @return HealthCheck[]
*/
public function getDegradedChecks(): array
{
return array_filter(
$this->checks,
fn (HealthCheck $check) => $check->status === HealthStatus::DEGRADED
);
}
/**
* Konvertiert zu Array.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'overall_status' => $this->overallStatus->value,
'overall_description' => $this->overallStatus->getDescription(),
'timestamp' => $this->timestamp->format('Y-m-d H:i:s'),
'checks' => array_map(fn (HealthCheck $c) => $c->toArray(), $this->checks),
'status_counts' => $this->getStatusCounts(),
];
}
}

View File

@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace App\Framework\Process\ValueObjects\Health;
/**
* System Health Status.
*/
enum HealthStatus: string
{
case HEALTHY = 'healthy';
case DEGRADED = 'degraded';
case UNHEALTHY = 'unhealthy';
/**
* Gibt eine menschenlesbare Beschreibung zurück.
*/
public function getDescription(): string
{
return match ($this) {
self::HEALTHY => 'System is operating normally',
self::DEGRADED => 'System is experiencing issues but operational',
self::UNHEALTHY => 'System has critical issues',
};
}
/**
* Gibt ein Icon für den Status zurück.
*/
public function getIcon(): string
{
return match ($this) {
self::HEALTHY => '✅',
self::DEGRADED => '⚠️',
self::UNHEALTHY => '❌',
};
}
/**
* Prüft, ob der Status gesund ist.
*/
public function isHealthy(): bool
{
return $this === self::HEALTHY;
}
/**
* Prüft, ob der Status degraded ist.
*/
public function isDegraded(): bool
{
return $this === self::DEGRADED;
}
/**
* Prüft, ob der Status ungesund ist.
*/
public function isUnhealthy(): bool
{
return $this === self::UNHEALTHY;
}
}

View File

@@ -0,0 +1,121 @@
<?php
declare(strict_types=1);
namespace App\Framework\Process\ValueObjects\Http;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Http\Headers;
use App\Framework\Http\Status;
use App\Framework\Http\Url\Url;
/**
* URL Health Check Result.
*/
final readonly class UrlHealthCheck
{
public function __construct(
public Url $url,
public bool $isAccessible,
public Status $status,
public Duration $responseTime,
public ?string $error = null,
public ?Url $redirectUrl = null,
public ?Headers $headers = null
) {
}
/**
* Erstellt ein erfolgreiches Health-Check-Ergebnis.
*/
public static function success(
Url $url,
Status $status,
Duration $responseTime,
?Headers $headers = null
): self {
return new self(
url: $url,
isAccessible: true,
status: $status,
responseTime: $responseTime,
headers: $headers
);
}
/**
* Erstellt ein fehlgeschlagenes Health-Check-Ergebnis.
*/
public static function failed(Url $url, string $error): self
{
return new self(
url: $url,
isAccessible: false,
status: Status::INTERNAL_SERVER_ERROR,
responseTime: Duration::fromMilliseconds(0),
error: $error
);
}
/**
* Fügt Redirect-Informationen hinzu.
*/
public function withRedirect(Url $redirectUrl): self
{
return new self(
url: $this->url,
isAccessible: $this->isAccessible,
status: $this->status,
responseTime: $this->responseTime,
error: $this->error,
redirectUrl: $redirectUrl,
headers: $this->headers
);
}
/**
* Prüft, ob Status-Code erfolgreich ist (2xx).
*/
public function isSuccessful(): bool
{
return $this->status->isSuccess();
}
/**
* Prüft, ob ein Redirect vorliegt (3xx).
*/
public function isRedirect(): bool
{
return $this->status->isRedirection();
}
/**
* Prüft, ob Response-Zeit akzeptabel ist.
*/
public function isResponseTimeFast(Duration $threshold): bool
{
return $this->responseTime->toMilliseconds() < $threshold->toMilliseconds();
}
/**
* Konvertiert zu Array.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'url' => $this->url->toString(),
'is_accessible' => $this->isAccessible,
'is_successful' => $this->isSuccessful(),
'status_code' => $this->status->value,
'status_description' => $this->status->getDescription(),
'response_time_ms' => $this->responseTime->toMilliseconds(),
'response_time_human' => $this->responseTime->toHumanReadable(),
'error' => $this->error,
'redirect_url' => $this->redirectUrl?->toString(),
'is_redirect' => $this->isRedirect(),
'headers' => $this->headers?->all(),
];
}
}

View File

@@ -0,0 +1,126 @@
<?php
declare(strict_types=1);
namespace App\Framework\Process\ValueObjects\Logs;
/**
* Ergebnis einer Log-Analyse.
*/
final readonly class LogAnalysisResult
{
/**
* @param LogEntry[] $entries
* @param array<string, int> $levelCounts
*/
public function __construct(
public array $entries,
public int $totalLines,
public array $levelCounts,
public \DateTimeImmutable $analyzedAt
) {
}
/**
* Erstellt Ergebnis aus Log-Einträgen.
*
* @param LogEntry[] $entries
*/
public static function fromEntries(array $entries, int $totalLines): self
{
$levelCounts = [];
foreach ($entries as $entry) {
$level = $entry->level;
$levelCounts[$level] = ($levelCounts[$level] ?? 0) + 1;
}
return new self(
entries: $entries,
totalLines: $totalLines,
levelCounts: $levelCounts,
analyzedAt: new \DateTimeImmutable()
);
}
/**
* Gibt die Anzahl der Errors zurück.
*/
public function getErrorCount(): int
{
return array_sum(array_map(
fn ($level, $count) => in_array($level, ['ERROR', 'CRITICAL', 'ALERT', 'EMERGENCY'], true) ? $count : 0,
array_keys($this->levelCounts),
$this->levelCounts
));
}
/**
* Gibt die Anzahl der Warnings zurück.
*/
public function getWarningCount(): int
{
return $this->levelCounts['WARNING'] ?? 0;
}
/**
* Gibt nur Error-Einträge zurück.
*
* @return LogEntry[]
*/
public function getErrors(): array
{
return array_filter(
$this->entries,
fn (LogEntry $entry) => $entry->isError()
);
}
/**
* Gibt nur Warning-Einträge zurück.
*
* @return LogEntry[]
*/
public function getWarnings(): array
{
return array_filter(
$this->entries,
fn (LogEntry $entry) => $entry->isWarning()
);
}
/**
* Gibt die häufigsten Fehler-Messages zurück.
*
* @return array<string, int>
*/
public function getTopErrors(int $limit = 10): array
{
$messageCounts = [];
foreach ($this->getErrors() as $entry) {
$messageCounts[$entry->message] = ($messageCounts[$entry->message] ?? 0) + 1;
}
arsort($messageCounts);
return array_slice($messageCounts, 0, $limit, true);
}
/**
* Konvertiert zu Array.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'total_lines' => $this->totalLines,
'analyzed_entries' => count($this->entries),
'level_counts' => $this->levelCounts,
'error_count' => $this->getErrorCount(),
'warning_count' => $this->getWarningCount(),
'analyzed_at' => $this->analyzedAt->format('Y-m-d H:i:s'),
];
}
}

View File

@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace App\Framework\Process\ValueObjects\Logs;
/**
* Einzelner Log-Eintrag.
*/
final readonly class LogEntry
{
public function __construct(
public string $level,
public string $message,
public ?\DateTimeImmutable $timestamp = null,
public ?string $context = null
) {
}
/**
* Erstellt einen Log Entry aus einer Log-Zeile.
*/
public static function fromLine(string $line): self
{
// Versuche Standard-Format zu parsen: [timestamp] level: message
if (preg_match('/^\[([^\]]+)\]\s+(\w+):\s+(.+)$/', $line, $matches)) {
return new self(
level: strtoupper($matches[2]),
message: $matches[3],
timestamp: new \DateTimeImmutable($matches[1])
);
}
// Fallback: Ganzer Line als Message
return new self(
level: 'UNKNOWN',
message: $line
);
}
/**
* Prüft, ob es ein Error-Level ist.
*/
public function isError(): bool
{
return in_array($this->level, ['ERROR', 'CRITICAL', 'ALERT', 'EMERGENCY'], true);
}
/**
* Prüft, ob es ein Warning-Level ist.
*/
public function isWarning(): bool
{
return $this->level === 'WARNING';
}
/**
* Konvertiert zu Array.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'level' => $this->level,
'message' => $this->message,
'timestamp' => $this->timestamp?->format('Y-m-d H:i:s'),
'context' => $this->context,
];
}
}

View File

@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace App\Framework\Process\ValueObjects\Network;
use App\Framework\Http\IpAddress;
/**
* DNS Lookup Result.
*/
final readonly class DnsResult
{
/**
* @param IpAddress[] $addresses
*/
public function __construct(
public string $hostname,
public bool $resolved,
public array $addresses = []
) {
}
/**
* Erstellt ein erfolgreiches DNS-Ergebnis.
*
* @param IpAddress[] $addresses
*/
public static function success(string $hostname, array $addresses): self
{
return new self(
hostname: $hostname,
resolved: true,
addresses: $addresses
);
}
/**
* Erstellt ein fehlgeschlagenes DNS-Ergebnis.
*/
public static function failed(string $hostname): self
{
return new self(
hostname: $hostname,
resolved: false,
addresses: []
);
}
/**
* Gibt die erste IP-Adresse zurück.
*/
public function getFirstAddress(): ?IpAddress
{
return $this->addresses[0] ?? null;
}
/**
* Gibt nur IPv4-Adressen zurück.
*
* @return IpAddress[]
*/
public function getIpv4Addresses(): array
{
return array_filter(
$this->addresses,
fn (IpAddress $ip) => $ip->isV4()
);
}
/**
* Gibt nur IPv6-Adressen zurück.
*
* @return IpAddress[]
*/
public function getIpv6Addresses(): array
{
return array_filter(
$this->addresses,
fn (IpAddress $ip) => $ip->isV6()
);
}
/**
* Konvertiert zu Array.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'hostname' => $this->hostname,
'resolved' => $this->resolved,
'addresses' => array_map(fn (IpAddress $ip) => $ip->value, $this->addresses),
'address_count' => count($this->addresses),
'ipv4_count' => count($this->getIpv4Addresses()),
'ipv6_count' => count($this->getIpv6Addresses()),
];
}
}

View File

@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace App\Framework\Process\ValueObjects\Network;
use App\Framework\Core\ValueObjects\Duration;
/**
* Ping Test Result.
*/
final readonly class PingResult
{
public function __construct(
public string $host,
public bool $isReachable,
public ?Duration $latency = null,
public ?int $packetsSent = null,
public ?int $packetsReceived = null,
public ?float $packetLoss = null
) {
}
/**
* Erstellt ein erfolgreiches Ping-Ergebnis.
*/
public static function success(
string $host,
Duration $latency,
int $packetsSent = 4,
int $packetsReceived = 4
): self {
$packetLoss = $packetsSent > 0
? (($packetsSent - $packetsReceived) / $packetsSent) * 100
: 0.0;
return new self(
host: $host,
isReachable: true,
latency: $latency,
packetsSent: $packetsSent,
packetsReceived: $packetsReceived,
packetLoss: $packetLoss
);
}
/**
* Erstellt ein fehlgeschlagenes Ping-Ergebnis.
*/
public static function failed(string $host): self
{
return new self(
host: $host,
isReachable: false
);
}
/**
* Konvertiert zu Array.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'host' => $this->host,
'is_reachable' => $this->isReachable,
'latency_ms' => $this->latency?->toMilliseconds(),
'latency_human' => $this->latency?->toHumanReadable(),
'packets_sent' => $this->packetsSent,
'packets_received' => $this->packetsReceived,
'packet_loss_percent' => $this->packetLoss,
];
}
}

View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace App\Framework\Process\ValueObjects\Network;
/**
* Port Status Result.
*/
final readonly class PortStatus
{
public function __construct(
public int $port,
public bool $isOpen,
public string $service = ''
) {
}
/**
* Erstellt einen offenen Port.
*/
public static function open(int $port, string $service = ''): self
{
return new self($port, true, $service);
}
/**
* Erstellt einen geschlossenen Port.
*/
public static function closed(int $port): self
{
return new self($port, false);
}
/**
* Konvertiert zu Array.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'port' => $this->port,
'is_open' => $this->isOpen,
'service' => $this->service,
];
}
}

View File

@@ -0,0 +1,116 @@
<?php
declare(strict_types=1);
namespace App\Framework\Process\ValueObjects\Npm;
/**
* NPM Audit Result.
*
* Ergebnis eines NPM Security Audits mit Severity-Breakdown.
*/
final readonly class NpmAuditResult
{
/**
* @param array<string> $vulnerabilities
*/
public function __construct(
public int $total,
public int $info,
public int $low,
public int $moderate,
public int $high,
public int $critical,
public array $vulnerabilities = []
) {
}
/**
* Erstellt Ergebnis aus NPM Audit JSON.
*/
public static function fromAuditJson(array $data): self
{
$metadata = $data['metadata'] ?? [];
$vulnerabilities = $metadata['vulnerabilities'] ?? [];
return new self(
total: (int) ($metadata['total'] ?? 0),
info: (int) ($vulnerabilities['info'] ?? 0),
low: (int) ($vulnerabilities['low'] ?? 0),
moderate: (int) ($vulnerabilities['moderate'] ?? 0),
high: (int) ($vulnerabilities['high'] ?? 0),
critical: (int) ($vulnerabilities['critical'] ?? 0),
vulnerabilities: self::extractVulnerabilityNames($data)
);
}
/**
* Erstellt sicheres Ergebnis (keine Vulnerabilities).
*/
public static function safe(): self
{
return new self(
total: 0,
info: 0,
low: 0,
moderate: 0,
high: 0,
critical: 0
);
}
/**
* Prüft ob es kritische oder hohe Vulnerabilities gibt.
*/
public function hasCriticalIssues(): bool
{
return $this->critical > 0 || $this->high > 0;
}
/**
* Prüft ob es irgendwelche Vulnerabilities gibt.
*/
public function hasVulnerabilities(): bool
{
return $this->total > 0;
}
/**
* Konvertiert zu Array.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'total' => $this->total,
'info' => $this->info,
'low' => $this->low,
'moderate' => $this->moderate,
'high' => $this->high,
'critical' => $this->critical,
'has_critical_issues' => $this->hasCriticalIssues(),
'has_vulnerabilities' => $this->hasVulnerabilities(),
'vulnerabilities' => $this->vulnerabilities,
];
}
/**
* Extrahiert Vulnerability-Namen aus Audit-Daten.
*
* @return string[]
*/
private static function extractVulnerabilityNames(array $data): array
{
$names = [];
$advisories = $data['advisories'] ?? [];
foreach ($advisories as $advisory) {
if (isset($advisory['module_name'])) {
$names[] = $advisory['module_name'];
}
}
return array_unique($names);
}
}

View File

@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace App\Framework\Process\ValueObjects\Npm;
/**
* NPM Package Information.
*/
final readonly class NpmPackage
{
public function __construct(
public string $name,
public string $currentVersion,
public ?string $wantedVersion = null,
public ?string $latestVersion = null,
public ?string $type = null, // 'dependencies' or 'devDependencies'
public bool $isOutdated = false
) {
}
/**
* Erstellt Package aus NPM-Output.
*/
public static function fromNpmOutput(
string $name,
string $currentVersion,
?string $wantedVersion = null,
?string $latestVersion = null,
?string $type = null
): self {
return new self(
name: $name,
currentVersion: $currentVersion,
wantedVersion: $wantedVersion,
latestVersion: $latestVersion,
type: $type,
isOutdated: $latestVersion !== null && $latestVersion !== $currentVersion
);
}
/**
* Konvertiert zu Array.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'name' => $this->name,
'current_version' => $this->currentVersion,
'wanted_version' => $this->wantedVersion,
'latest_version' => $this->latestVersion,
'type' => $this->type,
'is_outdated' => $this->isOutdated,
];
}
}

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace App\Framework\Process\ValueObjects\Ssl;
/**
* SSL Certificate Information.
*/
final readonly class CertificateInfo
{
public function __construct(
public string $subject,
public string $issuer,
public \DateTimeImmutable $validFrom,
public \DateTimeImmutable $validTo,
public array $subjectAltNames,
public bool $isSelfSigned,
public ?string $serialNumber = null,
public ?string $signatureAlgorithm = null
) {
}
/**
* Prüft, ob das Zertifikat gültig ist.
*/
public function isValid(): bool
{
$now = new \DateTimeImmutable();
return $now >= $this->validFrom && $now <= $this->validTo;
}
/**
* Prüft, ob das Zertifikat bald abläuft.
*/
public function isExpiringSoon(int $daysThreshold = 30): bool
{
$now = new \DateTimeImmutable();
$threshold = $now->modify("+{$daysThreshold} days");
return $this->validTo <= $threshold;
}
/**
* Gibt die verbleibenden Tage bis zum Ablauf zurück.
*/
public function getDaysUntilExpiry(): int
{
$now = new \DateTimeImmutable();
$diff = $now->diff($this->validTo);
return $diff->days * ($diff->invert ? -1 : 1);
}
/**
* Prüft, ob das Zertifikat abgelaufen ist.
*/
public function isExpired(): bool
{
return new \DateTimeImmutable() > $this->validTo;
}
/**
* Konvertiert zu Array.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'subject' => $this->subject,
'issuer' => $this->issuer,
'valid_from' => $this->validFrom->format('Y-m-d H:i:s'),
'valid_to' => $this->validTo->format('Y-m-d H:i:s'),
'subject_alt_names' => $this->subjectAltNames,
'is_self_signed' => $this->isSelfSigned,
'is_valid' => $this->isValid(),
'is_expired' => $this->isExpired(),
'days_until_expiry' => $this->getDaysUntilExpiry(),
'serial_number' => $this->serialNumber,
'signature_algorithm' => $this->signatureAlgorithm,
];
}
}

View File

@@ -0,0 +1,106 @@
<?php
declare(strict_types=1);
namespace App\Framework\Process\ValueObjects\Ssl;
/**
* SSL Certificate Validation Result.
*/
final readonly class CertificateValidationResult
{
public function __construct(
public string $hostname,
public bool $isValid,
public ?CertificateInfo $certificateInfo = null,
public array $errors = [],
public array $warnings = []
) {
}
/**
* Erstellt ein erfolgreiches Validierungsergebnis.
*/
public static function success(string $hostname, CertificateInfo $info): self
{
return new self(
hostname: $hostname,
isValid: true,
certificateInfo: $info
);
}
/**
* Erstellt ein fehlgeschlagenes Validierungsergebnis.
*
* @param string[] $errors
*/
public static function failed(string $hostname, array $errors): self
{
return new self(
hostname: $hostname,
isValid: false,
errors: $errors
);
}
/**
* Erstellt ein Ergebnis mit Warnungen.
*
* @param string[] $warnings
*/
public function withWarnings(array $warnings): self
{
return new self(
hostname: $this->hostname,
isValid: $this->isValid,
certificateInfo: $this->certificateInfo,
errors: $this->errors,
warnings: $warnings
);
}
/**
* Gibt alle Probleme (Errors + Warnings) zurück.
*
* @return string[]
*/
public function getAllIssues(): array
{
return array_merge($this->errors, $this->warnings);
}
/**
* Prüft, ob es Warnungen gibt.
*/
public function hasWarnings(): bool
{
return count($this->warnings) > 0;
}
/**
* Prüft, ob es Fehler gibt.
*/
public function hasErrors(): bool
{
return count($this->errors) > 0;
}
/**
* Konvertiert zu Array.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'hostname' => $this->hostname,
'is_valid' => $this->isValid,
'certificate' => $this->certificateInfo?->toArray(),
'errors' => $this->errors,
'warnings' => $this->warnings,
'has_warnings' => $this->hasWarnings(),
'has_errors' => $this->hasErrors(),
];
}
}

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace App\Framework\Process\ValueObjects\SystemInfo;
/**
* CPU Information.
*/
final readonly class CpuInfo
{
public function __construct(
public int $cores,
public string $model = 'Unknown'
) {
}
/**
* Erstellt eine leere CpuInfo Instanz.
*/
public static function empty(): self
{
return new self(0, 'Unknown');
}
/**
* Gibt einen gekürzten Model-Namen zurück.
*/
public function getShortModel(): string
{
// Entfernt "(R)", "(TM)", "CPU" etc. für lesbareren Output
$model = $this->model;
$model = str_replace(['(R)', '(TM)', '(tm)'], '', $model);
$model = preg_replace('/\s+CPU\s+/', ' ', $model);
$model = preg_replace('/\s+/', ' ', $model);
return trim($model);
}
/**
* Konvertiert zu Array.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'cores' => $this->cores,
'model' => $this->model,
'model_short' => $this->getShortModel(),
];
}
}

View File

@@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace App\Framework\Process\ValueObjects\SystemInfo;
use App\Framework\Core\ValueObjects\Byte;
/**
* Disk Information.
*/
final readonly class DiskInfo
{
public function __construct(
public int $totalBytes,
public int $usedBytes,
public int $availableBytes,
public string $mountPoint = '/'
) {
}
/**
* Erstellt eine leere DiskInfo Instanz.
*/
public static function empty(): self
{
return new self(0, 0, 0, '/');
}
/**
* Gibt die Auslastung als Prozent zurück (0-100).
*/
public function getUsagePercentage(): float
{
if ($this->totalBytes === 0) {
return 0.0;
}
return ($this->usedBytes / $this->totalBytes) * 100;
}
/**
* Prüft, ob die Disk fast voll ist (>90%).
*/
public function isAlmostFull(): bool
{
return $this->getUsagePercentage() > 90;
}
/**
* Gibt Total als Byte Value Object zurück.
*/
public function getTotal(): Byte
{
return Byte::fromBytes($this->totalBytes);
}
/**
* Gibt Used als Byte Value Object zurück.
*/
public function getUsed(): Byte
{
return Byte::fromBytes($this->usedBytes);
}
/**
* Gibt Available als Byte Value Object zurück.
*/
public function getAvailable(): Byte
{
return Byte::fromBytes($this->availableBytes);
}
/**
* Konvertiert zu Array.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'mount_point' => $this->mountPoint,
'total_bytes' => $this->totalBytes,
'total_human' => $this->getTotal()->toHumanReadable(),
'used_bytes' => $this->usedBytes,
'used_human' => $this->getUsed()->toHumanReadable(),
'available_bytes' => $this->availableBytes,
'available_human' => $this->getAvailable()->toHumanReadable(),
'usage_percentage' => round($this->getUsagePercentage(), 2),
'is_almost_full' => $this->isAlmostFull(),
];
}
}

View File

@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace App\Framework\Process\ValueObjects\SystemInfo;
/**
* System Load Average.
*/
final readonly class LoadAverage
{
public function __construct(
public float $oneMinute,
public float $fiveMinutes,
public float $fifteenMinutes
) {
}
/**
* Erstellt eine leere LoadAverage Instanz.
*/
public static function empty(): self
{
return new self(0.0, 0.0, 0.0);
}
/**
* Prüft, ob das System überlastet ist (basierend auf 1min Load).
* Überlastet wenn Load > CPU Cores.
*/
public function isOverloaded(int $cpuCores): bool
{
return $this->oneMinute > $cpuCores;
}
/**
* Gibt den Auslastungsgrad zurück (0.0 - 1.0+).
*/
public function getUtilization(int $cpuCores): float
{
if ($cpuCores === 0) {
return 0.0;
}
return $this->oneMinute / $cpuCores;
}
/**
* Konvertiert zu Array.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'one_minute' => $this->oneMinute,
'five_minutes' => $this->fiveMinutes,
'fifteen_minutes' => $this->fifteenMinutes,
];
}
}

View File

@@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace App\Framework\Process\ValueObjects\SystemInfo;
use App\Framework\Core\ValueObjects\Byte;
/**
* Memory Information.
*/
final readonly class MemoryInfo
{
public function __construct(
public int $totalBytes,
public int $usedBytes,
public int $freeBytes,
public int $availableBytes
) {
}
/**
* Erstellt eine leere MemoryInfo Instanz.
*/
public static function empty(): self
{
return new self(0, 0, 0, 0);
}
/**
* Gibt die Auslastung als Prozent zurück (0-100).
*/
public function getUsagePercentage(): float
{
if ($this->totalBytes === 0) {
return 0.0;
}
return ($this->usedBytes / $this->totalBytes) * 100;
}
/**
* Gibt Total als Byte Value Object zurück.
*/
public function getTotal(): Byte
{
return Byte::fromBytes($this->totalBytes);
}
/**
* Gibt Used als Byte Value Object zurück.
*/
public function getUsed(): Byte
{
return Byte::fromBytes($this->usedBytes);
}
/**
* Gibt Free als Byte Value Object zurück.
*/
public function getFree(): Byte
{
return Byte::fromBytes($this->freeBytes);
}
/**
* Gibt Available als Byte Value Object zurück.
*/
public function getAvailable(): Byte
{
return Byte::fromBytes($this->availableBytes);
}
/**
* Konvertiert zu Array.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'total_bytes' => $this->totalBytes,
'total_human' => $this->getTotal()->toHumanReadable(),
'used_bytes' => $this->usedBytes,
'used_human' => $this->getUsed()->toHumanReadable(),
'free_bytes' => $this->freeBytes,
'free_human' => $this->getFree()->toHumanReadable(),
'available_bytes' => $this->availableBytes,
'available_human' => $this->getAvailable()->toHumanReadable(),
'usage_percentage' => round($this->getUsagePercentage(), 2),
];
}
}

View File

@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace App\Framework\Process\ValueObjects\SystemInfo;
/**
* Process Information.
*/
final readonly class ProcessInfo
{
public function __construct(
public int $total,
public int $running,
public int $sleeping
) {
}
/**
* Erstellt eine leere ProcessInfo Instanz.
*/
public static function empty(): self
{
return new self(0, 0, 0);
}
/**
* Gibt die Anzahl der anderen Prozesse zurück.
*/
public function getOther(): int
{
return max(0, $this->total - $this->running - $this->sleeping);
}
/**
* Konvertiert zu Array.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'total' => $this->total,
'running' => $this->running,
'sleeping' => $this->sleeping,
'other' => $this->getOther(),
];
}
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace App\Framework\Process\ValueObjects\SystemInfo;
use App\Framework\Core\ValueObjects\Duration;
use DateTimeImmutable;
/**
* System Uptime Information.
*/
final readonly class SystemUptime
{
public function __construct(
public DateTimeImmutable $bootTime,
public Duration $uptime
) {
}
/**
* Gibt das Boot-Datum formatiert zurück.
*/
public function getBootTimeFormatted(): string
{
return $this->bootTime->format('Y-m-d H:i:s');
}
/**
* Gibt die Uptime in Tagen zurück.
*/
public function getUptimeDays(): int
{
return (int) floor($this->uptime->toSeconds() / 86400);
}
/**
* Konvertiert zu Array.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'boot_time' => $this->bootTime->format('Y-m-d H:i:s'),
'uptime_seconds' => $this->uptime->toSeconds(),
'uptime_human' => $this->uptime->toHumanReadable(),
'uptime_days' => $this->getUptimeDays(),
];
}
}