feat: CI/CD pipeline setup complete - Ansible playbooks updated, secrets configured, workflow ready

This commit is contained in:
2025-10-31 01:39:24 +01:00
parent 55c04e4fd0
commit e26eb2aa12
601 changed files with 44184 additions and 32477 deletions

View 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;
}
}

View 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();
}
}

View 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()
));
}
}
}