feat: CI/CD pipeline setup complete - Ansible playbooks updated, secrets configured, workflow ready
This commit is contained in:
103
src/Framework/Shutdown/ShutdownEvent.php
Normal file
103
src/Framework/Shutdown/ShutdownEvent.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Shutdown;
|
||||
|
||||
use App\Framework\Core\ValueObjects\Byte;
|
||||
use DateTimeImmutable;
|
||||
|
||||
/**
|
||||
* Event dispatched during application shutdown
|
||||
*
|
||||
* Allows other services to perform cleanup tasks when the application is shutting down.
|
||||
* The event is dispatched for both normal shutdowns and fatal error shutdowns.
|
||||
*/
|
||||
final readonly class ShutdownEvent
|
||||
{
|
||||
public function __construct(
|
||||
public ?array $error,
|
||||
public Byte $memoryUsage,
|
||||
public Byte $peakMemoryUsage,
|
||||
public DateTimeImmutable $occurredAt = new DateTimeImmutable()
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Check if shutdown was caused by a fatal error
|
||||
*/
|
||||
public function isFatalError(): bool
|
||||
{
|
||||
if ($this->error === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$type = $this->error['type'] ?? 0;
|
||||
|
||||
return in_array($type, [
|
||||
E_ERROR,
|
||||
E_PARSE,
|
||||
E_CORE_ERROR,
|
||||
E_CORE_WARNING,
|
||||
E_COMPILE_ERROR,
|
||||
E_COMPILE_WARNING,
|
||||
E_USER_ERROR,
|
||||
E_RECOVERABLE_ERROR
|
||||
], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this was a normal (non-error) shutdown
|
||||
*/
|
||||
public function isNormalShutdown(): bool
|
||||
{
|
||||
return $this->error === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get human-readable error type name
|
||||
*/
|
||||
public function getErrorTypeName(): ?string
|
||||
{
|
||||
if ($this->error === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = $this->error['type'] ?? 0;
|
||||
|
||||
return match ($type) {
|
||||
E_ERROR => 'E_ERROR',
|
||||
E_PARSE => 'E_PARSE',
|
||||
E_CORE_ERROR => 'E_CORE_ERROR',
|
||||
E_CORE_WARNING => 'E_CORE_WARNING',
|
||||
E_COMPILE_ERROR => 'E_COMPILE_ERROR',
|
||||
E_COMPILE_WARNING => 'E_COMPILE_WARNING',
|
||||
E_USER_ERROR => 'E_USER_ERROR',
|
||||
E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
|
||||
default => "UNKNOWN_ERROR_{$type}"
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get error message if fatal error occurred
|
||||
*/
|
||||
public function getErrorMessage(): ?string
|
||||
{
|
||||
return $this->error['message'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get error file if fatal error occurred
|
||||
*/
|
||||
public function getErrorFile(): ?string
|
||||
{
|
||||
return $this->error['file'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get error line if fatal error occurred
|
||||
*/
|
||||
public function getErrorLine(): ?int
|
||||
{
|
||||
return $this->error['line'] ?? null;
|
||||
}
|
||||
}
|
||||
33
src/Framework/Shutdown/ShutdownHandlerInitializer.php
Normal file
33
src/Framework/Shutdown/ShutdownHandlerInitializer.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Shutdown;
|
||||
|
||||
use App\Framework\Attributes\Initializer;
|
||||
use App\Framework\DI\Container;
|
||||
use App\Framework\Logging\Logger;
|
||||
|
||||
/**
|
||||
* Initializes the shutdown handler system
|
||||
*
|
||||
* Registers the ShutdownHandlerManager in the DI container and sets up
|
||||
* PHP's shutdown function to use the centralized handler management system.
|
||||
*/
|
||||
final readonly class ShutdownHandlerInitializer
|
||||
{
|
||||
#[Initializer]
|
||||
public function __invoke(Container $container): void
|
||||
{
|
||||
// Create ShutdownHandlerManager instance
|
||||
$shutdownManager = new ShutdownHandlerManager(
|
||||
logger: $container->get(Logger::class)
|
||||
);
|
||||
|
||||
// Register as singleton in container
|
||||
$container->singleton(ShutdownHandlerManager::class, $shutdownManager);
|
||||
|
||||
// Register PHP shutdown function
|
||||
$shutdownManager->register();
|
||||
}
|
||||
}
|
||||
202
src/Framework/Shutdown/ShutdownHandlerManager.php
Normal file
202
src/Framework/Shutdown/ShutdownHandlerManager.php
Normal file
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Shutdown;
|
||||
|
||||
use App\Framework\Core\ValueObjects\Byte;
|
||||
use App\Framework\Events\EventDispatcher;
|
||||
use App\Framework\Logging\Logger;
|
||||
use DateTimeImmutable;
|
||||
|
||||
/**
|
||||
* Manages application shutdown handlers with priority-based execution
|
||||
*
|
||||
* Provides a centralized system for registering and executing shutdown handlers,
|
||||
* ensuring proper cleanup even during fatal errors. Handlers are executed in
|
||||
* priority order (highest first) with isolation to prevent cascading failures.
|
||||
*/
|
||||
final class ShutdownHandlerManager
|
||||
{
|
||||
/** @var array<int, array<callable>> Priority-based handler registry */
|
||||
private array $handlers = [];
|
||||
|
||||
private bool $registered = false;
|
||||
|
||||
public function __construct(
|
||||
private readonly Logger $logger,
|
||||
private readonly ?EventDispatcher $eventDispatcher = null
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Register a shutdown handler with specified priority
|
||||
*
|
||||
* @param callable $handler Shutdown handler to execute
|
||||
* @param int $priority Execution priority (higher = earlier, default: 0)
|
||||
*/
|
||||
public function registerHandler(callable $handler, int $priority = 0): void
|
||||
{
|
||||
if (!isset($this->handlers[$priority])) {
|
||||
$this->handlers[$priority] = [];
|
||||
}
|
||||
|
||||
$this->handlers[$priority][] = $handler;
|
||||
|
||||
// Sort handlers by priority (descending)
|
||||
krsort($this->handlers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register this manager as PHP's shutdown function
|
||||
*
|
||||
* Should be called during application bootstrap. Can only be registered once.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
if ($this->registered) {
|
||||
return;
|
||||
}
|
||||
|
||||
register_shutdown_function([$this, 'handleShutdown']);
|
||||
$this->registered = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main shutdown handler - executes all registered handlers
|
||||
*
|
||||
* Called automatically by PHP during shutdown. Handles both normal shutdowns
|
||||
* and fatal errors with appropriate logging and event dispatch.
|
||||
*/
|
||||
public function handleShutdown(): void
|
||||
{
|
||||
$error = error_get_last();
|
||||
$memoryUsage = Byte::fromBytes(memory_get_usage(true));
|
||||
$peakMemoryUsage = Byte::fromBytes(memory_get_peak_usage(true));
|
||||
|
||||
// Create shutdown event
|
||||
$shutdownEvent = new ShutdownEvent(
|
||||
error: $error,
|
||||
memoryUsage: $memoryUsage,
|
||||
peakMemoryUsage: $peakMemoryUsage,
|
||||
occurredAt: new DateTimeImmutable()
|
||||
);
|
||||
|
||||
// Log fatal errors to OWASP Security Logger
|
||||
if ($shutdownEvent->isFatalError()) {
|
||||
$this->logFatalError($shutdownEvent);
|
||||
}
|
||||
|
||||
// Execute registered handlers in priority order
|
||||
$this->executeHandlers($shutdownEvent);
|
||||
|
||||
// Dispatch shutdown event for cleanup
|
||||
$this->dispatchShutdownEvent($shutdownEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute all registered handlers in priority order
|
||||
*/
|
||||
private function executeHandlers(ShutdownEvent $event): void
|
||||
{
|
||||
foreach ($this->handlers as $priority => $handlers) {
|
||||
foreach ($handlers as $handler) {
|
||||
try {
|
||||
$handler($event);
|
||||
} catch (\Throwable $e) {
|
||||
// Isolate handler failures - log but continue
|
||||
$this->logHandlerFailure($e, $priority);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch shutdown event to event system
|
||||
*
|
||||
* Uses fallback to prevent cascading failures during shutdown
|
||||
*/
|
||||
private function dispatchShutdownEvent(ShutdownEvent $event): void
|
||||
{
|
||||
// EventDispatcher is optional - skip if not available
|
||||
if ($this->eventDispatcher === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->eventDispatcher->dispatch($event);
|
||||
} catch (\Throwable $e) {
|
||||
// Fallback: Log to error_log if event dispatch fails
|
||||
error_log(sprintf(
|
||||
'[ShutdownHandlerManager] Event dispatch failed: %s',
|
||||
$e->getMessage()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log fatal error using structured logging with SecurityContext
|
||||
*/
|
||||
private function logFatalError(ShutdownEvent $event): void
|
||||
{
|
||||
try {
|
||||
// Create SecurityContext for fatal error
|
||||
$securityContext = new \App\Framework\Logging\ValueObjects\SecurityContext(
|
||||
eventId: 'SYSTEM_FATAL_ERROR',
|
||||
level: \App\Framework\Exception\SecurityLogLevel::FATAL,
|
||||
description: sprintf(
|
||||
'Fatal error: %s in %s:%d',
|
||||
$event->getErrorMessage(),
|
||||
$event->getErrorFile(),
|
||||
$event->getErrorLine()
|
||||
),
|
||||
category: 'system',
|
||||
requiresAlert: true,
|
||||
eventData: [
|
||||
'error_type' => $event->getErrorTypeName(),
|
||||
'error_message' => $event->getErrorMessage(),
|
||||
'error_file' => $event->getErrorFile(),
|
||||
'error_line' => $event->getErrorLine(),
|
||||
'memory_usage' => $event->memoryUsage->toHumanReadable(),
|
||||
'peak_memory_usage' => $event->peakMemoryUsage->toHumanReadable(),
|
||||
'occurred_at' => $event->occurredAt->format('Y-m-d H:i:s')
|
||||
]
|
||||
);
|
||||
|
||||
// Log with LogContext and SecurityContext
|
||||
$this->logger->log(
|
||||
\App\Framework\Logging\LogLevel::CRITICAL,
|
||||
sprintf('Fatal shutdown error: %s', $event->getErrorTypeName()),
|
||||
\App\Framework\Logging\ValueObjects\LogContext::empty()
|
||||
->withSecurityContext($securityContext)
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
// Fallback: Use error_log if logging fails
|
||||
error_log(sprintf(
|
||||
'[ShutdownHandlerManager] Logging failed: %s | Original error: %s',
|
||||
$e->getMessage(),
|
||||
$event->getErrorMessage()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log handler execution failure
|
||||
*/
|
||||
private function logHandlerFailure(\Throwable $exception, int $priority): void
|
||||
{
|
||||
try {
|
||||
$this->logger->error('[ShutdownHandlerManager] Handler failed', [
|
||||
'priority' => $priority,
|
||||
'error' => $exception->getMessage(),
|
||||
'trace' => $exception->getTraceAsString()
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
// Final fallback: error_log
|
||||
error_log(sprintf(
|
||||
'[ShutdownHandlerManager] Handler failed (priority %d): %s',
|
||||
$priority,
|
||||
$exception->getMessage()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user