fix(Discovery): Add comprehensive debug logging for router initialization

- Add initializer count logging in DiscoveryServiceBootstrapper
- Add route structure analysis in RouterSetup
- Add request parameter logging in HttpRouter
- Update PHP production config for better OPcache handling
- Fix various config and error handling improvements
This commit is contained in:
2025-10-27 22:23:18 +01:00
parent e326e3d6c6
commit 70e45fb56e
56 changed files with 1519 additions and 355 deletions

View File

@@ -12,9 +12,7 @@ final class RequestContext
private ?string $protocol,
private ?string $port,
private ?string $requestUri,
private ?string $requestMethod,
private ?string $region,
private ?string $geo
private ?string $requestMethod
) {
}
@@ -26,9 +24,7 @@ final class RequestContext
isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http',
$_SERVER['SERVER_PORT'] ?? null,
$_SERVER['REQUEST_URI'] ?? null,
$_SERVER['REQUEST_METHOD'] ?? null,
$_ENV['AWS_REGION'] ?? $_ENV['REGION'] ?? null,
$_ENV['GEO_REGION'] ?? null
$_SERVER['REQUEST_METHOD'] ?? null
);
}
@@ -61,14 +57,4 @@ final class RequestContext
{
return $this->requestMethod;
}
public function getRegion(): ?string
{
return $this->region;
}
public function getGeo(): ?string
{
return $this->geo;
}
}

View File

@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace App\Framework\Analytics;
use App\Framework\Config\Environment;
/**
* Analytics-Konfiguration
*/
@@ -28,20 +30,20 @@ final readonly class AnalyticsConfig
) {
}
public static function fromEnvironment(): self
public static function fromEnvironment(Environment $env): self
{
return new self(
enabled: filter_var($_ENV['ANALYTICS_ENABLED'] ?? true, FILTER_VALIDATE_BOOLEAN),
samplingRate: (float)($_ENV['ANALYTICS_SAMPLING_RATE'] ?? 1.0),
securityAnalyticsEnabled: filter_var($_ENV['SECURITY_ANALYTICS_ENABLED'] ?? true, FILTER_VALIDATE_BOOLEAN),
dataPath: $_ENV['ANALYTICS_DATA_PATH'] ?? '/var/www/html/storage/analytics',
bufferSize: (int)($_ENV['ANALYTICS_BUFFER_SIZE'] ?? 1000),
retentionDays: (int)($_ENV['ANALYTICS_RETENTION_DAYS'] ?? 365),
trackPageViews: filter_var($_ENV['ANALYTICS_TRACK_PAGE_VIEWS'] ?? true, FILTER_VALIDATE_BOOLEAN),
trackApiCalls: filter_var($_ENV['ANALYTICS_TRACK_API_CALLS'] ?? true, FILTER_VALIDATE_BOOLEAN),
trackUserActions: filter_var($_ENV['ANALYTICS_TRACK_USER_ACTIONS'] ?? true, FILTER_VALIDATE_BOOLEAN),
trackErrors: filter_var($_ENV['ANALYTICS_TRACK_ERRORS'] ?? true, FILTER_VALIDATE_BOOLEAN),
trackPerformance: filter_var($_ENV['ANALYTICS_TRACK_PERFORMANCE'] ?? true, FILTER_VALIDATE_BOOLEAN),
enabled: $env->getBool('ANALYTICS_ENABLED', true),
samplingRate: $env->getFloat('ANALYTICS_SAMPLING_RATE', 1.0),
securityAnalyticsEnabled: $env->getBool('SECURITY_ANALYTICS_ENABLED', true),
dataPath: $env->getString('ANALYTICS_DATA_PATH', '/var/www/html/storage/analytics'),
bufferSize: $env->getInt('ANALYTICS_BUFFER_SIZE', 1000),
retentionDays: $env->getInt('ANALYTICS_RETENTION_DAYS', 365),
trackPageViews: $env->getBool('ANALYTICS_TRACK_PAGE_VIEWS', true),
trackApiCalls: $env->getBool('ANALYTICS_TRACK_API_CALLS', true),
trackUserActions: $env->getBool('ANALYTICS_TRACK_USER_ACTIONS', true),
trackErrors: $env->getBool('ANALYTICS_TRACK_ERRORS', true),
trackPerformance: $env->getBool('ANALYTICS_TRACK_PERFORMANCE', true),
);
}
}

View File

