- Fix RedisCache driver to handle MGET failures gracefully with fallback - Add comprehensive discovery context comparison debug tools - Identify root cause: WEB context discovery missing 166 items vs CLI - WEB context missing RequestFactory class entirely (52 vs 69 commands) - Improved exception handling with detailed binding diagnostics
274 lines
6.8 KiB
PHP
274 lines
6.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Exception;
|
|
|
|
class FrameworkException extends \RuntimeException
|
|
{
|
|
protected ExceptionContext $context;
|
|
|
|
protected ?ErrorCode $errorCode;
|
|
|
|
protected ?int $retryAfter;
|
|
|
|
public function __construct(
|
|
string $message,
|
|
ExceptionContext $context,
|
|
int $code = 0,
|
|
?\Throwable $previous = null,
|
|
?ErrorCode $errorCode = null,
|
|
?int $retryAfter = null
|
|
) {
|
|
parent::__construct($message, $code, $previous);
|
|
$this->context = $context;
|
|
$this->errorCode = $errorCode;
|
|
$this->retryAfter = $retryAfter ?? $errorCode?->getRetryAfterSeconds();
|
|
}
|
|
|
|
public function getContext(): ExceptionContext
|
|
{
|
|
return $this->context;
|
|
}
|
|
|
|
public function __clone(): void
|
|
{
|
|
// Allow cloning of the exception for immutable modifications
|
|
}
|
|
|
|
public function withContext(ExceptionContext $context): self
|
|
{
|
|
return new static(
|
|
$this->getMessage(),
|
|
$context,
|
|
$this->getCode(),
|
|
$this->getPrevious(),
|
|
$this->errorCode,
|
|
$this->retryAfter
|
|
);
|
|
}
|
|
|
|
public function withOperation(string $operation, ?string $component = null): self
|
|
{
|
|
return $this->withContext(
|
|
$this->context->withOperation($operation, $component)
|
|
);
|
|
}
|
|
|
|
public function withData(array $data): self
|
|
{
|
|
return $this->withContext(
|
|
$this->context->withData($data)
|
|
);
|
|
}
|
|
|
|
public function withDebug(array $debug): self
|
|
{
|
|
return $this->withContext(
|
|
$this->context->withDebug($debug)
|
|
);
|
|
}
|
|
|
|
public function withMetadata(array $metadata): self
|
|
{
|
|
return $this->withContext(
|
|
$this->context->withMetadata($metadata)
|
|
);
|
|
}
|
|
|
|
public function toArray(): array
|
|
{
|
|
$array = [
|
|
'class' => static::class,
|
|
'message' => $this->getMessage(),
|
|
'code' => $this->getCode(),
|
|
'file' => $this->getFile(),
|
|
'line' => $this->getLine(),
|
|
'context' => $this->context->toArray(),
|
|
'trace' => $this->getTraceAsString(),
|
|
];
|
|
|
|
// ErrorCode-spezifische Daten hinzufügen wenn vorhanden
|
|
if ($this->errorCode) {
|
|
$array['error_code'] = $this->errorCode->value;
|
|
$array['error_category'] = $this->errorCode->getCategory();
|
|
$array['description'] = $this->errorCode->getDescription();
|
|
$array['recovery_hint'] = $this->errorCode->getRecoveryHint();
|
|
$array['is_recoverable'] = $this->errorCode->isRecoverable();
|
|
$array['retry_after'] = $this->retryAfter;
|
|
}
|
|
|
|
return $array;
|
|
}
|
|
|
|
// === Factory Methods ===
|
|
|
|
/**
|
|
* Einfache Exception ohne ErrorCode - für schnelle Verwendung
|
|
*/
|
|
public static function simple(
|
|
string $message,
|
|
?\Throwable $previous = null,
|
|
int $code = 0
|
|
): static {
|
|
return new static(
|
|
message: $message,
|
|
context: ExceptionContext::empty(),
|
|
code: $code,
|
|
previous: $previous
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Exception mit ErrorCode und automatischer Beschreibung
|
|
*/
|
|
public static function create(
|
|
ErrorCode $errorCode,
|
|
?string $message = null,
|
|
?ExceptionContext $context = null,
|
|
?\Throwable $previous = null,
|
|
int $code = 0
|
|
): static {
|
|
$finalMessage = $message ?? $errorCode->getDescription();
|
|
$finalContext = $context ?? ExceptionContext::empty();
|
|
|
|
return new static(
|
|
message: $finalMessage,
|
|
context: $finalContext,
|
|
code: $code,
|
|
previous: $previous,
|
|
errorCode: $errorCode
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Exception mit Operation Context
|
|
*/
|
|
public static function forOperation(
|
|
string $operation,
|
|
?string $component = null,
|
|
?string $message = null,
|
|
?ErrorCode $errorCode = null,
|
|
?\Throwable $previous = null,
|
|
int $code = 0
|
|
): static {
|
|
$context = ExceptionContext::forOperation($operation, $component);
|
|
$finalMessage = $message ?? $errorCode?->getDescription() ?? "Operation failed: $operation";
|
|
|
|
return new static(
|
|
message: $finalMessage,
|
|
context: $context,
|
|
code: $code,
|
|
previous: $previous,
|
|
errorCode: $errorCode
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Exception mit vollständigem Context und Daten
|
|
*/
|
|
public static function fromContext(
|
|
string $message,
|
|
ExceptionContext $context,
|
|
?ErrorCode $errorCode = null,
|
|
?\Throwable $previous = null,
|
|
int $code = 0
|
|
): static {
|
|
return new static(
|
|
message: $message,
|
|
context: $context,
|
|
code: $code,
|
|
previous: $previous,
|
|
errorCode: $errorCode
|
|
);
|
|
}
|
|
|
|
// === ErrorCode Getter Methods ===
|
|
|
|
public function getErrorCode(): ?ErrorCode
|
|
{
|
|
return $this->errorCode;
|
|
}
|
|
|
|
public function getRetryAfter(): ?int
|
|
{
|
|
return $this->retryAfter;
|
|
}
|
|
|
|
public function isRecoverable(): bool
|
|
{
|
|
return $this->errorCode?->isRecoverable() ?? false;
|
|
}
|
|
|
|
public function getRecoveryHint(): ?string
|
|
{
|
|
return $this->errorCode?->getRecoveryHint();
|
|
}
|
|
|
|
// === ErrorCode Utility Methods ===
|
|
|
|
/**
|
|
* Prüft ob Exception von bestimmtem Error Code Typ ist
|
|
*/
|
|
public function isErrorCode(ErrorCode $errorCode): bool
|
|
{
|
|
return $this->errorCode === $errorCode;
|
|
}
|
|
|
|
/**
|
|
* Prüft ob Exception zu bestimmter Kategorie gehört
|
|
*/
|
|
public function isCategory(string $category): bool
|
|
{
|
|
return $this->errorCode?->getCategory() === strtoupper($category);
|
|
}
|
|
|
|
/**
|
|
* Setzt ErrorCode nachträglich
|
|
*/
|
|
public function withErrorCode(ErrorCode $errorCode): self
|
|
{
|
|
$new = clone $this;
|
|
$new->errorCode = $errorCode;
|
|
$new->retryAfter = $errorCode->getRetryAfterSeconds();
|
|
|
|
return $new;
|
|
}
|
|
|
|
/**
|
|
* Setzt Custom Retry-Zeit
|
|
*/
|
|
public function withRetryAfter(int $seconds): self
|
|
{
|
|
$new = clone $this;
|
|
$new->retryAfter = $seconds;
|
|
|
|
return $new;
|
|
}
|
|
|
|
/**
|
|
* Get exception data from context
|
|
*/
|
|
public function getData(): array
|
|
{
|
|
return $this->context->data;
|
|
}
|
|
|
|
/**
|
|
* String-Representation für Logging
|
|
*/
|
|
public function __toString(): string
|
|
{
|
|
$errorCodePart = $this->errorCode ? '[' . $this->errorCode->value . ']' : '';
|
|
|
|
return sprintf(
|
|
'%s %s: %s in %s:%d',
|
|
static::class,
|
|
$errorCodePart,
|
|
$this->getMessage(),
|
|
$this->getFile(),
|
|
$this->getLine()
|
|
);
|
|
}
|
|
}
|