- 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
220 lines
7.9 KiB
PHP
220 lines
7.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Console\ErrorRecovery;
|
|
|
|
use App\Framework\Console\ConsoleColor;
|
|
use App\Framework\Console\ConsoleOutputInterface;
|
|
use App\Framework\Console\ExitCode;
|
|
use App\Framework\Exception\ErrorCode;
|
|
use App\Framework\Exception\FrameworkException;
|
|
use App\Framework\Logging\Logger;
|
|
use App\Framework\Logging\ValueObjects\LogContext;
|
|
|
|
final readonly class ConsoleErrorHandler
|
|
{
|
|
public function __construct(
|
|
private ErrorRecoveryService $recoveryService,
|
|
private ?Logger $logger = null
|
|
) {
|
|
}
|
|
|
|
public function handleCommandNotFound(string $command, ConsoleOutputInterface $output): ExitCode
|
|
{
|
|
$this->logError("Command not found: {$command}");
|
|
$this->recoveryService->handleCommandNotFound($command, $output);
|
|
|
|
return ExitCode::COMMAND_NOT_FOUND;
|
|
}
|
|
|
|
public function handleCommandExecutionError(
|
|
string $command,
|
|
\Throwable $error,
|
|
ConsoleOutputInterface $output
|
|
): ExitCode {
|
|
$this->logError("Command execution error in '{$command}': " . $error->getMessage(), [
|
|
'command' => $command,
|
|
'error_type' => get_class($error),
|
|
'trace' => $error->getTraceAsString(),
|
|
]);
|
|
|
|
if ($error instanceof FrameworkException) {
|
|
return $this->handleFrameworkException($command, $error, $output);
|
|
}
|
|
|
|
$this->recoveryService->handleCommandExecutionError($command, $error, $output);
|
|
|
|
return $this->determineExitCode($error);
|
|
}
|
|
|
|
public function handleValidationError(
|
|
string $command,
|
|
string $validationError,
|
|
ConsoleOutputInterface $output
|
|
): ExitCode {
|
|
$this->logError("Validation error in '{$command}': {$validationError}");
|
|
$this->recoveryService->handleArgumentError($command, $validationError, $output);
|
|
|
|
return ExitCode::INVALID_INPUT;
|
|
}
|
|
|
|
public function handlePermissionError(
|
|
string $command,
|
|
ConsoleOutputInterface $output
|
|
): ExitCode {
|
|
$this->logError("Permission denied for command: {$command}");
|
|
$this->recoveryService->handlePermissionError($command, $output);
|
|
|
|
return ExitCode::PERMISSION_DENIED;
|
|
}
|
|
|
|
public function handleUnexpectedError(
|
|
string $command,
|
|
\Throwable $error,
|
|
ConsoleOutputInterface $output
|
|
): ExitCode {
|
|
$this->logError("Unexpected error in '{$command}': " . $error->getMessage(), [
|
|
'command' => $command,
|
|
'error_type' => get_class($error),
|
|
'file' => $error->getFile(),
|
|
'line' => $error->getLine(),
|
|
'trace' => $error->getTraceAsString(),
|
|
]);
|
|
|
|
$output->writeLine("💥 An unexpected error occurred:", ConsoleColor::RED);
|
|
$output->writeLine(" {$error->getMessage()}", ConsoleColor::RED);
|
|
$output->newLine();
|
|
|
|
$output->writeLine("🔍 Debug information:", ConsoleColor::GRAY);
|
|
$output->writeLine(" Error: " . get_class($error), ConsoleColor::GRAY);
|
|
$output->writeLine(" File: {$error->getFile()}:{$error->getLine()}", ConsoleColor::GRAY);
|
|
$output->newLine();
|
|
|
|
$this->recoveryService->handleGeneralError($command, $error, $output);
|
|
|
|
return ExitCode::GENERAL_ERROR;
|
|
}
|
|
|
|
public function handleGracefulShutdown(string $reason, ConsoleOutputInterface $output): ExitCode
|
|
{
|
|
$this->logInfo("Graceful shutdown: {$reason}");
|
|
|
|
$output->writeLine("🛑 Operation interrupted: {$reason}", ConsoleColor::YELLOW);
|
|
$output->writeLine(" The command was safely terminated.", ConsoleColor::GRAY);
|
|
$output->newLine();
|
|
|
|
return ExitCode::INTERRUPTED;
|
|
}
|
|
|
|
private function handleFrameworkException(
|
|
string $command,
|
|
FrameworkException $exception,
|
|
ConsoleOutputInterface $output
|
|
): ExitCode {
|
|
$errorCode = $exception->getErrorCode();
|
|
|
|
return match ($errorCode) {
|
|
ErrorCode::CON_COMMAND_NOT_FOUND => $this->handleCommandNotFound($command, $output),
|
|
|
|
ErrorCode::CON_INVALID_ARGUMENTS => $this->handleValidationError(
|
|
$command,
|
|
$exception->getMessage(),
|
|
$output
|
|
),
|
|
|
|
ErrorCode::AUTH_UNAUTHORIZED,
|
|
ErrorCode::AUTH_INSUFFICIENT_PRIVILEGES => $this->handlePermissionError($command, $output),
|
|
|
|
ErrorCode::DB_CONNECTION_FAILED,
|
|
ErrorCode::DB_QUERY_FAILED => $this->handleDatabaseError($command, $exception, $output),
|
|
|
|
ErrorCode::HTTP_RATE_LIMIT_EXCEEDED => $this->handleRateLimitError($command, $exception, $output),
|
|
|
|
default => $this->handleGeneralFrameworkError($command, $exception, $output)
|
|
};
|
|
}
|
|
|
|
private function handleDatabaseError(
|
|
string $command,
|
|
FrameworkException $exception,
|
|
ConsoleOutputInterface $output
|
|
): ExitCode {
|
|
$output->writeLine("🗄️ Database error in command '{$command}':", ConsoleColor::RED);
|
|
$output->writeLine(" {$exception->getMessage()}", ConsoleColor::RED);
|
|
$output->newLine();
|
|
|
|
$output->writeLine("💡 Database troubleshooting:", ConsoleColor::CYAN);
|
|
$output->writeLine(" • Check database connection settings", ConsoleColor::WHITE);
|
|
$output->writeLine(" • Verify database server is running", ConsoleColor::WHITE);
|
|
$output->writeLine(" • Check network connectivity", ConsoleColor::WHITE);
|
|
$output->writeLine(" • Verify database credentials", ConsoleColor::WHITE);
|
|
$output->newLine();
|
|
|
|
return ExitCode::DATABASE_ERROR;
|
|
}
|
|
|
|
private function handleRateLimitError(
|
|
string $command,
|
|
FrameworkException $exception,
|
|
ConsoleOutputInterface $output
|
|
): ExitCode {
|
|
$output->writeLine("⏳ Rate limit exceeded for command '{$command}':", ConsoleColor::YELLOW);
|
|
$output->writeLine(" {$exception->getMessage()}", ConsoleColor::YELLOW);
|
|
$output->newLine();
|
|
|
|
if ($exception->hasRetryAfter()) {
|
|
$retryAfter = $exception->getRetryAfter();
|
|
$output->writeLine("🕐 You can retry this command in {$retryAfter} seconds.", ConsoleColor::CYAN);
|
|
} else {
|
|
$output->writeLine("🕐 Please wait before retrying this command.", ConsoleColor::CYAN);
|
|
}
|
|
$output->newLine();
|
|
|
|
return ExitCode::RATE_LIMITED;
|
|
}
|
|
|
|
private function handleGeneralFrameworkError(
|
|
string $command,
|
|
FrameworkException $exception,
|
|
ConsoleOutputInterface $output
|
|
): ExitCode {
|
|
$this->recoveryService->handleCommandExecutionError($command, $exception, $output);
|
|
|
|
return $this->determineExitCode($exception);
|
|
}
|
|
|
|
private function determineExitCode(\Throwable $error): ExitCode
|
|
{
|
|
if ($error instanceof FrameworkException) {
|
|
return match ($error->getErrorCode()) {
|
|
ErrorCode::CON_COMMAND_NOT_FOUND => ExitCode::COMMAND_NOT_FOUND,
|
|
ErrorCode::CON_INVALID_ARGUMENTS => ExitCode::INVALID_INPUT,
|
|
ErrorCode::AUTH_UNAUTHORIZED,
|
|
ErrorCode::AUTH_INSUFFICIENT_PRIVILEGES => ExitCode::PERMISSION_DENIED,
|
|
ErrorCode::DB_CONNECTION_FAILED,
|
|
ErrorCode::DB_QUERY_FAILED => ExitCode::DATABASE_ERROR,
|
|
ErrorCode::HTTP_RATE_LIMIT_EXCEEDED => ExitCode::RATE_LIMITED,
|
|
default => ExitCode::GENERAL_ERROR
|
|
};
|
|
}
|
|
|
|
return match (true) {
|
|
$error instanceof \ArgumentCountError,
|
|
$error instanceof \TypeError => ExitCode::INVALID_INPUT,
|
|
$error instanceof \Error => ExitCode::FATAL_ERROR,
|
|
default => ExitCode::GENERAL_ERROR
|
|
};
|
|
}
|
|
|
|
private function logError(string $message, array $context = []): void
|
|
{
|
|
$this->logger?->error($message, LogContext::withData($context));
|
|
}
|
|
|
|
private function logInfo(string $message, array $context = []): void
|
|
{
|
|
$this->logger?->info($message, LogContext::withData($context));
|
|
}
|
|
}
|