@@ -4,13 +4,14 @@ declare(strict_types=1);
namespace App\Framework\Config;
use App\Framework\Core\ValueObjects\Version;
use App\Framework\DateTime\Timezone;
final readonly class AppConfig
{
public function __construct(
public string $name = 'Framework App',
public string $version = '1.0.0',
public Version $version = new Version(1, 0, 0),
public string $environment = 'production',
public bool $debug = false,
public Timezone $timezone = Timezone::EuropeBerlin,
@@ -43,4 +44,9 @@ final readonly class AppConfig
{
return $this->debug || $this->type->isDebugEnabled();
}
public function isDebug(): bool
{
return $this->isDebugEnabled();
}
}

View File

@@ -41,7 +41,8 @@ final readonly class EncryptedEnvLoader
}
// Load environment-specific files
$appEnv = $variables['APP_ENV'] ?? $_ENV['APP_ENV'] ?? 'development';
// Priority: 1. Already loaded from .env file, 2. Default to development
$appEnv = $variables['APP_ENV'] ?? 'development';
$envSpecificFile = $baseDir->join(".env.{$appEnv}");
if ($envSpecificFile->exists()) {
$envSpecificVariables = $this->parseEnvFile($envSpecificFile);

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Framework\Config;
use App\Framework\Config\External\ExternalApiConfig;
use App\Framework\Core\ValueObjects\Version;
use App\Framework\Database\Config\DatabaseConfig;
use App\Framework\Database\Config\DatabaseConfigInitializer;
use App\Framework\DateTime\Timezone;
@@ -51,10 +52,11 @@ final readonly class TypedConfigInitializer
private function createAppConfig(): AppConfig
{
$environmentType = EnvironmentType::fromEnvironment($this->env);
$versionString = $this->env->getString('APP_VERSION', '1.0.0');
return new AppConfig(
name: $this->env->getString(EnvKey::APP_NAME, 'Framework App'),
version: $this->env->getString('APP_VERSION', '1.0.0'),
version: Version::fromString($versionString),
environment: $this->env->getString(
key: EnvKey::APP_ENV,
default: 'production'

View File

@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace App\Framework\Console\Analytics\Middleware;
use App\Framework\Config\AppConfig;
use App\Framework\Config\Environment;
use App\Framework\Console\Analytics\Repository\CommandUsageRepository;
use App\Framework\Console\Analytics\ValueObjects\CommandUsageMetric;
use App\Framework\Console\ExitCode;
@@ -17,6 +19,8 @@ final readonly class AnalyticsCollectionMiddleware implements ConsoleMiddleware
{
public function __construct(
private CommandUsageRepository $repository,
private AppConfig $appConfig,
private Environment $environment,
private bool $enabled = true
) {
}
@@ -92,7 +96,7 @@ final readonly class AnalyticsCollectionMiddleware implements ConsoleMiddleware
{
// In a real implementation, this would extract user ID from context
// Could be from environment variables, session data, etc.
return $_ENV['CONSOLE_USER_ID'] ?? null;
return $this->environment->getString('CONSOLE_USER_ID');
}
private function collectMetadata(ConsoleInput $input, ExitCode $exitCode): array
@@ -103,13 +107,9 @@ final readonly class AnalyticsCollectionMiddleware implements ConsoleMiddleware
'php_version' => PHP_VERSION,
'memory_peak' => memory_get_peak_usage(true),
'exit_code_name' => $exitCode->name,
'environment' => $this->appConfig->type->value,
];
// Add environment context
if (isset($_ENV['APP_ENV'])) {
$metadata['environment'] = $_ENV['APP_ENV'];
}
// Add process info
if (function_exists('posix_getpid')) {
$metadata['process_id'] = posix_getpid();

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Framework\Console\Layout\Commands;
use App\Framework\Config\AppConfig;
use App\Framework\Console\Attributes\ConsoleCommand;
use App\Framework\Console\ExitCode;
use App\Framework\Console\Input\ConsoleInput;
@@ -13,6 +14,10 @@ use App\Framework\Console\Output\ConsoleOutput;
final readonly class LayoutTestCommand
{
public function __construct(
private AppConfig $appConfig
) {
}
#[ConsoleCommand(
name: 'layout:test',
description: 'Test responsive terminal layout features'
@@ -48,8 +53,8 @@ final readonly class LayoutTestCommand
'Framework Version' => '1.0.0',
'PHP Version' => PHP_VERSION,
'Memory Usage' => memory_get_usage(true) . ' bytes',
'Environment' => $_ENV['APP_ENV'] ?? 'development',
'Debug Mode' => 'Enabled',
'Environment' => $this->appConfig->type->value,
'Debug Mode' => $this->appConfig->isDebug() ? 'Enabled' : 'Disabled',
];
$responsiveOutput->writeKeyValue($data);

View File

@@ -4,8 +4,7 @@ declare(strict_types=1);
namespace App\Framework\Context;
use App\Framework\Config\Environment;
use App\Framework\Config\EnvKey;
use App\Framework\Config\AppConfig;
use App\Framework\Http\ServerEnvironment;
final readonly class ExecutionContext
@@ -68,10 +67,8 @@ final readonly class ExecutionContext
], $this->metadata);
}
public static function detect(?Environment $environment = null): self
public static function detect(?AppConfig $appConfig = null): self
{
#$environment->get(EnvKey::APP_ENV);
// Debug logging
#error_log("ExecutionContext::detect() - SAPI: " . php_sapi_name());
#error_log("ExecutionContext::detect() - REQUEST_METHOD: " . ($_SERVER['REQUEST_METHOD'] ?? 'not set'));
@@ -80,7 +77,7 @@ final readonly class ExecutionContext
// Test Environment
if (defined('PHPUNIT_COMPOSER_INSTALL') ||
isset($_ENV['APP_ENV']) && $_ENV['APP_ENV'] === 'testing') {
($appConfig !== null && $appConfig->type->isTesting())) {
return new self(ContextType::TEST, ['detected_by' => 'phpunit_or_env']);
}

View File

@@ -53,11 +53,12 @@ final readonly class AppBootstrapper
// Make Environment available throughout the application
$this->container->instance(Environment::class, $env);
$this->container->instance(TypedConfiguration::class, new TypedConfigInitializer($env)($this->container));
$typedConfig = new TypedConfigInitializer($env)($this->container);
$this->container->instance(TypedConfiguration::class, $typedConfig);
// ExecutionContext detection sollte das erste sein, das nach dem Instanziieren des containers passiert. noch bevor dem bootstrap des containers.
$executionContext = ExecutionContext::detect($env);
$executionContext = ExecutionContext::detect($typedConfig->app);
$this->container->instance(ExecutionContext::class, $executionContext);
// Register MemoryMonitor as singleton

View File

@@ -213,6 +213,7 @@ final readonly class ContainerBootstrapper
private function autowire(Container $container): void
{
// Discovery service bootstrapping
error_log("🔧 CONTAINER BOOTSTRAP: autowire() starting Discovery");
$clock = $container->get(Clock::class);
$bootstrapper = new DiscoveryServiceBootstrapper($container, $clock);
$results = $bootstrapper->bootstrap();

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Framework\Database\Driver;
use App\Framework\Config\AppConfig;
use PDO;
/**
@@ -47,8 +48,7 @@ final readonly class PostgresDriver implements Driver
}
// Application name for pg_stat_activity monitoring
$appName = $_ENV['APP_NAME'] ?? 'custom-php-framework';
$parts['application_name'] = $appName;
$parts['application_name'] = 'php-app';
// Connect timeout (5 seconds default)
$parts['connect_timeout'] = '5';

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Framework\Database\Migration\Commands;
use App\Framework\Config\AppConfig;
use App\Framework\Console\ConsoleCommand;
use App\Framework\Console\ExitCode;
use App\Framework\Core\PathProvider;
@@ -20,7 +21,8 @@ final readonly class MakeMigrationFromDiffCommand
public function __construct(
private DatabaseManager $databaseManager,
private MigrationGenerator $migrationGenerator,
private PathProvider $pathProvider
private PathProvider $pathProvider,
private AppConfig $appConfig
) {
}
@@ -111,7 +113,7 @@ final readonly class MakeMigrationFromDiffCommand
return ExitCode::SUCCESS;
} catch (\Throwable $e) {
echo "Error creating migration: {$e->getMessage()}\n";
if (isset($_ENV['APP_DEBUG']) && $_ENV['APP_DEBUG']) {
if ($this->appConfig->isDebug()) {
echo $e->getTraceAsString() . "\n";
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Framework\Database\Migration\Services;
use App\Framework\Config\AppConfig;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\Migration\Migration;
@@ -18,7 +19,8 @@ final readonly class MigrationValidator
{
public function __construct(
private ConnectionInterface $connection,
private DatabasePlatform $platform
private DatabasePlatform $platform,
private AppConfig $appConfig
) {
}
@@ -309,7 +311,7 @@ final readonly class MigrationValidator
private function getEnvironmentDetails(): array
{
return [
'app_env' => $_ENV['APP_ENV'] ?? 'unknown',
'app_env' => $this->appConfig->type->value,
'php_version' => PHP_VERSION,
'server_name' => $_SERVER['SERVER_NAME'] ?? 'unknown',
'database_driver' => $this->platform->getName(),

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Framework\Database\Schema\Commands;
use App\Framework\Config\AppConfig;
use App\Framework\Console\ConsoleCommand;
use App\Framework\Console\ExitCode;
use App\Framework\Database\DatabaseManager;
@@ -15,7 +16,8 @@ use App\Framework\Database\Schema\Comparison\SchemaComparator;
final readonly class SchemaDiffCommand
{
public function __construct(
private DatabaseManager $databaseManager
private DatabaseManager $databaseManager,
private AppConfig $appConfig
) {
}
@@ -98,7 +100,7 @@ final readonly class SchemaDiffCommand
return ExitCode::SUCCESS;
} catch (\Throwable $e) {
echo "Error comparing schemas: {$e->getMessage()}\n";
if (isset($_ENV['APP_DEBUG']) && $_ENV['APP_DEBUG']) {
if ($this->appConfig->isDebug()) {
echo $e->getTraceAsString() . "\n";
}

View File

@@ -48,14 +48,23 @@ final readonly class DiscoveryServiceBootstrapper
$currentContext = ExecutionContext::detect();
$contextString = $currentContext->getType()->value;
// TEMPORARY DEBUG LOGGING
error_log("🔍 DISCOVERY DEBUG: Context detected = {$contextString}");
error_log("🔍 DISCOVERY DEBUG: Source path = " . $pathProvider->getSourcePath());
// Direkter Cache-Check mit expliziter toArray/fromArray Serialisierung
$defaultPaths = [$pathProvider->getSourcePath()];
$cacheKey = DiscoveryCacheIdentifiers::fullDiscoveryKey($defaultPaths, $contextString);
error_log("🔍 DISCOVERY DEBUG: Cache key = {$cacheKey->toString()}");
$cachedItem = $cache->get($cacheKey);
error_log("🔍 DISCOVERY DEBUG: Cache hit = " . ($cachedItem->isHit ? 'YES' : 'NO'));
if ($cachedItem->isHit) {
error_log("🔍 DISCOVERY DEBUG: Loading from cache...");
// Ensure DiscoveryRegistry class is loaded before attempting deserialization
if (! class_exists(DiscoveryRegistry::class, true)) {
$cachedRegistry = null;
@@ -82,6 +91,9 @@ final readonly class DiscoveryServiceBootstrapper
}
if ($cachedRegistry !== null && ! $cachedRegistry->isEmpty()) {
$routeCount = count($cachedRegistry->attributes->get(\App\Framework\Attributes\Route::class));
error_log("🔍 DISCOVERY DEBUG: Cached registry loaded - Route count: {$routeCount}");
$this->container->singleton(DiscoveryRegistry::class, $cachedRegistry);
// Process DefaultImplementation attributes first (before Initializers)
@@ -97,10 +109,18 @@ final readonly class DiscoveryServiceBootstrapper
}
// Fallback: Vollständige Discovery durchführen
error_log("🔍 DISCOVERY DEBUG: Performing fresh discovery...");
$results = $this->performBootstrap($pathProvider, $cache, $discoveryConfig);
error_log("🔍 DISCOVERY DEBUG: Discovery completed - isEmpty: " . ($results->isEmpty() ? 'YES' : 'NO'));
// Nach der Discovery explizit in unserem eigenen Cache-Format speichern
$consoleCommandCount = count($results->attributes->get(\App\Framework\Console\ConsoleCommand::class));
$routeCount = count($results->attributes->get(\App\Framework\Attributes\Route::class));
$initializerCount = count($results->attributes->get(\App\Framework\DI\Initializer::class));
error_log("🔍 DISCOVERY DEBUG: Found {$routeCount} routes");
error_log("🔍 DISCOVERY DEBUG: Found {$consoleCommandCount} console commands");
error_log("🔍 DISCOVERY DEBUG: Found {$initializerCount} initializers");
// Only cache if we found meaningful results
// An empty discovery likely indicates initialization timing issues

View File

@@ -37,7 +37,7 @@ final readonly class ErrorEvent
/**
* Creates ErrorEvent from ErrorHandlerContext
*/
public static function fromErrorHandlerContext(ErrorHandlerContext $context, \App\Framework\DateTime\Clock $clock): self
public static function fromErrorHandlerContext(ErrorHandlerContext $context, \App\Framework\DateTime\Clock $clock, bool $isDebug = false): self
{
return new self(
id: new Ulid($clock),
@@ -54,7 +54,7 @@ final readonly class ErrorEvent
userId: $context->request->userId ?? null,
clientIp: $context->request->clientIp,
isSecurityEvent: $context->exception->metadata['security_event'] ?? false,
stackTrace: self::extractStackTrace($context),
stackTrace: self::extractStackTrace($context, $isDebug),
userAgent: $context->request->userAgent,
);
}
@@ -263,10 +263,10 @@ final readonly class ErrorEvent
};
}
private static function extractStackTrace(ErrorHandlerContext $context): ?string
private static function extractStackTrace(ErrorHandlerContext $context, bool $isDebug = false): ?string
{
// Don't include stack traces for security events in production
if (($context->exception->metadata['security_event'] ?? false) && ! ($_ENV['APP_DEBUG'] ?? false)) {
if (($context->exception->metadata['security_event'] ?? false) && ! $isDebug) {
return null;
}

View File

@@ -51,8 +51,13 @@ final readonly class ErrorHandler
?SecurityEventHandler $securityHandler = null
) {
$this->isDebugMode = $isDebugMode ?? $this->getDebugModeFromEnvironment();
$this->logger = new ErrorLogger($logger);
$this->securityHandler = $securityHandler ?? SecurityEventHandler::createDefault($logger);
// Get Logger and AppConfig from container if not provided
$logger = $logger ?? $container->get(Logger::class);
$appConfig = $container->get(\App\Framework\Config\AppConfig::class);
$this->logger = new ErrorLogger($logger, $appConfig);
$this->securityHandler = $securityHandler ?? SecurityEventHandler::createDefault($logger, $appConfig);
}
public function register(): void
@@ -456,9 +461,12 @@ final readonly class ErrorHandler
}
}
// Get AppConfig from container
$appConfig = $this->container->get(\App\Framework\Config\AppConfig::class);
$htmlRenderer = $templateRenderer ?
new ErrorTemplateRenderer($templateRenderer) :
new ErrorTemplateRenderer(new DummyTemplateRenderer());
new ErrorTemplateRenderer($templateRenderer, $appConfig) :
new ErrorTemplateRenderer(new DummyTemplateRenderer(), $appConfig);
$apiRenderer = new ApiErrorRenderer();

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Framework\ErrorHandling;
use App\Framework\Config\AppConfig;
use App\Framework\Exception\Core\ErrorSeverity;
use App\Framework\Exception\ErrorHandlerContext;
use App\Framework\Logging\Logger;
@@ -16,7 +17,8 @@ use App\Framework\Logging\ValueObjects\LogContext;
final readonly class ErrorLogger
{
public function __construct(
private ?Logger $logger = null
private Logger $logger,
private AppConfig $appConfig
) {
}
@@ -42,13 +44,8 @@ final readonly class ErrorLogger
'additionalData' => $context->additionalData,
];
if ($this->logger !== null) {
$logLevel = $this->mapErrorSeverityToLogLevel($context->level);
$this->logger->log($logLevel, $message, $contextData);
} else {
// Fallback auf error_log
$this->logToErrorLog($context);
}
$logLevel = $this->mapErrorSeverityToLogLevel($context->level);
$this->logger->log($logLevel, $message, $contextData);
}
/**
@@ -73,23 +70,18 @@ final readonly class ErrorLogger
);
if ($this->logger !== null) {
$logLevel = $this->determineLogLevel($context);
$logLevel = $this->determineLogLevel($context);
// Erstelle LogContext mit strukturierten Daten
$logContext = LogContext::withData($logData)
->addTags('error_handler', 'framework');
// Erstelle LogContext mit strukturierten Daten
$logContext = LogContext::withData($logData)
->addTags('error_handler', 'framework');
// Füge Metadaten-Tags hinzu
if ($context->exception->metadata['security_event'] ?? false) {
$logContext = $logContext->addTags('security');
}
$this->logger->log($logLevel, $message, $logContext);
} else {
// Fallback auf error_log
$this->logHandlerContextToErrorLog($context);
// Füge Metadaten-Tags hinzu
if ($context->exception->metadata['security_event'] ?? false) {
$logContext = $logContext->addTags('security');
}
$this->logger->log($logLevel, $message, $logContext);
}
/**
@@ -97,18 +89,13 @@ final readonly class ErrorLogger
*/
private function logSecurityEvent(ErrorHandlerContext $context): void
{
$securityLog = $context->toSecurityEventFormat($_ENV['APP_NAME'] ?? 'app');
$securityLog = $context->toSecurityEventFormat($this->appConfig->name);
if ($this->logger !== null) {
// Verwende LogContext für strukturiertes Security Event Logging
$logContext = LogContext::withData($securityLog)
->addTags('security_event', 'owasp');
// Verwende LogContext für strukturiertes Security Event Logging
$logContext = LogContext::withData($securityLog)
->addTags('security_event', 'owasp');
$this->logger->log(LogLevel::WARNING, 'Security Event Detected', $logContext);
} else {
// Fallback nur als letzte Option
error_log('SECURITY_EVENT: ' . json_encode($securityLog, JSON_UNESCAPED_SLASHES));
}
$this->logger->log(LogLevel::WARNING, 'Security Event Detected', $logContext);
}
/**
@@ -164,51 +151,6 @@ final readonly class ErrorLogger
};
}
/**
* Fallback-Methode für ErrorHandlerContext ohne Framework Logger
*/
private function logHandlerContextToErrorLog(ErrorHandlerContext $context): void
{
$message = sprintf(
'[%s] [%s] %s: %s',
date('Y-m-d H:i:s'),
$context->exception->metadata['error_level'] ?? 'ERROR',
$context->exception->component ?? 'Application',
$this->extractExceptionMessage($context)
);
error_log($message);
// Security Events auch separat loggen
if ($context->exception->metadata['security_event'] ?? false) {
$securityLog = $context->toSecurityEventJson($_ENV['APP_NAME'] ?? 'app');
error_log('SECURITY_EVENT: ' . $securityLog);
}
}
/**
* Fallback-Methode für Logging ohne Framework Logger (Legacy)
*/
private function logToErrorLog(ErrorContext $context): void
{
$exception = $context->exception;
$message = sprintf(
'[%s] [%s] %s: %s in %s:%d',
date('Y-m-d H:i:s'),
$context->level->name,
get_class($exception),
$exception->getMessage(),
$exception->getFile(),
$exception->getLine()
);
error_log($message);
// Bei kritischen Fehlern auch den Stacktrace loggen
if ($context->level === ErrorSeverity::CRITICAL) {
error_log("Stack trace: \n" . $exception->getTraceAsString());
}
}
/**
* Mappt ErrorSeverity auf Framework LogLevel

View File

@@ -16,7 +16,7 @@ use Throwable;
final readonly class SecurityEventHandler
{
public function __construct(
private SecurityEventLogger $securityLogger,
private ?SecurityEventLogger $securityLogger,
private ?SecurityAlertManager $alertManager = null
) {
}
@@ -28,6 +28,11 @@ final readonly class SecurityEventHandler
SecurityException $exception,
?MiddlewareContext $context = null
): void {
// Skip if no logger available
if ($this->securityLogger === null) {
return;
}
try {
// Erstelle ErrorHandlerContext für OWASP-Format
$errorHandlerContext = $this->createErrorHandlerContext($exception, $context);
@@ -71,7 +76,7 @@ final readonly class SecurityEventHandler
): void {
if ($this->alertManager) {
$this->alertManager->sendAlert($exception, $context);
} else {
} elseif ($this->securityLogger !== null) {
// Fallback: Logge als kritisches Event
$this->securityLogger->logCriticalAlert($exception, $context);
}
@@ -135,9 +140,16 @@ final readonly class SecurityEventHandler
/**
* Factory-Methode mit Standard-Konfiguration
*/
public static function createDefault(?Logger $logger = null): self
public static function createDefault(?Logger $logger = null, ?\App\Framework\Config\AppConfig $appConfig = null): self
{
$securityLogger = new SecurityEventLogger($logger);
// If AppConfig not provided, we can't create SecurityEventLogger properly
// This is a temporary solution - ideally AppConfig should always be provided
if ($logger === null || $appConfig === null) {
// Return a minimal handler without SecurityEventLogger
return new self(null, null);
}
$securityLogger = new SecurityEventLogger($logger, $appConfig);
$alertManager = null; // Kann später konfiguriert werden
return new self($securityLogger, $alertManager);

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Framework\ErrorHandling;
use App\Framework\Config\AppConfig;
use App\Framework\Exception\ErrorHandlerContext;
use App\Framework\Exception\SecurityException;
use App\Framework\Exception\SecurityLogLevel;
@@ -16,8 +17,8 @@ use App\Framework\Logging\LogLevel;
final readonly class SecurityEventLogger
{
public function __construct(
private ?Logger $logger = null,
private string $applicationId = 'app'
private Logger $logger,
private AppConfig $appConfig
) {
}
@@ -33,26 +34,21 @@ final readonly class SecurityEventLogger
// OWASP-konformes Log generieren
$owaspLog = $this->createOWASPLog($exception, $context);
if ($this->logger) {
// Strukturiertes Logging über Framework Logger
$frameworkLogLevel = $this->mapSecurityLevelToFrameworkLevel($securityEvent->getLogLevel());
// Strukturiertes Logging über Framework Logger
$frameworkLogLevel = $this->mapSecurityLevelToFrameworkLevel($securityEvent->getLogLevel());
$this->logger->log(
$frameworkLogLevel,
$securityEvent->getDescription(),
[
'security_event' => $securityEvent->getEventIdentifier(),
'security_category' => $securityEvent->getCategory(),
'requires_alert' => $securityEvent->requiresAlert(),
'owasp_format' => $owaspLog,
'event_data' => $securityEvent->toArray(),
'context_data' => $context->forLogging(),
]
);
} else {
// Fallback auf error_log
$this->logToErrorLog($owaspLog);
}
$this->logger->log(
$frameworkLogLevel,
$securityEvent->getDescription(),
[
'security_event' => $securityEvent->getEventIdentifier(),
'security_category' => $securityEvent->getCategory(),
'requires_alert' => $securityEvent->requiresAlert(),
'owasp_format' => $owaspLog,
'event_data' => $securityEvent->toArray(),
'context_data' => $context->forLogging(),
]
);
}
/**
@@ -64,15 +60,11 @@ final readonly class SecurityEventLogger
): void {
$alertData = $this->createAlertData($exception, $context);
if ($this->logger) {
$this->logger->log(
LogLevel::CRITICAL,
'SECURITY_ALERT: ' . $exception->getSecurityEvent()->getEventIdentifier(),
$alertData
);
} else {
error_log('SECURITY_ALERT: ' . json_encode($alertData));
}
$this->logger->log(
LogLevel::CRITICAL,
'SECURITY_ALERT: ' . $exception->getSecurityEvent()->getEventIdentifier(),
$alertData
);
}
/**
@@ -86,7 +78,7 @@ final readonly class SecurityEventLogger
return [
'datetime' => date('c'),
'appid' => $this->applicationId,
'appid' => $this->appConfig->name,
'event' => $securityEvent->getEventIdentifier(),
'level' => $securityEvent->getLogLevel()->value,
'description' => $securityEvent->getDescription(),
@@ -98,8 +90,6 @@ final readonly class SecurityEventLogger
'port' => $context->request->port,
'request_uri' => $context->request->requestUri,
'request_method' => $context->request->requestMethod,
'region' => $_ENV['AWS_REGION'] ?? 'unknown',
'geo' => $_ENV['GEO_LOCATION'] ?? 'unknown',
'category' => $securityEvent->getCategory(),
'requires_alert' => $securityEvent->requiresAlert(),
];
@@ -127,15 +117,6 @@ final readonly class SecurityEventLogger
];
}
/**
* Fallback-Logging über error_log
*/
private function logToErrorLog(array $owaspLog): void
{
$logMessage = 'SECURITY_EVENT: ' . json_encode($owaspLog, JSON_UNESCAPED_SLASHES);
error_log($logMessage);
}
/**
* Mappt SecurityLogLevel auf Framework LogLevel
*/
@@ -150,15 +131,4 @@ final readonly class SecurityEventLogger
SecurityLogLevel::FATAL => LogLevel::CRITICAL,
};
}
/**
* Factory-Methode für Standard-Konfiguration
*/
public static function create(?Logger $logger = null): self
{
return new self(
$logger,
$_ENV['APP_NAME'] ?? $_ENV['APP_ID'] ?? 'app'
);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Framework\ErrorHandling\View;
use App\Framework\Config\AppConfig;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\ErrorHandling\StackTrace;
@@ -23,6 +24,7 @@ final readonly class ErrorTemplateRenderer implements ErrorViewRendererInterface
{
public function __construct(
private TemplateRenderer $renderer,
private AppConfig $appConfig,
private string $debugTemplate = 'enhanced-debug',
private string $productionTemplate = 'production'
) {
@@ -65,7 +67,7 @@ final readonly class ErrorTemplateRenderer implements ErrorViewRendererInterface
'userAgent' => (string) ($context->request->userAgent ?? 'Unknown'),
'traceCount' => 0, // Will be updated below if trace is available
// Environment information
'environment' => $_ENV['APP_ENV'] ?? 'development',
'environment' => $this->appConfig->type->value,
'debugMode' => $isDebug ? 'Enabled' : 'Disabled',
'phpVersion' => PHP_VERSION,
'frameworkVersion' => '1.0.0-dev',

View File

@@ -48,7 +48,8 @@ final readonly class ErrorReport
public static function fromThrowable(
Throwable $throwable,
string $level = 'error',
array $context = []
array $context = [],
?string $environment = null
): self {
return new self(
id: self::generateId(),
@@ -60,7 +61,7 @@ final readonly class ErrorReport
line: $throwable->getLine(),
trace: $throwable->getTraceAsString(),
context: $context,
environment: $_ENV['APP_ENV'] ?? 'production',
environment: $environment ?? 'production',
serverInfo: self::getServerInfo()
);
}
@@ -72,7 +73,8 @@ final readonly class ErrorReport
string $level,
string $message,
array $context = [],
?Throwable $exception = null
?Throwable $exception = null,
?string $environment = null
): self {
return new self(
id: self::generateId(),
@@ -84,7 +86,7 @@ final readonly class ErrorReport
line: $exception?->getLine() ?? debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['line'] ?? 0,
trace: $exception?->getTraceAsString() ?? self::getCurrentTrace(),
context: $context,
environment: $_ENV['APP_ENV'] ?? 'production',
environment: $environment ?? 'production',
serverInfo: self::getServerInfo()
);
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Framework\ErrorReporting;
use App\Framework\Config\Environment;
use App\Framework\Database\ConnectionInterface;
use App\Framework\DateTime\Clock;
use App\Framework\DI\Container;
@@ -25,7 +26,8 @@ final readonly class ErrorReportingInitializer
#[Initializer]
public function initialize(Container $container): void
{
$enabled = (bool) ($_ENV['ERROR_REPORTING_ENABLED'] ?? true);
$env = $container->get(Environment::class);
$enabled = $env->getBool('ERROR_REPORTING_ENABLED', true);
// Storage
$container->bind(ErrorReportStorageInterface::class, function (Container $container) use ($enabled) {
@@ -55,6 +57,7 @@ final readonly class ErrorReportingInitializer
return new NullErrorReporter();
}
$env = $container->get(Environment::class);
$processors = [];
$filters = [];
@@ -68,15 +71,17 @@ final readonly class ErrorReportingInitializer
}
// Add environment-based filters
if (($_ENV['ERROR_REPORTING_FILTER_LEVELS'] ?? null)) {
$allowedLevels = explode(',', $_ENV['ERROR_REPORTING_FILTER_LEVELS']);
$filterLevels = $env->getString('ERROR_REPORTING_FILTER_LEVELS', '');
if ($filterLevels !== '') {
$allowedLevels = explode(',', $filterLevels);
$filters[] = function (ErrorReport $report) use ($allowedLevels) {
return in_array($report->level, $allowedLevels);
};
}
// Add environment filter for production
if (($_ENV['APP_ENV'] ?? 'production') === 'production') {
$appEnv = $env->getString('APP_ENV', 'production');
if ($appEnv === 'production') {
$filters[] = function (ErrorReport $report) {
// Don't report debug/info in production
return ! in_array($report->level, ['debug', 'info']);
@@ -88,7 +93,7 @@ final readonly class ErrorReportingInitializer
clock: $container->get(Clock::class),
logger: $container->has(Logger::class) ? $container->get(Logger::class) : null,
queue: $container->has(Queue::class) ? $container->get(Queue::class) : null,
asyncProcessing: (bool) ($_ENV['ERROR_REPORTING_ASYNC'] ?? true),
asyncProcessing: $env->getBool('ERROR_REPORTING_ASYNC', true),
processors: $processors,
filters: $filters
);
@@ -100,6 +105,7 @@ final readonly class ErrorReportingInitializer
throw new \RuntimeException('ErrorReporter is disabled. Use ErrorReporterInterface instead.');
}
$env = $container->get(Environment::class);
$processors = [];
$filters = [];
@@ -113,15 +119,17 @@ final readonly class ErrorReportingInitializer
}
// Add environment-based filters
if (($_ENV['ERROR_REPORTING_FILTER_LEVELS'] ?? null)) {
$allowedLevels = explode(',', $_ENV['ERROR_REPORTING_FILTER_LEVELS']);
$filterLevels = $env->getString('ERROR_REPORTING_FILTER_LEVELS', '');
if ($filterLevels !== '') {
$allowedLevels = explode(',', $filterLevels);
$filters[] = function (ErrorReport $report) use ($allowedLevels) {
return in_array($report->level, $allowedLevels);
};
}
// Add environment filter for production
if (($_ENV['APP_ENV'] ?? 'production') === 'production') {
$appEnv = $env->getString('APP_ENV', 'production');
if ($appEnv === 'production') {
$filters[] = function (ErrorReport $report) {
// Don't report debug/info in production
return ! in_array($report->level, ['debug', 'info']);
@@ -133,17 +141,17 @@ final readonly class ErrorReportingInitializer
clock: $container->get(Clock::class),
logger: $container->has(Logger::class) ? $container->get(Logger::class) : null,
queue: $container->has(Queue::class) ? $container->get(Queue::class) : null,
asyncProcessing: (bool) ($_ENV['ERROR_REPORTING_ASYNC'] ?? true),
asyncProcessing: $env->getBool('ERROR_REPORTING_ASYNC', true),
processors: $processors,
filters: $filters
);
});
// Middleware
$container->bind(ErrorReportingMiddleware::class, function (Container $container) {
$container->bind(ErrorReportingMiddleware::class, function (Container $container) use ($enabled) {
return new ErrorReportingMiddleware(
reporter: $container->get(ErrorReporter::class),
enabled: (bool) ($_ENV['ERROR_REPORTING_ENABLED'] ?? true)
enabled: $enabled
);
});

View File

@@ -145,8 +145,6 @@ final readonly class ErrorHandlerContext
'port' => $this->request->port,
'request_uri' => $this->request->requestUri,
'request_method' => $this->request->requestMethod,
'region' => $_ENV['AWS_REGION'] ?? 'unknown',
'geo' => $_ENV['GEO_LOCATION'] ?? 'unknown',
];
// Security-Event-spezifische Daten falls verfügbar

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Framework\Filesystem;
use App\Framework\Config\Environment;
use App\Framework\DI\Container;
use App\Framework\DI\Initializer;
use App\Framework\Filesystem\Serializers\CsvSerializer;
@@ -23,26 +24,28 @@ final readonly class FilesystemInitializer
public function initializeFilesystem(Container $container): void
{
// Filesystem Config
$container->singleton(FilesystemConfig::class, function () {
$container->singleton(FilesystemConfig::class, function (Container $container) {
$env = $container->get(Environment::class);
return new FilesystemConfig(
defaultStorage: $_ENV['FILESYSTEM_DEFAULT_STORAGE'] ?? 'local',
defaultStorage: $env->getString('FILESYSTEM_DEFAULT_STORAGE', 'local'),
storages: [
'local' => [
'type' => 'file',
'path' => $_ENV['FILESYSTEM_LOCAL_PATH'] ?? '/var/www/html/storage',
'path' => $env->getString('FILESYSTEM_LOCAL_PATH', '/var/www/html/storage'),
],
'temp' => [
'type' => 'file',
'path' => $_ENV['FILESYSTEM_TEMP_PATH'] ?? '/tmp',
'path' => $env->getString('FILESYSTEM_TEMP_PATH', '/tmp'),
],
'analytics' => [
'type' => 'file',
'path' => $_ENV['ANALYTICS_DATA_PATH'] ?? '/var/www/html/storage/analytics',
'path' => $env->getString('ANALYTICS_DATA_PATH', '/var/www/html/storage/analytics'),
],
],
enableCompression: filter_var($_ENV['FILESYSTEM_COMPRESSION'] ?? 'false', FILTER_VALIDATE_BOOLEAN),
enableAtomicWrites: filter_var($_ENV['FILESYSTEM_ATOMIC_WRITES'] ?? 'true', FILTER_VALIDATE_BOOLEAN),
enableFileLocking: filter_var($_ENV['FILESYSTEM_FILE_LOCKING'] ?? 'true', FILTER_VALIDATE_BOOLEAN)
enableCompression: $env->getBool('FILESYSTEM_COMPRESSION', false),
enableAtomicWrites: $env->getBool('FILESYSTEM_ATOMIC_WRITES', true),
enableFileLocking: $env->getBool('FILESYSTEM_FILE_LOCKING', true)
);
});
@@ -72,12 +75,13 @@ final readonly class FilesystemInitializer
});
// FileValidator - Default Validator (always cached for performance)
$container->singleton(FileValidator::class, function () {
$container->singleton(FileValidator::class, function (Container $container) {
$env = $container->get(Environment::class);
$validator = FileValidator::createDefault();
// Always use caching for optimal performance
// Disable only for debugging: FILESYSTEM_DISABLE_CACHE=true
$disableCache = filter_var($_ENV['FILESYSTEM_DISABLE_CACHE'] ?? 'false', FILTER_VALIDATE_BOOLEAN);
$disableCache = $env->getBool('FILESYSTEM_DISABLE_CACHE', false);
if ($disableCache) {
return $validator;
@@ -91,10 +95,11 @@ final readonly class FilesystemInitializer
});
// FileValidator - Upload Validator (always cached)
$container->singleton('filesystem.validator.upload', function () {
$container->singleton('filesystem.validator.upload', function (Container $container) {
$env = $container->get(Environment::class);
$validator = FileValidator::forUploads();
$disableCache = filter_var($_ENV['FILESYSTEM_DISABLE_CACHE'] ?? 'false', FILTER_VALIDATE_BOOLEAN);
$disableCache = $env->getBool('FILESYSTEM_DISABLE_CACHE', false);
if ($disableCache) {
return $validator;
@@ -104,10 +109,11 @@ final readonly class FilesystemInitializer
});
// FileValidator - Image Validator (always cached)
$container->singleton('filesystem.validator.image', function () {
$container->singleton('filesystem.validator.image', function (Container $container) {
$env = $container->get(Environment::class);
$validator = FileValidator::forImages();
$disableCache = filter_var($_ENV['FILESYSTEM_DISABLE_CACHE'] ?? 'false', FILTER_VALIDATE_BOOLEAN);
$disableCache = $env->getBool('FILESYSTEM_DISABLE_CACHE', false);
if ($disableCache) {
return $validator;
@@ -144,7 +150,8 @@ final readonly class FilesystemInitializer
{
// Always enable storage caching for optimal performance
// Disable only for debugging: FILESYSTEM_DISABLE_CACHE=true
$disableCache = filter_var($_ENV['FILESYSTEM_DISABLE_CACHE'] ?? 'false', FILTER_VALIDATE_BOOLEAN);
$env = $container->get(Environment::class);
$disableCache = $env->getBool('FILESYSTEM_DISABLE_CACHE', false);
$enableStorageCache = !$disableCache;
// Default Storage - nutzt PathProvider aus Container

View File

@@ -6,6 +6,7 @@ namespace App\Framework\Http;
use App\Framework\Cache\Cache;
use App\Framework\CircuitBreaker\CircuitBreaker;
use App\Framework\Config\Environment;
use App\Framework\DI\Container;
use App\Framework\Http\Exceptions\MiddlewareTimeoutException;
use App\Framework\Http\Metrics\MiddlewareMetricsCollector;
@@ -46,7 +47,18 @@ final readonly class MiddlewareInvoker
?MiddlewareMetricsCollector $metricsCollector = null
) {
$this->logger = $this->container->get(DefaultLogger::class);
$this->defaultTimeout = $defaultTimeout ?? (float)($_ENV['MIDDLEWARE_TIMEOUT'] ?? 5.0);
if ($defaultTimeout === null) {
try {
$env = $this->container->get(Environment::class);
$this->defaultTimeout = $env->getFloat('MIDDLEWARE_TIMEOUT', 5.0);
} catch (\Throwable) {
$this->defaultTimeout = 5.0;
}
} else {
$this->defaultTimeout = $defaultTimeout;
}
$this->middlewareTimeouts = $middlewareTimeouts;
$this->circuitBreaker = $circuitBreaker ?? new MiddlewareCircuitBreaker(
$this->container->get(CircuitBreaker::class)

View File

@@ -204,7 +204,7 @@ final readonly class MiddlewareManager implements MiddlewareManagerInterface
\App\Framework\Http\Session\SessionMiddleware::class,
// 2. Security und Rate Limiting
RateLimitMiddleware::class,
//RateLimitMiddleware::class,
#\App\Application\Security\Middleware\SecurityEventMiddleware::class,
// 3. Headers und CORS

View File

@@ -19,13 +19,11 @@ final readonly class RequestId
/**
* Erstellt eine neue Request-ID oder parsed eine bestehende
*
* @param string $secret Das Secret für die HMAC-Signatur (REQUIRED)
* @param string|null $combined Wenn nicht null, wird diese ID validiert und verwendet
* @param string $secret Das Secret für die HMAC-Signatur
*/
public function __construct(?string $combined = null, string $secret = '')
public function __construct(string $secret, ?string $combined = null)
{
// Secret über eine Umgebungsvariable beziehen, falls nicht angegeben
$secret = $secret ?: ($_ENV['APP_SECRET'] ?? 'default-secret-change-me');
if ($combined !== null && self::isValidFormat($combined)) {
// Bestehende ID parsen

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Framework\Http;
use App\Framework\Attributes\Singleton;
use App\Framework\Config\Environment;
/**
* Service zur Verwaltung der Request-ID für den aktuellen Request.
@@ -17,15 +18,22 @@ final class RequestIdGenerator
private ?RequestId $requestId = null;
private string $secret;
private readonly string $secret;
/**
* Initialisiert den RequestIdGenerator mit einem optionalen Secret
* Initialisiert den RequestIdGenerator mit Environment für Secret-Auflösung
*/
public function __construct(string $secret = '')
public function __construct(?Environment $env = null, string $secret = '')
{
// Secret über eine Umgebungsvariable beziehen, falls nicht angegeben
$this->secret = $secret ?: ($_ENV['APP_SECRET'] ?? 'default-secret-change-me');
// Fallback für BC: Wenn kein Environment übergeben wird und Secret angegeben ist
if ($secret !== '') {
$this->secret = $secret;
} elseif ($env !== null) {
$this->secret = $env->getString('APP_SECRET', 'default-secret-change-me');
} else {
// Final fallback für alte Verwendung ohne Environment
$this->secret = 'default-secret-change-me';
}
}
/**

View File

@@ -156,18 +156,13 @@ final readonly class SessionFingerprintConfig
/**
* Erstellt eine Konfiguration aus Umgebungsvariablen
*/
public static function fromEnvironment(): self
public static function fromEnvironment(\App\Framework\Config\Environment $env): self
{
$strictMode = filter_var(
$_ENV['SESSION_FINGERPRINT_STRICT'] ?? false,
FILTER_VALIDATE_BOOLEAN
);
$strictMode = $env->getBool('SESSION_FINGERPRINT_STRICT', false);
// Default threshold abhängig vom Modus
$defaultThreshold = $strictMode ? 1.0 : 0.7;
$threshold = isset($_ENV['SESSION_FINGERPRINT_THRESHOLD'])
? (float) $_ENV['SESSION_FINGERPRINT_THRESHOLD']
: $defaultThreshold;
$threshold = $env->getFloat('SESSION_FINGERPRINT_THRESHOLD', $defaultThreshold);
// Auto-Korrektur: Im strict mode MUSS threshold 1.0 sein
if ($strictMode && $threshold < 1.0) {
@@ -180,30 +175,12 @@ final readonly class SessionFingerprintConfig
// Überschreibe mit spezifischen Env-Vars wenn vorhanden
return new self(
strictMode: $strictMode,
userAgent: filter_var(
$_ENV['SESSION_FINGERPRINT_USER_AGENT'] ?? $config->userAgent,
FILTER_VALIDATE_BOOLEAN
),
acceptLanguage: filter_var(
$_ENV['SESSION_FINGERPRINT_ACCEPT_LANGUAGE'] ?? $config->acceptLanguage,
FILTER_VALIDATE_BOOLEAN
),
acceptEncoding: filter_var(
$_ENV['SESSION_FINGERPRINT_ACCEPT_ENCODING'] ?? $config->acceptEncoding,
FILTER_VALIDATE_BOOLEAN
),
ipPrefix: filter_var(
$_ENV['SESSION_FINGERPRINT_IP_PREFIX'] ?? $config->ipPrefix,
FILTER_VALIDATE_BOOLEAN
),
secChUa: filter_var(
$_ENV['SESSION_FINGERPRINT_SEC_CH_UA'] ?? $config->secChUa,
FILTER_VALIDATE_BOOLEAN
),
dnt: filter_var(
$_ENV['SESSION_FINGERPRINT_DNT'] ?? $config->dnt,
FILTER_VALIDATE_BOOLEAN
),
userAgent: $env->getBool('SESSION_FINGERPRINT_USER_AGENT', $config->userAgent),
acceptLanguage: $env->getBool('SESSION_FINGERPRINT_ACCEPT_LANGUAGE', $config->acceptLanguage),
acceptEncoding: $env->getBool('SESSION_FINGERPRINT_ACCEPT_ENCODING', $config->acceptEncoding),
ipPrefix: $env->getBool('SESSION_FINGERPRINT_IP_PREFIX', $config->ipPrefix),
secChUa: $env->getBool('SESSION_FINGERPRINT_SEC_CH_UA', $config->secChUa),
dnt: $env->getBool('SESSION_FINGERPRINT_DNT', $config->dnt),
similarityThreshold: $threshold,
);
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Framework\Http\Session;
use App\Framework\Config\Environment;
use App\Framework\Context\ContextType;
use App\Framework\Core\Events\EventDispatcher;
use App\Framework\DateTime\Clock;
@@ -55,7 +56,8 @@ final readonly class SessionInitializer
}
// Session Fingerprinting konfigurieren
$fingerprintConfig = SessionFingerprintConfig::fromEnvironment();
$env = $this->container->get(Environment::class);
$fingerprintConfig = SessionFingerprintConfig::fromEnvironment($env);
$fingerprint = new SessionFingerprint($fingerprintConfig);
// EventDispatcher optional laden
@@ -65,7 +67,8 @@ final readonly class SessionInitializer
}
// Cookie-Konfiguration basierend auf Umgebung
$isProduction = ($_ENV['APP_ENV'] ?? 'development') === 'production';
$appEnv = $env->getString('APP_ENV', 'development');
$isProduction = $appEnv === 'production';
$cookieConfig = $isProduction
? SessionCookieConfig::forProduction()
: SessionCookieConfig::forDevelopment();

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Framework\MachineLearning\ModelManagement;
use App\Framework\Config\Environment;
use App\Framework\Core\ValueObjects\Duration;
/**
@@ -53,21 +54,19 @@ final readonly class MLConfig
/**
* Create configuration from environment
*/
public static function fromEnvironment(array $env = []): self
public static function fromEnvironment(Environment $env): self
{
$getEnv = fn(string $key, mixed $default = null): mixed => $env[$key] ?? $_ENV[$key] ?? $default;
return new self(
monitoringEnabled: filter_var($getEnv('ML_MONITORING_ENABLED', true), FILTER_VALIDATE_BOOLEAN),
driftThreshold: (float) $getEnv('ML_DRIFT_THRESHOLD', 0.15),
performanceWindow: Duration::fromHours((int) $getEnv('ML_PERFORMANCE_WINDOW_HOURS', 24)),
autoTuningEnabled: filter_var($getEnv('ML_AUTO_TUNING_ENABLED', false), FILTER_VALIDATE_BOOLEAN),
predictionCacheTtl: Duration::fromSeconds((int) $getEnv('ML_PREDICTION_CACHE_TTL', 3600)),
modelCacheTtl: Duration::fromSeconds((int) $getEnv('ML_MODEL_CACHE_TTL', 7200)),
baselineUpdateInterval: Duration::fromSeconds((int) $getEnv('ML_BASELINE_UPDATE_INTERVAL', 86400)),
minPredictionsForDrift: (int) $getEnv('ML_MIN_PREDICTIONS_FOR_DRIFT', 100),
confidenceAlertThreshold: (float) $getEnv('ML_CONFIDENCE_ALERT_THRESHOLD', 0.65),
accuracyAlertThreshold: (float) $getEnv('ML_ACCURACY_ALERT_THRESHOLD', 0.75)
monitoringEnabled: $env->getBool('ML_MONITORING_ENABLED', true),
driftThreshold: $env->getFloat('ML_DRIFT_THRESHOLD', 0.15),
performanceWindow: Duration::fromHours($env->getInt('ML_PERFORMANCE_WINDOW_HOURS', 24)),
autoTuningEnabled: $env->getBool('ML_AUTO_TUNING_ENABLED', false),
predictionCacheTtl: Duration::fromSeconds($env->getInt('ML_PREDICTION_CACHE_TTL', 3600)),
modelCacheTtl: Duration::fromSeconds($env->getInt('ML_MODEL_CACHE_TTL', 7200)),
baselineUpdateInterval: Duration::fromSeconds($env->getInt('ML_BASELINE_UPDATE_INTERVAL', 86400)),
minPredictionsForDrift: $env->getInt('ML_MIN_PREDICTIONS_FOR_DRIFT', 100),
confidenceAlertThreshold: $env->getFloat('ML_CONFIDENCE_ALERT_THRESHOLD', 0.65),
accuracyAlertThreshold: $env->getFloat('ML_ACCURACY_ALERT_THRESHOLD', 0.75)
);
}

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Framework\Notification\Channels\Telegram\Webhook;
use App\Framework\Attributes\Route;
use App\Framework\Config\Environment;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\Method;
use App\Framework\Router\Result\JsonResult;
@@ -20,7 +21,8 @@ use App\Framework\Webhook\Processing\WebhookRequestHandler;
final readonly class TelegramWebhookController
{
public function __construct(
private WebhookRequestHandler $webhookHandler
private WebhookRequestHandler $webhookHandler,
private Environment $environment
) {
}
@@ -47,7 +49,7 @@ final readonly class TelegramWebhookController
public function handleWebhook(HttpRequest $request): JsonResult
{
// Get secret token from environment
$secretToken = $_ENV['TELEGRAM_WEBHOOK_SECRET'] ?? '';
$secretToken = $this->environment->getString('TELEGRAM_WEBHOOK_SECRET', '');
if (empty($secretToken)) {
return new JsonResult([

View File

@@ -24,6 +24,8 @@ final readonly class HttpRouter implements Router
$host = $request->server->getHttpHost();
$subdomain = $this->extractSubdomain($host);
error_log("🔍 ROUTER DEBUG: Host={$host}, Subdomain=" . ($subdomain ?: 'NONE') . ", Path={$path}, Method={$method->value}");
// 1. Try subdomain-specific routes first
if ($subdomain) {
$subdomainKey = 'exact:' . $subdomain;

View File

@@ -35,12 +35,39 @@ final readonly class RouterSetup
#$routeCache = new RouteCache($this->pathProvider->getCachePath('routes.cache.php'));
$routeCount = $this->results->attributes->getCount(Route::class);
error_log("🚦 ROUTER SETUP: Route count from discovery = {$routeCount}");
if ($routeCount > 0) {
$discoveredRoutes = $this->results->attributes->get(Route::class);
// Direct DiscoveredAttribute API
$optimizedRoutes = $this->routeCompiler->compileOptimized(...$discoveredRoutes);
error_log("🚦 ROUTER SETUP: About to compile routes");
try {
$optimizedRoutes = $this->routeCompiler->compileOptimized(...$discoveredRoutes);
error_log("🚦 ROUTER SETUP: Routes compiled successfully");
} catch (\Throwable $e) {
error_log("🚦 ROUTER SETUP: ERROR compiling routes: " . $e->getMessage());
error_log("🚦 ROUTER SETUP: Exception trace: " . $e->getTraceAsString());
throw $e;
}
// DEBUG: Log available static routes structure
error_log("🚦 ROUTER SETUP: About to analyze route structure");
$staticRoutes = $optimizedRoutes->getStaticRoutes();
error_log("🚦 ROUTER SETUP: HTTP Methods count = " . count($staticRoutes));
// Check GET method subdomain keys
$getSubdomainKeys = $optimizedRoutes->getSubdomainKeys(\App\Framework\Http\Method::GET);
error_log("🚦 ROUTER SETUP: GET method has subdomain keys: " . json_encode($getSubdomainKeys));
// Log routes per subdomain for GET
foreach ($getSubdomainKeys as $subdomainKey) {
$routes = $optimizedRoutes->getStaticRoutesForSubdomain(\App\Framework\Http\Method::GET, $subdomainKey);
error_log("🚦 ROUTER SETUP: GET subdomain '{$subdomainKey}' has " . count($routes) . " routes");
foreach ($routes as $path => $route) {
error_log("🚦 ROUTER SETUP: - {$path}");
}
}
// Cache speichern
#$routeCache->save($optimizedRoutes);

View File

@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace App\Framework\Security\RequestSigning;
use App\Framework\Config\Environment;
/**
* Configuration for request signing functionality
*/
@@ -24,26 +26,28 @@ final readonly class RequestSigningConfig
/**
* Create configuration from environment variables
*/
public static function fromEnvironment(): self
public static function fromEnvironment(Environment $env): self
{
$enabled = filter_var($_ENV['REQUEST_SIGNING_ENABLED'] ?? 'false', FILTER_VALIDATE_BOOLEAN);
$requireSignature = filter_var($_ENV['REQUEST_SIGNING_REQUIRED'] ?? 'false', FILTER_VALIDATE_BOOLEAN);
$enabled = $env->getBool('REQUEST_SIGNING_ENABLED', false);
$requireSignature = $env->getBool('REQUEST_SIGNING_REQUIRED', false);
$exemptPaths = [];
if (isset($_ENV['REQUEST_SIGNING_EXEMPT_PATHS'])) {
$exemptPaths = array_filter(array_map('trim', explode(',', $_ENV['REQUEST_SIGNING_EXEMPT_PATHS'])));
$exemptPathsString = $env->getString('REQUEST_SIGNING_EXEMPT_PATHS', '');
if ($exemptPathsString !== '') {
$exemptPaths = array_filter(array_map('trim', explode(',', $exemptPathsString)));
}
$defaultHeaders = ['(request-target)', 'host', 'date'];
if (isset($_ENV['REQUEST_SIGNING_DEFAULT_HEADERS'])) {
$defaultHeaders = array_filter(array_map('trim', explode(',', $_ENV['REQUEST_SIGNING_DEFAULT_HEADERS'])));
$defaultHeadersString = $env->getString('REQUEST_SIGNING_DEFAULT_HEADERS', '');
if ($defaultHeadersString !== '') {
$defaultHeaders = array_filter(array_map('trim', explode(',', $defaultHeadersString)));
}
$maxClockSkew = (int) ($_ENV['REQUEST_SIGNING_MAX_CLOCK_SKEW'] ?? 300);
$defaultExpiry = (int) ($_ENV['REQUEST_SIGNING_DEFAULT_EXPIRY'] ?? 3600);
$maxClockSkew = $env->getInt('REQUEST_SIGNING_MAX_CLOCK_SKEW', 300);
$defaultExpiry = $env->getInt('REQUEST_SIGNING_DEFAULT_EXPIRY', 3600);
$algorithm = SigningAlgorithm::tryFrom($_ENV['REQUEST_SIGNING_ALGORITHM'] ?? 'hmac-sha256')
?? SigningAlgorithm::HMAC_SHA256;
$algorithmString = $env->getString('REQUEST_SIGNING_ALGORITHM', 'hmac-sha256');
$algorithm = SigningAlgorithm::tryFrom($algorithmString) ?? SigningAlgorithm::HMAC_SHA256;
return new self(
enabled: $enabled,

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Framework\Security\RequestSigning;
use App\Framework\Cache\Cache;
use App\Framework\Config\Environment;
use App\Framework\Database\EntityManager;
use App\Framework\DateTime\Clock;
use App\Framework\DI\Container;
@@ -58,7 +59,9 @@ final readonly class RequestSigningInitializer
*/
private function getConfig(): RequestSigningConfig
{
$isProduction = ($_ENV['APP_ENV'] ?? 'development') === 'production';
$env = $this->container->get(Environment::class);
$appEnv = $env->getString('APP_ENV', 'development');
$isProduction = $appEnv === 'production';
return $isProduction
? RequestSigningConfig::production()
@@ -70,7 +73,9 @@ final readonly class RequestSigningInitializer
*/
private function createKeyRepository(RequestSigningConfig $config): SigningKeyRepository
{
$isProduction = ($_ENV['APP_ENV'] ?? 'development') === 'production';
$env = $this->container->get(Environment::class);
$appEnv = $env->getString('APP_ENV', 'development');
$isProduction = $appEnv === 'production';
if ($isProduction && $this->container->has(EntityManager::class)) {
return new EntityManagerSigningKeyRepository(
@@ -94,7 +99,9 @@ final readonly class RequestSigningInitializer
}
// Add a default development key
if (($_ENV['APP_ENV'] ?? 'development') === 'development') {
$env = $this->container->get(Environment::class);
$appEnv = $env->getString('APP_ENV', 'development');
if ($appEnv === 'development') {
if ($keyRepository instanceof InMemorySigningKeyRepository) {
$keyRepository->addDefaultTestKey();
}
@@ -109,9 +116,11 @@ final readonly class RequestSigningInitializer
*/
private function loadKeysFromEnvironment(SigningKeyRepository $keyRepository): void
{
$env = $this->container->get(Environment::class);
// Load HMAC keys from environment
$hmacKeys = $_ENV['REQUEST_SIGNING_HMAC_KEYS'] ?? '';
if ($hmacKeys) {
$hmacKeys = $env->getString('REQUEST_SIGNING_HMAC_KEYS', '');
if ($hmacKeys !== '') {
$keys = json_decode($hmacKeys, true);
if (is_array($keys)) {
foreach ($keys as $keyData) {
@@ -138,8 +147,8 @@ final readonly class RequestSigningInitializer
}
// Load RSA keys from environment
$rsaKeys = $_ENV['REQUEST_SIGNING_RSA_KEYS'] ?? '';
if ($rsaKeys) {
$rsaKeys = $env->getString('REQUEST_SIGNING_RSA_KEYS', '');
if ($rsaKeys !== '') {
$keys = json_decode($rsaKeys, true);
if (is_array($keys)) {
foreach ($keys as $keyData) {

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Framework\View\Dom\Transformer;
use App\Framework\Config\AppConfig;
use App\Framework\LiveComponents\Contracts\ComponentRegistryInterface;
use App\Framework\LiveComponents\Performance\ComponentMetadataCacheInterface;
use App\Framework\LiveComponents\ValueObjects\ComponentId;
@@ -34,7 +35,8 @@ final class XComponentTransformer implements NodeVisitor, AstTransformer
private readonly ComponentRegistryInterface $componentRegistry,
private readonly StaticComponentRenderer $staticComponentRenderer,
private readonly ComponentMetadataCacheInterface $metadataCache,
private readonly HtmlParser $parser
private readonly HtmlParser $parser,
private readonly AppConfig $appConfig
) {
}
@@ -391,7 +393,7 @@ final class XComponentTransformer implements NodeVisitor, AstTransformer
*/
private function handleTransformError(ElementNode $element, \Throwable $e): void
{
$isDebug = ($_ENV['APP_ENV'] ?? 'production') === 'development';
$isDebug = $this->appConfig->isDebug();
if ($isDebug) {
// Create error node in place of component

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Framework\View\Processors;
use App\Framework\Config\AppConfig;
use App\Framework\Config\Environment;
use App\Framework\DI\Container;
use App\Framework\LiveComponents\ComponentRegistry;
use App\Framework\LiveComponents\Contracts\LiveComponentContract;
@@ -449,8 +450,13 @@ final class PlaceholderReplacer implements StringProcessor
return $appConfig->isDebug();
} catch (\Throwable $e) {
// Fallback zu Environment-Variable falls AppConfig nicht verfügbar
return ($_ENV['APP_ENV'] ?? 'production') === 'development';
// Fallback zu Environment falls AppConfig nicht verfügbar
try {
$env = $this->container->get(Environment::class);
return $env->getString('APP_ENV', 'production') === 'development';
} catch (\Throwable) {
return false; // Safe fallback: production mode
}
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Framework\View\Processors;
use App\Framework\Config\AppConfig;
use App\Framework\LiveComponents\Contracts\ComponentRegistryInterface;
use App\Framework\LiveComponents\Performance\ComponentMetadataCacheInterface;
use App\Framework\LiveComponents\ValueObjects\ComponentData;
@@ -42,7 +43,8 @@ final readonly class XComponentProcessor implements DomProcessor
public function __construct(
private ComponentRegistryInterface $componentRegistry,
private ComponentMetadataCacheInterface $metadataCache,
private DomComponentService $componentService
private DomComponentService $componentService,
private AppConfig $appConfig
) {
}
@@ -349,8 +351,8 @@ final readonly class XComponentProcessor implements DomProcessor
HTMLElement $element,
\Throwable $e
): void {
// Check if we're in debug mode (via environment or config)
$isDebug = ($_ENV['APP_ENV'] ?? 'production') === 'development';
// Check if we're in debug mode
$isDebug = $this->appConfig->isDebug();
if ($isDebug) {
// Show error message in place of component

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Framework\WebPush\Controllers;
use App\Framework\Attributes\Route;
use App\Framework\Config\Environment;
use App\Framework\Http\Method;
use App\Framework\Http\Request;
use App\Framework\Http\Session\Session;
@@ -26,6 +27,7 @@ final readonly class WebPushController
private SubscriptionRepository $subscriptionRepository,
private WebPushService $webPushService,
private Session $session,
private Environment $environment
) {
}
@@ -305,8 +307,7 @@ final readonly class WebPushController
#[Route(path: '/api/push/vapid-key', method: Method::GET)]
public function vapidKey(Request $request): JsonResult
{
// This should be injected from config
$publicKey = $_ENV['VAPID_PUBLIC_KEY'] ?? null;
$publicKey = $this->environment->getString('VAPID_PUBLIC_KEY');
if ($publicKey === null) {
return new JsonResult(

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Framework\WebPush;
use App\Framework\Cache\Cache;
use App\Framework\Config\Environment;
use App\Framework\DI\Container;
use App\Framework\DI\Initializer;
use App\Framework\WebPush\Console\VapidKeyCommands;
@@ -42,9 +43,11 @@ final readonly class WebPushInitializer
public function initWebPushService(): ?WebPushService
{
// Try to load VAPID keys from environment
$publicKey = $_ENV['VAPID_PUBLIC_KEY'] ?? null;
$privateKey = $_ENV['VAPID_PRIVATE_KEY'] ?? null;
$subject = $_ENV['VAPID_SUBJECT'] ?? 'mailto:admin@example.com';
$env = $this->container->get(Environment::class);
$publicKey = $env->getString('VAPID_PUBLIC_KEY');
$privateKey = $env->getString('VAPID_PRIVATE_KEY');
$subject = $env->getString('VAPID_SUBJECT', 'mailto:admin@example.com');
if ($publicKey !== null && $privateKey !== null) {
$vapidKeys = new VapidKeyPair($publicKey, $privateKey);