refactor(deployment): Remove WireGuard VPN dependency and restore public service access
Remove WireGuard integration from production deployment to simplify infrastructure: - Remove docker-compose-direct-access.yml (VPN-bound services) - Remove VPN-only middlewares from Grafana, Prometheus, Portainer - Remove WireGuard middleware definitions from Traefik - Remove WireGuard IPs (10.8.0.0/24) from Traefik forwarded headers All monitoring services now publicly accessible via subdomains: - grafana.michaelschiemer.de (with Grafana native auth) - prometheus.michaelschiemer.de (with Basic Auth) - portainer.michaelschiemer.de (with Portainer native auth) All services use Let's Encrypt SSL certificates via Traefik.
This commit is contained in:
309
src/Framework/ExceptionHandling/Context/ExceptionContextData.php
Normal file
309
src/Framework/ExceptionHandling/Context/ExceptionContextData.php
Normal file
@@ -0,0 +1,309 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\ExceptionHandling\Context;
|
||||
|
||||
use DateTimeImmutable;
|
||||
|
||||
/**
|
||||
* Exception Context Data
|
||||
*
|
||||
* Immutable value object containing rich exception context.
|
||||
* Stored externally via ExceptionContextProvider - never embedded in exceptions.
|
||||
*
|
||||
* PHP 8.5+ readonly class with asymmetric visibility for extensibility.
|
||||
*/
|
||||
final readonly class ExceptionContextData
|
||||
{
|
||||
public readonly DateTimeImmutable $occurredAt;
|
||||
|
||||
/**
|
||||
* @param string|null $operation Operation being performed (e.g., 'user.create', 'payment.process')
|
||||
* @param string|null $component Component where error occurred (e.g., 'UserService', 'PaymentGateway')
|
||||
* @param array<string, mixed> $data Domain data (e.g., user_id, order_id, amount)
|
||||
* @param array<string, mixed> $debug Debug data (queries, traces, internal state)
|
||||
* @param array<string, mixed> $metadata Additional metadata (tags, severity, fingerprint)
|
||||
* @param DateTimeImmutable|null $occurredAt When the exception occurred
|
||||
* @param string|null $userId User ID if available
|
||||
* @param string|null $requestId Request ID for tracing
|
||||
* @param string|null $sessionId Session ID if available
|
||||
* @param string|null $clientIp Client IP address for HTTP requests
|
||||
* @param string|null $userAgent User agent string for HTTP requests
|
||||
* @param array<string> $tags Tags for categorization (e.g., ['payment', 'external_api'])
|
||||
*/
|
||||
public function __construct(
|
||||
public ?string $operation = null,
|
||||
public ?string $component = null,
|
||||
public array $data = [],
|
||||
public array $debug = [],
|
||||
public array $metadata = [],
|
||||
?DateTimeImmutable $occurredAt = null,
|
||||
public ?string $userId = null,
|
||||
public ?string $requestId = null,
|
||||
public ?string $sessionId = null,
|
||||
public ?string $clientIp = null,
|
||||
public ?string $userAgent = null,
|
||||
public array $tags = [],
|
||||
) {
|
||||
$this->occurredAt ??= new DateTimeImmutable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create empty context
|
||||
*/
|
||||
public static function empty(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create context with operation
|
||||
*/
|
||||
public static function forOperation(string $operation, ?string $component = null): self
|
||||
{
|
||||
return new self(
|
||||
operation: $operation,
|
||||
component: $component
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create context with data
|
||||
*/
|
||||
public static function withData(array $data): self
|
||||
{
|
||||
return new self(data: $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new instance with operation
|
||||
*/
|
||||
public function withOperation(string $operation, ?string $component = null): self
|
||||
{
|
||||
return new self(
|
||||
operation: $operation,
|
||||
component: $component ?? $this->component,
|
||||
data: $this->data,
|
||||
debug: $this->debug,
|
||||
metadata: $this->metadata,
|
||||
occurredAt: $this->occurredAt,
|
||||
userId: $this->userId,
|
||||
requestId: $this->requestId,
|
||||
sessionId: $this->sessionId,
|
||||
clientIp: $this->clientIp,
|
||||
userAgent: $this->userAgent,
|
||||
tags: $this->tags
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add data to context
|
||||
*/
|
||||
public function addData(array $data): self
|
||||
{
|
||||
return new self(
|
||||
operation: $this->operation,
|
||||
component: $this->component,
|
||||
data: array_merge($this->data, $data),
|
||||
debug: $this->debug,
|
||||
metadata: $this->metadata,
|
||||
occurredAt: $this->occurredAt,
|
||||
userId: $this->userId,
|
||||
requestId: $this->requestId,
|
||||
sessionId: $this->sessionId,
|
||||
clientIp: $this->clientIp,
|
||||
userAgent: $this->userAgent,
|
||||
tags: $this->tags
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add debug information
|
||||
*/
|
||||
public function addDebug(array $debug): self
|
||||
{
|
||||
return new self(
|
||||
operation: $this->operation,
|
||||
component: $this->component,
|
||||
data: $this->data,
|
||||
debug: array_merge($this->debug, $debug),
|
||||
metadata: $this->metadata,
|
||||
occurredAt: $this->occurredAt,
|
||||
userId: $this->userId,
|
||||
requestId: $this->requestId,
|
||||
sessionId: $this->sessionId,
|
||||
clientIp: $this->clientIp,
|
||||
userAgent: $this->userAgent,
|
||||
tags: $this->tags
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add metadata
|
||||
*/
|
||||
public function addMetadata(array $metadata): self
|
||||
{
|
||||
return new self(
|
||||
operation: $this->operation,
|
||||
component: $this->component,
|
||||
data: $this->data,
|
||||
debug: $this->debug,
|
||||
metadata: array_merge($this->metadata, $metadata),
|
||||
occurredAt: $this->occurredAt,
|
||||
userId: $this->userId,
|
||||
requestId: $this->requestId,
|
||||
sessionId: $this->sessionId,
|
||||
clientIp: $this->clientIp,
|
||||
userAgent: $this->userAgent,
|
||||
tags: $this->tags
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add user ID
|
||||
*/
|
||||
public function withUserId(string $userId): self
|
||||
{
|
||||
return new self(
|
||||
operation: $this->operation,
|
||||
component: $this->component,
|
||||
data: $this->data,
|
||||
debug: $this->debug,
|
||||
metadata: $this->metadata,
|
||||
occurredAt: $this->occurredAt,
|
||||
userId: $userId,
|
||||
requestId: $this->requestId,
|
||||
sessionId: $this->sessionId,
|
||||
clientIp: $this->clientIp,
|
||||
userAgent: $this->userAgent,
|
||||
tags: $this->tags
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add request ID
|
||||
*/
|
||||
public function withRequestId(string $requestId): self
|
||||
{
|
||||
return new self(
|
||||
operation: $this->operation,
|
||||
component: $this->component,
|
||||
data: $this->data,
|
||||
debug: $this->debug,
|
||||
metadata: $this->metadata,
|
||||
occurredAt: $this->occurredAt,
|
||||
userId: $this->userId,
|
||||
requestId: $requestId,
|
||||
sessionId: $this->sessionId,
|
||||
clientIp: $this->clientIp,
|
||||
userAgent: $this->userAgent,
|
||||
tags: $this->tags
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add session ID
|
||||
*/
|
||||
public function withSessionId(string $sessionId): self
|
||||
{
|
||||
return new self(
|
||||
operation: $this->operation,
|
||||
component: $this->component,
|
||||
data: $this->data,
|
||||
debug: $this->debug,
|
||||
metadata: $this->metadata,
|
||||
occurredAt: $this->occurredAt,
|
||||
userId: $this->userId,
|
||||
requestId: $this->requestId,
|
||||
sessionId: $sessionId,
|
||||
clientIp: $this->clientIp,
|
||||
userAgent: $this->userAgent,
|
||||
tags: $this->tags
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add client IP
|
||||
*/
|
||||
public function withClientIp(string $clientIp): self
|
||||
{
|
||||
return new self(
|
||||
operation: $this->operation,
|
||||
component: $this->component,
|
||||
data: $this->data,
|
||||
debug: $this->debug,
|
||||
metadata: $this->metadata,
|
||||
occurredAt: $this->occurredAt,
|
||||
userId: $this->userId,
|
||||
requestId: $this->requestId,
|
||||
sessionId: $this->sessionId,
|
||||
clientIp: $clientIp,
|
||||
userAgent: $this->userAgent,
|
||||
tags: $this->tags
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add user agent
|
||||
*/
|
||||
public function withUserAgent(string $userAgent): self
|
||||
{
|
||||
return new self(
|
||||
operation: $this->operation,
|
||||
component: $this->component,
|
||||
data: $this->data,
|
||||
debug: $this->debug,
|
||||
metadata: $this->metadata,
|
||||
occurredAt: $this->occurredAt,
|
||||
userId: $this->userId,
|
||||
requestId: $this->requestId,
|
||||
sessionId: $this->sessionId,
|
||||
clientIp: $this->clientIp,
|
||||
userAgent: $userAgent,
|
||||
tags: $this->tags
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add tags
|
||||
*/
|
||||
public function withTags(string ...$tags): self
|
||||
{
|
||||
return new self(
|
||||
operation: $this->operation,
|
||||
component: $this->component,
|
||||
data: $this->data,
|
||||
debug: $this->debug,
|
||||
metadata: $this->metadata,
|
||||
occurredAt: $this->occurredAt,
|
||||
userId: $this->userId,
|
||||
requestId: $this->requestId,
|
||||
sessionId: $this->sessionId,
|
||||
clientIp: $this->clientIp,
|
||||
userAgent: $this->userAgent,
|
||||
tags: array_merge($this->tags, $tags)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert to array for serialization
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'operation' => $this->operation,
|
||||
'component' => $this->component,
|
||||
'data' => $this->data,
|
||||
'debug' => $this->debug,
|
||||
'metadata' => $this->metadata,
|
||||
'occurred_at' => $this->occurredAt?->format('Y-m-d H:i:s.u'),
|
||||
'user_id' => $this->userId,
|
||||
'request_id' => $this->requestId,
|
||||
'session_id' => $this->sessionId,
|
||||
'client_ip' => $this->clientIp,
|
||||
'user_agent' => $this->userAgent,
|
||||
'tags' => $this->tags,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\ExceptionHandling\Context;
|
||||
|
||||
use WeakMap;
|
||||
|
||||
/**
|
||||
* Exception Context Provider
|
||||
*
|
||||
* Manages exception context externally using WeakMap for automatic garbage collection.
|
||||
* Context is automatically cleaned up when the exception is garbage collected.
|
||||
*
|
||||
* PHP 8.5+ WeakMap-based implementation - no memory leaks possible.
|
||||
*/
|
||||
final class ExceptionContextProvider
|
||||
{
|
||||
/** @var WeakMap<\Throwable, ExceptionContextData> */
|
||||
private WeakMap $contexts;
|
||||
|
||||
private static ?self $instance = null;
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
$this->contexts = new WeakMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get singleton instance
|
||||
*
|
||||
* Singleton pattern ensures consistent context across the application
|
||||
*/
|
||||
public static function instance(): self
|
||||
{
|
||||
return self::$instance ??= new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach context to exception
|
||||
*
|
||||
* @param \Throwable $exception The exception to attach context to
|
||||
* @param ExceptionContextData $context The context data
|
||||
*/
|
||||
public function attach(\Throwable $exception, ExceptionContextData $context): void
|
||||
{
|
||||
$this->contexts[$exception] = $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get context for exception
|
||||
*
|
||||
* @param \Throwable $exception The exception to get context for
|
||||
* @return ExceptionContextData|null The context data or null if not found
|
||||
*/
|
||||
public function get(\Throwable $exception): ?ExceptionContextData
|
||||
{
|
||||
return $this->contexts[$exception] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if exception has context
|
||||
*
|
||||
* @param \Throwable $exception The exception to check
|
||||
* @return bool True if context exists
|
||||
*/
|
||||
public function has(\Throwable $exception): bool
|
||||
{
|
||||
return isset($this->contexts[$exception]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove context from exception
|
||||
*
|
||||
* Note: Usually not needed due to WeakMap automatic cleanup,
|
||||
* but provided for explicit control if needed.
|
||||
*
|
||||
* @param \Throwable $exception The exception to remove context from
|
||||
*/
|
||||
public function detach(\Throwable $exception): void
|
||||
{
|
||||
unset($this->contexts[$exception]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get statistics about context storage
|
||||
*
|
||||
* @return array{total_contexts: int}
|
||||
*/
|
||||
public function getStats(): array
|
||||
{
|
||||
// WeakMap doesn't provide count(), so we iterate
|
||||
$count = 0;
|
||||
foreach ($this->contexts as $_) {
|
||||
$count++;
|
||||
}
|
||||
|
||||
return [
|
||||
'total_contexts' => $count,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all contexts
|
||||
*
|
||||
* Mainly for testing purposes
|
||||
*/
|
||||
public function clear(): void
|
||||
{
|
||||
$this->contexts = new WeakMap();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user