feat(Production): Complete production deployment infrastructure

- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
This commit is contained in:
2025-10-25 19:18:37 +02:00
parent caa85db796
commit fc3d7e6357
83016 changed files with 378904 additions and 20919 deletions

View File

@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Framework\Exception\Authentication;
use App\Framework\Exception\ErrorCode;
use App\Framework\Exception\Core\AuthErrorCode;
use App\Framework\Exception\ExceptionContext;
use App\Framework\Exception\FrameworkException;
@@ -54,7 +54,7 @@ final class AccountLockedException extends FrameworkException
context: $context,
code: 423, // Locked
previous: $previous,
errorCode: ErrorCode::AUTH_ACCOUNT_LOCKED,
errorCode: AuthErrorCode::USER_LOCKED,
retryAfter: $this->lockDurationMinutes * 60 // Retry nach Sperrzeit in Sekunden
);
}

View File

@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Framework\Exception\Authentication;
use App\Framework\Exception\ErrorCode;
use App\Framework\Exception\Core\AuthErrorCode;
use App\Framework\Exception\ExceptionContext;
use App\Framework\Exception\FrameworkException;
@@ -58,7 +58,7 @@ final class InsufficientPrivilegesException extends FrameworkException
context: $context,
code: 403, // Forbidden
previous: $previous,
errorCode: ErrorCode::AUTH_INSUFFICIENT_PRIVILEGES
errorCode: AuthErrorCode::INSUFFICIENT_PRIVILEGES
);
}

View File

@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Framework\Exception\Authentication;
use App\Framework\Exception\ErrorCode;
use App\Framework\Exception\Core\AuthErrorCode;
use App\Framework\Exception\ExceptionContext;
use App\Framework\Exception\FrameworkException;
@@ -50,7 +50,7 @@ final class InvalidCredentialsException extends FrameworkException
context: $context,
code: 401, // Unauthorized
previous: $previous,
errorCode: ErrorCode::AUTH_CREDENTIALS_INVALID
errorCode: AuthErrorCode::CREDENTIALS_INVALID
);
}

View File

@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Framework\Exception\Authentication;
use App\Framework\Exception\ErrorCode;
use App\Framework\Exception\Core\AuthErrorCode;
use App\Framework\Exception\ExceptionContext;
use App\Framework\Exception\FrameworkException;
@@ -54,7 +54,7 @@ final class SessionTimeoutException extends FrameworkException
context: $context,
code: 401, // Unauthorized
previous: $previous,
errorCode: ErrorCode::AUTH_SESSION_EXPIRED
errorCode: AuthErrorCode::SESSION_EXPIRED
);
}

View File

@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Framework\Exception\Authentication;
use App\Framework\Exception\ErrorCode;
use App\Framework\Exception\Core\AuthErrorCode;
use App\Framework\Exception\ExceptionContext;
use App\Framework\Exception\FrameworkException;
@@ -52,7 +52,7 @@ final class TokenExpiredException extends FrameworkException
context: $context,
code: 401, // Unauthorized
previous: $previous,
errorCode: ErrorCode::AUTH_TOKEN_EXPIRED
errorCode: AuthErrorCode::TOKEN_EXPIRED
);
}

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
use App\Framework\Exception\ErrorCode;
/**
* API error codes
*
* Covers API integration, external service communication, and response handling
*/
enum ApiErrorCode: string implements ErrorCode
{
case REQUEST_FAILED = 'API001';
case INVALID_RESPONSE = 'API002';
case TIMEOUT = 'API003';
case AUTHENTICATION_FAILED = 'API004';
case SERVICE_UNAVAILABLE = 'API005';
public function getValue(): string
{
return $this->value;
}
public function getCategory(): string
{
return 'API';
}
public function getNumericCode(): int
{
return (int) substr($this->value, -3);
}
public function getSeverity(): ErrorSeverity
{
return match($this) {
self::SERVICE_UNAVAILABLE => ErrorSeverity::CRITICAL,
self::REQUEST_FAILED,
self::TIMEOUT,
self::AUTHENTICATION_FAILED => ErrorSeverity::ERROR,
self::INVALID_RESPONSE => ErrorSeverity::WARNING,
};
}
public function getDescription(): string
{
return match($this) {
self::REQUEST_FAILED => 'API request execution failed',
self::INVALID_RESPONSE => 'API response format is invalid',
self::TIMEOUT => 'API request timed out',
self::AUTHENTICATION_FAILED => 'API authentication failed',
self::SERVICE_UNAVAILABLE => 'External API service is unavailable',
};
}
public function getRecoveryHint(): string
{
return match($this) {
self::REQUEST_FAILED => 'Check request parameters and network connectivity',
self::INVALID_RESPONSE => 'Verify API version and response schema',
self::TIMEOUT => 'Increase timeout or retry with exponential backoff',
self::AUTHENTICATION_FAILED => 'Verify API credentials and permissions',
self::SERVICE_UNAVAILABLE => 'Check service status or use fallback provider',
};
}
public function isRecoverable(): bool
{
return true;
}
public function getRetryAfterSeconds(): ?int
{
return match($this) {
self::TIMEOUT => 30,
self::SERVICE_UNAVAILABLE => 60,
default => null,
};
}
}

View File

@@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
use App\Framework\Exception\ErrorCode;
/**
* Authentication and authorization error codes
*
* Covers user authentication, token validation, and access control
*/
enum AuthErrorCode: string implements ErrorCode
{
case CREDENTIALS_INVALID = 'AUTH001';
case TOKEN_EXPIRED = 'AUTH002';
case TOKEN_INVALID = 'AUTH003';
case USER_LOCKED = 'AUTH004';
case SESSION_EXPIRED = 'AUTH005';
case INSUFFICIENT_PRIVILEGES = 'AUTH006';
case UNAUTHORIZED = 'AUTH007';
public function getValue(): string
{
return $this->value;
}
public function getCategory(): string
{
return 'AUTH';
}
public function getNumericCode(): int
{
return (int) substr($this->value, -3);
}
public function getSeverity(): ErrorSeverity
{
return match($this) {
self::CREDENTIALS_INVALID,
self::USER_LOCKED,
self::INSUFFICIENT_PRIVILEGES,
self::UNAUTHORIZED => ErrorSeverity::ERROR,
self::TOKEN_EXPIRED,
self::TOKEN_INVALID,
self::SESSION_EXPIRED => ErrorSeverity::WARNING,
};
}
public function getDescription(): string
{
return match($this) {
self::CREDENTIALS_INVALID => 'Provided credentials are invalid',
self::TOKEN_EXPIRED => 'Authentication token has expired',
self::TOKEN_INVALID => 'Authentication token is invalid',
self::USER_LOCKED => 'User account is locked',
self::SESSION_EXPIRED => 'User session has expired',
self::INSUFFICIENT_PRIVILEGES => 'Insufficient privileges for this operation',
self::UNAUTHORIZED => 'Unauthorized access attempt',
};
}
public function getRecoveryHint(): string
{
return match($this) {
self::CREDENTIALS_INVALID => 'Verify username and password',
self::TOKEN_EXPIRED => 'Refresh authentication token',
self::TOKEN_INVALID => 'Obtain new authentication token',
self::USER_LOCKED => 'Contact administrator to unlock account',
self::SESSION_EXPIRED => 'Log in again to create new session',
self::INSUFFICIENT_PRIVILEGES => 'Request appropriate permissions from administrator',
self::UNAUTHORIZED => 'Authenticate with valid credentials and proper authorization',
};
}
public function isRecoverable(): bool
{
return match($this) {
self::USER_LOCKED => false,
default => true,
};
}
public function getRetryAfterSeconds(): ?int
{
return match($this) {
self::TOKEN_EXPIRED => 0, // Retry immediately after token refresh
self::SESSION_EXPIRED => 0, // Retry immediately after re-authentication
default => null,
};
}
}

View File

@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
use App\Framework\Exception\ErrorCode;
/**
* Business error codes
*
* Covers business rule violations, workflow errors, and domain-specific failures
*/
enum BusinessErrorCode: string implements ErrorCode
{
case BUSINESS_RULE_VIOLATION = 'BIZ001';
case WORKFLOW_STATE_INVALID = 'BIZ002';
case OPERATION_NOT_ALLOWED = 'BIZ003';
case RESOURCE_LOCKED = 'BIZ004';
public function getValue(): string
{
return $this->value;
}
public function getCategory(): string
{
return 'BIZ';
}
public function getNumericCode(): int
{
return (int) substr($this->value, -3);
}
public function getSeverity(): ErrorSeverity
{
return match($this) {
self::BUSINESS_RULE_VIOLATION,
self::WORKFLOW_STATE_INVALID,
self::OPERATION_NOT_ALLOWED,
self::RESOURCE_LOCKED => ErrorSeverity::ERROR,
};
}
public function getDescription(): string
{
return match($this) {
self::BUSINESS_RULE_VIOLATION => 'Business rule violation occurred',
self::WORKFLOW_STATE_INVALID => 'Invalid workflow state for operation',
self::OPERATION_NOT_ALLOWED => 'Operation not allowed in current context',
self::RESOURCE_LOCKED => 'Resource is locked by another process',
};
}
public function getRecoveryHint(): string
{
return match($this) {
self::BUSINESS_RULE_VIOLATION => 'Review business rules and adjust operation',
self::WORKFLOW_STATE_INVALID => 'Check workflow state and allowed transitions',
self::OPERATION_NOT_ALLOWED => 'Verify operation prerequisites and permissions',
self::RESOURCE_LOCKED => 'Wait for lock release or cancel conflicting operation',
};
}
public function isRecoverable(): bool
{
return match($this) {
self::RESOURCE_LOCKED => true,
default => false,
};
}
public function getRetryAfterSeconds(): ?int
{
return match($this) {
self::RESOURCE_LOCKED => 10,
default => null,
};
}
}

View File

@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
use App\Framework\Exception\ErrorCode;
/**
* Cache error codes
*
* Covers cache operations, invalidation, and driver issues
*/
enum CacheErrorCode: string implements ErrorCode
{
case CONNECTION_FAILED = 'CACHE001';
case READ_FAILED = 'CACHE002';
case WRITE_FAILED = 'CACHE003';
case INVALIDATION_FAILED = 'CACHE004';
public function getValue(): string
{
return $this->value;
}
public function getCategory(): string
{
return 'CACHE';
}
public function getNumericCode(): int
{
return (int) substr($this->value, -3);
}
public function getSeverity(): ErrorSeverity
{
return match($this) {
self::CONNECTION_FAILED => ErrorSeverity::CRITICAL,
self::READ_FAILED,
self::WRITE_FAILED,
self::INVALIDATION_FAILED => ErrorSeverity::WARNING,
};
}
public function getDescription(): string
{
return match($this) {
self::CONNECTION_FAILED => 'Cache server connection failed',
self::READ_FAILED => 'Cache read operation failed',
self::WRITE_FAILED => 'Cache write operation failed',
self::INVALIDATION_FAILED => 'Cache invalidation failed',
};
}
public function getRecoveryHint(): string
{
return match($this) {
self::CONNECTION_FAILED => 'Check cache server status and configuration',
self::READ_FAILED => 'Retry operation or regenerate cached data',
self::WRITE_FAILED => 'Check cache storage capacity and permissions',
self::INVALIDATION_FAILED => 'Manually clear cache or restart cache service',
};
}
public function isRecoverable(): bool
{
return true;
}
public function getRetryAfterSeconds(): ?int
{
return match($this) {
self::CONNECTION_FAILED => 30,
default => null,
};
}
}

View File

@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
use App\Framework\Exception\ErrorCode;
/**
* Console error codes
*
* Covers console command execution, argument parsing, and CLI operations
*/
enum ConsoleErrorCode: string implements ErrorCode
{
case COMMAND_NOT_FOUND = 'CON001';
case INVALID_ARGUMENT = 'CON002';
case EXECUTION_FAILED = 'CON003';
case MISSING_REQUIRED_ARGUMENT = 'CON004';
case INVALID_OPTION = 'CON005';
case COMMAND_TIMEOUT = 'CON006';
case PERMISSION_DENIED = 'CON007';
case OUTPUT_WRITE_FAILED = 'CON008';
case INVALID_COMMAND_STRUCTURE = 'CON009';
public function getValue(): string
{
return $this->value;
}
public function getCategory(): string
{
return 'CON';
}
public function getNumericCode(): int
{
return (int) substr($this->value, -3);
}
public function getSeverity(): ErrorSeverity
{
return match($this) {
self::EXECUTION_FAILED,
self::PERMISSION_DENIED,
self::INVALID_COMMAND_STRUCTURE => ErrorSeverity::ERROR,
self::COMMAND_NOT_FOUND,
self::INVALID_ARGUMENT,
self::MISSING_REQUIRED_ARGUMENT,
self::INVALID_OPTION,
self::COMMAND_TIMEOUT,
self::OUTPUT_WRITE_FAILED => ErrorSeverity::WARNING,
};
}
public function getDescription(): string
{
return match($this) {
self::COMMAND_NOT_FOUND => 'Console command not found',
self::INVALID_ARGUMENT => 'Invalid command argument provided',
self::EXECUTION_FAILED => 'Command execution failed',
self::MISSING_REQUIRED_ARGUMENT => 'Required command argument is missing',
self::INVALID_OPTION => 'Invalid command option provided',
self::COMMAND_TIMEOUT => 'Command execution timed out',
self::PERMISSION_DENIED => 'Insufficient permissions to execute command',
self::OUTPUT_WRITE_FAILED => 'Failed to write command output',
self::INVALID_COMMAND_STRUCTURE => 'Command has invalid structure or configuration',
};
}
public function getRecoveryHint(): string
{
return match($this) {
self::COMMAND_NOT_FOUND => 'Check command name and available commands list',
self::INVALID_ARGUMENT => 'Provide valid argument according to command help',
self::EXECUTION_FAILED => 'Check command logs and fix underlying issue',
self::MISSING_REQUIRED_ARGUMENT => 'Provide all required arguments',
self::INVALID_OPTION => 'Use valid options according to command help',
self::COMMAND_TIMEOUT => 'Increase timeout or optimize command execution',
self::PERMISSION_DENIED => 'Run command with appropriate permissions',
self::OUTPUT_WRITE_FAILED => 'Check output stream and file permissions',
self::INVALID_COMMAND_STRUCTURE => 'Verify command class structure and attribute configuration',
};
}
public function isRecoverable(): bool
{
return true;
}
public function getRetryAfterSeconds(): ?int
{
return null;
}
}

View File

@@ -0,0 +1,140 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
use App\Framework\Exception\ErrorCode;
/**
* Database error codes
*
* Covers database connectivity, queries, transactions, and migrations
*/
enum DatabaseErrorCode: string implements ErrorCode
{
case CONNECTION_FAILED = 'DB001';
case QUERY_FAILED = 'DB002';
case CONSTRAINT_VIOLATION = 'DB003';
case TRANSACTION_FAILED = 'DB004';
case MIGRATION_FAILED = 'DB005';
case MIGRATION_ROLLBACK_FAILED = 'DB006';
case MIGRATION_TABLE_CREATION_FAILED = 'DB007';
case MIGRATION_DEPENDENCY_ERROR = 'DB008';
case MIGRATION_PREFLIGHT_FAILED = 'DB009';
case POOL_EXHAUSTED = 'DB010';
case TIMEOUT = 'DB011';
case DEADLOCK_DETECTED = 'DB012';
case QUERY_SYNTAX_ERROR = 'DB013';
case QUERY_EXECUTION_FAILED = 'DB014';
case ENTITY_NOT_FOUND = 'DB015';
case MIGRATION_NOT_REVERSIBLE = 'DB016';
public function getValue(): string
{
return $this->value;
}
public function getCategory(): string
{
return 'DB';
}
public function getNumericCode(): int
{
return (int) substr($this->value, -3);
}
public function getSeverity(): ErrorSeverity
{
return match($this) {
self::CONNECTION_FAILED,
self::CONSTRAINT_VIOLATION,
self::MIGRATION_FAILED,
self::MIGRATION_ROLLBACK_FAILED,
self::POOL_EXHAUSTED,
self::DEADLOCK_DETECTED => ErrorSeverity::CRITICAL,
self::QUERY_FAILED,
self::TRANSACTION_FAILED,
self::MIGRATION_DEPENDENCY_ERROR,
self::TIMEOUT,
self::QUERY_EXECUTION_FAILED => ErrorSeverity::ERROR,
self::MIGRATION_TABLE_CREATION_FAILED,
self::MIGRATION_PREFLIGHT_FAILED,
self::MIGRATION_NOT_REVERSIBLE,
self::QUERY_SYNTAX_ERROR,
self::ENTITY_NOT_FOUND => ErrorSeverity::WARNING,
};
}
public function getDescription(): string
{
return match($this) {
self::CONNECTION_FAILED => 'Database connection could not be established',
self::QUERY_FAILED => 'Database query execution failed',
self::CONSTRAINT_VIOLATION => 'Database constraint violation occurred',
self::TRANSACTION_FAILED => 'Database transaction failed',
self::MIGRATION_FAILED => 'Database migration failed',
self::MIGRATION_ROLLBACK_FAILED => 'Database migration rollback failed',
self::MIGRATION_TABLE_CREATION_FAILED => 'Database migration table creation failed',
self::MIGRATION_DEPENDENCY_ERROR => 'Migration dependency validation failed',
self::MIGRATION_PREFLIGHT_FAILED => 'Migration preflight check failed',
self::POOL_EXHAUSTED => 'Database connection pool exhausted',
self::TIMEOUT => 'Database operation timed out',
self::DEADLOCK_DETECTED => 'Database deadlock detected - transaction conflicts with concurrent operation',
self::QUERY_SYNTAX_ERROR => 'Database query contains syntax errors or invalid references',
self::QUERY_EXECUTION_FAILED => 'Database query execution failed due to data or runtime error',
self::ENTITY_NOT_FOUND => 'Requested entity does not exist in the database',
self::MIGRATION_NOT_REVERSIBLE => 'Migration does not support safe rollback - data loss would occur',
};
}
public function getRecoveryHint(): string
{
return match($this) {
self::CONNECTION_FAILED => 'Check database server status and connection settings',
self::QUERY_FAILED => 'Review query syntax and database schema',
self::CONSTRAINT_VIOLATION => 'Check data integrity and constraint definitions',
self::TRANSACTION_FAILED => 'Retry transaction or check for deadlocks',
self::MIGRATION_FAILED => 'Review migration scripts and database state',
self::MIGRATION_ROLLBACK_FAILED => 'Manually rollback migration and fix database state',
self::MIGRATION_TABLE_CREATION_FAILED => 'Check database permissions and table creation syntax',
self::MIGRATION_DEPENDENCY_ERROR => 'Add missing dependency migrations or review dependency chains',
self::MIGRATION_PREFLIGHT_FAILED => 'Fix preflight check issues before running migration',
self::POOL_EXHAUSTED => 'Increase connection pool size or optimize queries',
self::TIMEOUT => 'Optimize query performance or increase timeout limits',
self::DEADLOCK_DETECTED => 'Retry transaction immediately - deadlocks are typically resolved automatically',
self::QUERY_SYNTAX_ERROR => 'Review SQL syntax, verify table/column names match schema, check database permissions',
self::QUERY_EXECUTION_FAILED => 'Validate input data format, check for null/out-of-range values, review query logic',
self::ENTITY_NOT_FOUND => 'Verify entity ID is correct, check if entity was deleted, review query criteria',
self::MIGRATION_NOT_REVERSIBLE => 'Create a new forward migration to undo changes instead of rolling back',
};
}
public function isRecoverable(): bool
{
return match($this) {
self::MIGRATION_ROLLBACK_FAILED,
self::MIGRATION_NOT_REVERSIBLE,
self::CONSTRAINT_VIOLATION,
self::QUERY_SYNTAX_ERROR,
self::ENTITY_NOT_FOUND => false, // Not found errors are not transient
default => true,
};
}
public function getRetryAfterSeconds(): ?int
{
return match($this) {
self::CONNECTION_FAILED => 30,
self::TIMEOUT => 60,
self::POOL_EXHAUSTED => 15,
self::TRANSACTION_FAILED => 5,
self::DEADLOCK_DETECTED => 1, // Retry deadlocks immediately (1 second)
self::QUERY_EXECUTION_FAILED => 5, // Retry data errors after brief delay
default => null,
};
}
}

View File

@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
use App\Framework\Exception\ErrorCode;
/**
* Dependency Injection error codes
*
* Covers DI container operations, binding resolution, and lifecycle management
*/
enum DiErrorCode: string implements ErrorCode
{
case BINDING_NOT_FOUND = 'DI001';
case CIRCULAR_DEPENDENCY = 'DI002';
case RESOLUTION_FAILED = 'DI003';
case INVALID_BINDING = 'DI004';
case SINGLETON_CONFLICT = 'DI005';
public function getValue(): string
{
return $this->value;
}
public function getCategory(): string
{
return 'DI';
}
public function getNumericCode(): int
{
return (int) substr($this->value, -3);
}
public function getSeverity(): ErrorSeverity
{
return match($this) {
self::CIRCULAR_DEPENDENCY,
self::RESOLUTION_FAILED => ErrorSeverity::CRITICAL,
self::BINDING_NOT_FOUND,
self::INVALID_BINDING,
self::SINGLETON_CONFLICT => ErrorSeverity::ERROR,
};
}
public function getDescription(): string
{
return match($this) {
self::BINDING_NOT_FOUND => 'DI binding not found for requested type',
self::CIRCULAR_DEPENDENCY => 'Circular dependency detected',
self::RESOLUTION_FAILED => 'Dependency resolution failed',
self::INVALID_BINDING => 'Invalid binding configuration',
self::SINGLETON_CONFLICT => 'Singleton instance conflict',
};
}
public function getRecoveryHint(): string
{
return match($this) {
self::BINDING_NOT_FOUND => 'Register binding in DI container',
self::CIRCULAR_DEPENDENCY => 'Refactor dependencies to break circular reference',
self::RESOLUTION_FAILED => 'Check dependency constructors and bindings',
self::INVALID_BINDING => 'Fix binding configuration or implementation',
self::SINGLETON_CONFLICT => 'Review singleton lifecycle management',
};
}
public function isRecoverable(): bool
{
return false;
}
public function getRetryAfterSeconds(): ?int
{
return null;
}
}

View File

@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
use App\Framework\Exception\ErrorCode;
/**
* Discovery error codes
*
* Covers attribute scanning, component discovery, and auto-registration
*/
enum DiscoveryErrorCode: string implements ErrorCode
{
case SCAN_FAILED = 'DISC001';
case INVALID_ATTRIBUTE = 'DISC002';
case DUPLICATE_REGISTRATION = 'DISC003';
case CACHE_READ_FAILED = 'DISC004';
case CACHE_WRITE_FAILED = 'DISC005';
public function getValue(): string
{
return $this->value;
}
public function getCategory(): string
{
return 'DISC';
}
public function getNumericCode(): int
{
return (int) substr($this->value, -3);
}
public function getSeverity(): ErrorSeverity
{
return match($this) {
self::SCAN_FAILED => ErrorSeverity::CRITICAL,
self::INVALID_ATTRIBUTE,
self::DUPLICATE_REGISTRATION => ErrorSeverity::ERROR,
self::CACHE_READ_FAILED,
self::CACHE_WRITE_FAILED => ErrorSeverity::WARNING,
};
}
public function getDescription(): string
{
return match($this) {
self::SCAN_FAILED => 'Discovery scan operation failed',
self::INVALID_ATTRIBUTE => 'Invalid or malformed attribute detected',
self::DUPLICATE_REGISTRATION => 'Duplicate component registration detected',
self::CACHE_READ_FAILED => 'Discovery cache read failed',
self::CACHE_WRITE_FAILED => 'Discovery cache write failed',
};
}
public function getRecoveryHint(): string
{
return match($this) {
self::SCAN_FAILED => 'Check file permissions and directory structure',
self::INVALID_ATTRIBUTE => 'Fix attribute syntax and parameters',
self::DUPLICATE_REGISTRATION => 'Remove duplicate registrations or use unique identifiers',
self::CACHE_READ_FAILED => 'Clear discovery cache and rescan',
self::CACHE_WRITE_FAILED => 'Check cache directory permissions',
};
}
public function isRecoverable(): bool
{
return match($this) {
self::SCAN_FAILED => false,
default => true,
};
}
public function getRetryAfterSeconds(): ?int
{
return null;
}
}

View File

@@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
use App\Framework\Exception\ErrorCode;
/**
* Entity error codes
*
* Covers entity operations, persistence, and lifecycle management
*/
enum EntityErrorCode: string implements ErrorCode
{
case ENTITY_NOT_FOUND = 'ENT001';
case ENTITY_ALREADY_EXISTS = 'ENT002';
case PERSISTENCE_FAILED = 'ENT003';
case INVALID_ENTITY_STATE = 'ENT004';
case VERSION_CONFLICT = 'ENT005';
case DETACHED_ENTITY = 'ENT006';
public function getValue(): string
{
return $this->value;
}
public function getCategory(): string
{
return 'ENT';
}
public function getNumericCode(): int
{
return (int) substr($this->value, -3);
}
public function getSeverity(): ErrorSeverity
{
return match($this) {
self::PERSISTENCE_FAILED => ErrorSeverity::CRITICAL,
self::ENTITY_ALREADY_EXISTS,
self::INVALID_ENTITY_STATE,
self::VERSION_CONFLICT => ErrorSeverity::ERROR,
self::ENTITY_NOT_FOUND,
self::DETACHED_ENTITY => ErrorSeverity::WARNING,
};
}
public function getDescription(): string
{
return match($this) {
self::ENTITY_NOT_FOUND => 'Entity not found',
self::ENTITY_ALREADY_EXISTS => 'Entity already exists',
self::PERSISTENCE_FAILED => 'Entity persistence operation failed',
self::INVALID_ENTITY_STATE => 'Entity is in invalid state',
self::VERSION_CONFLICT => 'Entity version conflict detected',
self::DETACHED_ENTITY => 'Operation on detached entity',
};
}
public function getRecoveryHint(): string
{
return match($this) {
self::ENTITY_NOT_FOUND => 'Verify entity identifier and existence',
self::ENTITY_ALREADY_EXISTS => 'Use update operation or different identifier',
self::PERSISTENCE_FAILED => 'Check database connection and constraints',
self::INVALID_ENTITY_STATE => 'Validate entity state before operation',
self::VERSION_CONFLICT => 'Reload entity and retry operation',
self::DETACHED_ENTITY => 'Reattach entity to persistence context',
};
}
public function isRecoverable(): bool
{
return match($this) {
self::ENTITY_NOT_FOUND => false,
default => true,
};
}
public function getRetryAfterSeconds(): ?int
{
return match($this) {
self::VERSION_CONFLICT => 1,
default => null,
};
}
}

View File

@@ -0,0 +1,212 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
/**
* Error severity levels with retention policies and alert behavior
*
* Defines severity classification for exceptions with automatic retention
* policies and alert triggering behavior.
*
* Severity Levels:
* - CRITICAL: System failures, data corruption, security breaches (365 days retention)
* - ERROR: Operation failures, validation errors, resource issues (90 days retention)
* - WARNING: Potential issues, degraded performance (30 days retention)
* - NOTICE: Notification of non-critical conditions (14 days retention)
* - INFO: Informational events (7 days retention)
* - DEBUG: Debugging information (1 day retention)
*/
enum ErrorSeverity: string
{
case CRITICAL = 'critical';
case ERROR = 'error';
case WARNING = 'warning';
case NOTICE = 'notice';
case INFO = 'info';
case DEBUG = 'debug';
/**
* Get retention period in days for this severity level
*
* Data retention based on severity:
* - CRITICAL: 365 days (1 year) - Critical errors require long-term analysis
* - ERROR: 90 days (3 months) - Standard error retention
* - WARNING: 30 days (1 month) - Warning-level issues
* - NOTICE: 14 days (2 weeks) - Notice-level events
* - INFO: 7 days (1 week) - Informational events
* - DEBUG: 1 day - Debug information only needed short-term
*/
public function getRetentionDays(): int
{
return match ($this) {
self::CRITICAL => 365,
self::ERROR => 90,
self::WARNING => 30,
self::NOTICE => 14,
self::INFO => 7,
self::DEBUG => 1,
};
}
/**
* Check if this severity level should trigger alerts
*
* Only CRITICAL and ERROR severities trigger automatic alerts
*/
public function shouldAlert(): bool
{
return match ($this) {
self::CRITICAL, self::ERROR => true,
default => false,
};
}
/**
* Get alert priority for this severity level
*
* Returns:
* - URGENT: For CRITICAL errors (immediate attention required)
* - HIGH: For ERROR level (attention required soon)
* - MEDIUM: For WARNING level (review when convenient)
* - LOW: For NOTICE level (review when convenient)
* - null: For INFO and DEBUG (no alerting)
*/
public function getAlertPriority(): ?string
{
return match ($this) {
self::CRITICAL => 'URGENT',
self::ERROR => 'HIGH',
self::WARNING => 'MEDIUM',
self::NOTICE => 'LOW',
default => null,
};
}
/**
* Get numeric weight for severity comparison
*
* Higher weight = more severe
* Useful for sorting and filtering by severity
*/
public function getWeight(): int
{
return match ($this) {
self::CRITICAL => 6,
self::ERROR => 5,
self::WARNING => 4,
self::NOTICE => 3,
self::INFO => 2,
self::DEBUG => 1,
};
}
/**
* Check if this severity is more severe than another
*/
public function isMoreSevereThan(self $other): bool
{
return $this->getWeight() > $other->getWeight();
}
/**
* Check if this severity is less severe than another
*/
public function isLessSevereThan(self $other): bool
{
return $this->getWeight() < $other->getWeight();
}
/**
* Check if this severity requires immediate action
*/
public function requiresImmediateAction(): bool
{
return $this === self::CRITICAL;
}
/**
* Get human-readable description of severity level
*/
public function getDescription(): string
{
return match ($this) {
self::CRITICAL => 'Critical - System failure or security breach requiring immediate action',
self::ERROR => 'Error - Operation failure requiring attention',
self::WARNING => 'Warning - Potential issue or degraded performance',
self::NOTICE => 'Notice - Notification of non-critical condition',
self::INFO => 'Info - Informational event',
self::DEBUG => 'Debug - Debugging information',
};
}
/**
* Get color code for UI representation
*/
public function getColorCode(): string
{
return match ($this) {
self::CRITICAL => '#DC2626', // Red-600
self::ERROR => '#F59E0B', // Amber-500
self::WARNING => '#FBBF24', // Yellow-400
self::NOTICE => '#10B981', // Green-500
self::INFO => '#3B82F6', // Blue-500
self::DEBUG => '#6B7280', // Gray-500
};
}
/**
* Get recommended response time for this severity
*
* Returns time in minutes
*/
public function getRecommendedResponseTime(): ?int
{
return match ($this) {
self::CRITICAL => 15, // 15 minutes
self::ERROR => 60, // 1 hour
self::WARNING => 240, // 4 hours
default => null, // No SLA for NOTICE/INFO/DEBUG
};
}
/**
* Get UI color code (alias for getColorCode for test compatibility)
*/
public function getColor(): string
{
return $this->getColorCode();
}
/**
* Get icon name for UI display
*/
public function getIcon(): string
{
return match ($this) {
self::CRITICAL => 'exclamation-triangle',
self::ERROR => 'exclamation-circle',
self::WARNING => 'exclamation',
self::NOTICE => 'bell',
self::INFO => 'information-circle',
self::DEBUG => 'bug',
};
}
/**
* Compare severity levels (alias for isMoreSevereThan for test compatibility)
*/
public function isHigherThan(self $other): bool
{
return $this->isMoreSevereThan($other);
}
/**
* Map to PSR-3 log level
*/
public function toLogLevel(): string
{
return $this->value;
}
}

View File

@@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
use App\Framework\Exception\ErrorCode;
/**
* Event error codes
*
* Covers event dispatching, handler execution, and event bus operations
*/
enum EventErrorCode: string implements ErrorCode
{
case HANDLER_NOT_FOUND = 'EVENT001';
case DISPATCH_FAILED = 'EVENT002';
case HANDLER_EXECUTION_FAILED = 'EVENT003';
case INVALID_EVENT = 'EVENT004';
case EVENT_BUS_UNAVAILABLE = 'EVENT005';
public function getValue(): string
{
return $this->value;
}
public function getCategory(): string
{
return 'EVENT';
}
public function getNumericCode(): int
{
return (int) substr($this->value, -3);
}
public function getSeverity(): ErrorSeverity
{
return match($this) {
self::EVENT_BUS_UNAVAILABLE => ErrorSeverity::CRITICAL,
self::DISPATCH_FAILED,
self::HANDLER_EXECUTION_FAILED => ErrorSeverity::ERROR,
self::HANDLER_NOT_FOUND,
self::INVALID_EVENT => ErrorSeverity::WARNING,
};
}
public function getDescription(): string
{
return match($this) {
self::HANDLER_NOT_FOUND => 'Event handler not found',
self::DISPATCH_FAILED => 'Event dispatch operation failed',
self::HANDLER_EXECUTION_FAILED => 'Event handler execution failed',
self::INVALID_EVENT => 'Invalid event object or structure',
self::EVENT_BUS_UNAVAILABLE => 'Event bus is unavailable',
};
}
public function getRecoveryHint(): string
{
return match($this) {
self::HANDLER_NOT_FOUND => 'Register event handler or check event name',
self::DISPATCH_FAILED => 'Check event bus connection and retry',
self::HANDLER_EXECUTION_FAILED => 'Review handler logs and fix underlying issue',
self::INVALID_EVENT => 'Validate event structure and required properties',
self::EVENT_BUS_UNAVAILABLE => 'Check event bus service and configuration',
};
}
public function isRecoverable(): bool
{
return match($this) {
self::HANDLER_NOT_FOUND,
self::INVALID_EVENT => false,
default => true,
};
}
public function getRetryAfterSeconds(): ?int
{
return match($this) {
self::EVENT_BUS_UNAVAILABLE => 30,
default => null,
};
}
}

View File

@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
use App\Framework\Exception\ErrorCode;
/**
* File system error codes
*
* Covers file operations, permissions, and storage issues
*/
enum FileSystemErrorCode: string implements ErrorCode
{
case FILE_NOT_FOUND = 'FS001';
case PERMISSION_DENIED = 'FS002';
case DISK_FULL = 'FS003';
case READ_FAILED = 'FS004';
case WRITE_FAILED = 'FS005';
public function getValue(): string
{
return $this->value;
}
public function getCategory(): string
{
return 'FS';
}
public function getNumericCode(): int
{
return (int) substr($this->value, -3);
}
public function getSeverity(): ErrorSeverity
{
return match($this) {
self::DISK_FULL => ErrorSeverity::CRITICAL,
self::PERMISSION_DENIED,
self::WRITE_FAILED => ErrorSeverity::ERROR,
self::FILE_NOT_FOUND,
self::READ_FAILED => ErrorSeverity::WARNING,
};
}
public function getDescription(): string
{
return match($this) {
self::FILE_NOT_FOUND => 'File or directory not found',
self::PERMISSION_DENIED => 'File system permission denied',
self::DISK_FULL => 'Disk storage is full',
self::READ_FAILED => 'File read operation failed',
self::WRITE_FAILED => 'File write operation failed',
};
}
public function getRecoveryHint(): string
{
return match($this) {
self::FILE_NOT_FOUND => 'Verify file path and existence',
self::PERMISSION_DENIED => 'Check file permissions and ownership',
self::DISK_FULL => 'Free up disk space or increase storage capacity',
self::READ_FAILED => 'Check file integrity and permissions',
self::WRITE_FAILED => 'Check disk space and write permissions',
};
}
public function isRecoverable(): bool
{
return match($this) {
self::FILE_NOT_FOUND => false,
default => true,
};
}
public function getRetryAfterSeconds(): ?int
{
return null;
}
}

View File

@@ -0,0 +1,114 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
use App\Framework\Exception\ErrorCode;
/**
* Filesystem error codes
*
* Covers file operations, directory operations, permissions, and I/O errors
*/
enum FilesystemErrorCode: string implements ErrorCode
{
case FILE_NOT_FOUND = 'FS001';
case FILE_READ_FAILED = 'FS002';
case FILE_WRITE_FAILED = 'FS003';
case FILE_DELETE_FAILED = 'FS004';
case FILE_COPY_FAILED = 'FS005';
case FILE_METADATA_FAILED = 'FS006';
case PERMISSION_DENIED = 'FS007';
case DIRECTORY_CREATE_FAILED = 'FS008';
case DIRECTORY_LIST_FAILED = 'FS009';
case PATH_TRAVERSAL_ATTEMPT = 'FS010';
case INVALID_PATH = 'FS011';
case DISK_FULL = 'FS012';
case FILE_LOCKED = 'FS013';
case SYMLINK_FAILED = 'FS014';
case COMPRESSION_FAILED = 'FS015';
case SERIALIZER_NOT_FOUND = 'FS016';
public function getValue(): string
{
return $this->value;
}
public function getCategory(): string
{
return 'FS';
}
public function getNumericCode(): int
{
return (int) substr($this->value, 2); // Extract numeric part after 'FS'
}
public function getSeverity(): ErrorSeverity
{
return match ($this) {
self::PATH_TRAVERSAL_ATTEMPT => ErrorSeverity::CRITICAL,
self::PERMISSION_DENIED,
self::DISK_FULL => ErrorSeverity::ERROR,
self::FILE_NOT_FOUND,
self::FILE_LOCKED,
self::DIRECTORY_LIST_FAILED => ErrorSeverity::WARNING,
default => ErrorSeverity::NOTICE
};
}
public function getDescription(): string
{
return match ($this) {
self::FILE_NOT_FOUND => 'The requested file could not be found',
self::FILE_READ_FAILED => 'Failed to read file contents',
self::FILE_WRITE_FAILED => 'Failed to write to file',
self::FILE_DELETE_FAILED => 'Failed to delete file',
self::FILE_COPY_FAILED => 'Failed to copy file',
self::FILE_METADATA_FAILED => 'Failed to retrieve file metadata',
self::PERMISSION_DENIED => 'Insufficient permissions for file operation',
self::DIRECTORY_CREATE_FAILED => 'Failed to create directory',
self::DIRECTORY_LIST_FAILED => 'Failed to list directory contents',
self::PATH_TRAVERSAL_ATTEMPT => 'Path traversal attempt detected',
self::INVALID_PATH => 'Invalid file path',
self::DISK_FULL => 'Disk space exhausted',
self::FILE_LOCKED => 'File is locked by another process',
self::SYMLINK_FAILED => 'Failed to create symbolic link',
self::COMPRESSION_FAILED => 'File compression/decompression failed',
self::SERIALIZER_NOT_FOUND => 'Serializer not found in registry',
};
}
public function getRecoveryHint(): string
{
return match ($this) {
self::FILE_NOT_FOUND => 'Verify the file path exists',
self::PERMISSION_DENIED => 'Check file/directory permissions and ownership',
self::DISK_FULL => 'Free up disk space or increase storage capacity',
self::FILE_LOCKED => 'Wait for other process to release the file lock',
self::DIRECTORY_CREATE_FAILED => 'Verify parent directory exists and is writable',
self::SERIALIZER_NOT_FOUND => 'Register the required serializer in SerializerRegistry',
self::PATH_TRAVERSAL_ATTEMPT => 'Use validated paths only, avoid user-controlled path components',
default => 'Check file system logs for more details'
};
}
public function isRecoverable(): bool
{
return match ($this) {
self::FILE_LOCKED,
self::DISK_FULL => true,
default => false
};
}
public function getRetryAfterSeconds(): ?int
{
return match ($this) {
self::FILE_LOCKED => 5, // Retry after 5 seconds
self::DISK_FULL => 60, // Retry after 1 minute
default => null
};
}
}

View File

@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
use App\Framework\Exception\ErrorCode;
/**
* HTTP error codes
*
* Covers HTTP protocol errors, client/server errors, and network issues
*/
enum HttpErrorCode: string implements ErrorCode
{
case BAD_REQUEST = 'HTTP001';
case NOT_FOUND = 'HTTP002';
case METHOD_NOT_ALLOWED = 'HTTP003';
case RATE_LIMIT_EXCEEDED = 'HTTP004';
case INTERNAL_SERVER_ERROR = 'HTTP005';
public function getValue(): string
{
return $this->value;
}
public function getCategory(): string
{
return 'HTTP';
}
public function getNumericCode(): int
{
return (int) substr($this->value, -3);
}
public function getSeverity(): ErrorSeverity
{
return match($this) {
self::INTERNAL_SERVER_ERROR => ErrorSeverity::CRITICAL,
self::RATE_LIMIT_EXCEEDED => ErrorSeverity::ERROR,
self::BAD_REQUEST,
self::NOT_FOUND,
self::METHOD_NOT_ALLOWED => ErrorSeverity::WARNING,
};
}
public function getDescription(): string
{
return match($this) {
self::BAD_REQUEST => 'Request is malformed or invalid',
self::NOT_FOUND => 'Requested resource not found',
self::METHOD_NOT_ALLOWED => 'HTTP method not allowed for this resource',
self::RATE_LIMIT_EXCEEDED => 'API rate limit exceeded',
self::INTERNAL_SERVER_ERROR => 'Internal server error occurred',
};
}
public function getRecoveryHint(): string
{
return match($this) {
self::BAD_REQUEST => 'Check request format and parameters',
self::NOT_FOUND => 'Verify resource URL and existence',
self::METHOD_NOT_ALLOWED => 'Use correct HTTP method for this endpoint',
self::RATE_LIMIT_EXCEEDED => 'Wait and retry after rate limit window',
self::INTERNAL_SERVER_ERROR => 'Contact support or retry later',
};
}
public function isRecoverable(): bool
{
return match($this) {
self::INTERNAL_SERVER_ERROR => false,
default => true,
};
}
public function getRetryAfterSeconds(): ?int
{
return match($this) {
self::RATE_LIMIT_EXCEEDED => 60,
default => null,
};
}
}

View File

@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
use App\Framework\Exception\ErrorCode;
/**
* MCP (Model Context Protocol) error codes
*
* Covers MCP server operations, tool execution, and protocol communication
*/
enum McpErrorCode: string implements ErrorCode
{
case TOOL_NOT_FOUND = 'MCP001';
case TOOL_EXECUTION_FAILED = 'MCP002';
case INVALID_TOOL_ARGUMENTS = 'MCP003';
case RESOURCE_NOT_FOUND = 'MCP004';
case PROTOCOL_ERROR = 'MCP005';
case SERVER_UNAVAILABLE = 'MCP006';
public function getValue(): string
{
return $this->value;
}
public function getCategory(): string
{
return 'MCP';
}
public function getNumericCode(): int
{
return (int) substr($this->value, -3);
}
public function getSeverity(): ErrorSeverity
{
return match($this) {
self::SERVER_UNAVAILABLE,
self::PROTOCOL_ERROR => ErrorSeverity::CRITICAL,
self::TOOL_EXECUTION_FAILED => ErrorSeverity::ERROR,
self::TOOL_NOT_FOUND,
self::INVALID_TOOL_ARGUMENTS,
self::RESOURCE_NOT_FOUND => ErrorSeverity::WARNING,
};
}
public function getDescription(): string
{
return match($this) {
self::TOOL_NOT_FOUND => 'MCP tool not found',
self::TOOL_EXECUTION_FAILED => 'MCP tool execution failed',
self::INVALID_TOOL_ARGUMENTS => 'Invalid arguments provided to MCP tool',
self::RESOURCE_NOT_FOUND => 'MCP resource not found',
self::PROTOCOL_ERROR => 'MCP protocol communication error',
self::SERVER_UNAVAILABLE => 'MCP server is unavailable',
};
}
public function getRecoveryHint(): string
{
return match($this) {
self::TOOL_NOT_FOUND => 'Check tool name and available MCP tools',
self::TOOL_EXECUTION_FAILED => 'Review tool logs and fix underlying issue',
self::INVALID_TOOL_ARGUMENTS => 'Provide valid arguments according to tool schema',
self::RESOURCE_NOT_FOUND => 'Verify resource URI and existence',
self::PROTOCOL_ERROR => 'Check MCP protocol version and message format',
self::SERVER_UNAVAILABLE => 'Start MCP server or check server configuration',
};
}
public function isRecoverable(): bool
{
return match($this) {
self::TOOL_NOT_FOUND,
self::RESOURCE_NOT_FOUND => false,
default => true,
};
}
public function getRetryAfterSeconds(): ?int
{
return match($this) {
self::SERVER_UNAVAILABLE => 30,
default => null,
};
}
}

View File

@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
use App\Framework\Exception\ErrorCode;
/**
* Payment error codes
*
* Covers payment processing, gateway communication, and transaction failures
*/
enum PaymentErrorCode: string implements ErrorCode
{
case PAYMENT_DECLINED = 'PAY001';
case INSUFFICIENT_FUNDS = 'PAY002';
case GATEWAY_ERROR = 'PAY003';
case INVALID_CARD = 'PAY004';
public function getValue(): string
{
return $this->value;
}
public function getCategory(): string
{
return 'PAY';
}
public function getNumericCode(): int
{
return (int) substr($this->value, -3);
}
public function getSeverity(): ErrorSeverity
{
return match($this) {
self::GATEWAY_ERROR => ErrorSeverity::CRITICAL,
self::PAYMENT_DECLINED,
self::INSUFFICIENT_FUNDS,
self::INVALID_CARD => ErrorSeverity::ERROR,
};
}
public function getDescription(): string
{
return match($this) {
self::PAYMENT_DECLINED => 'Payment was declined',
self::INSUFFICIENT_FUNDS => 'Insufficient funds for transaction',
self::GATEWAY_ERROR => 'Payment gateway communication error',
self::INVALID_CARD => 'Invalid or expired card information',
};
}
public function getRecoveryHint(): string
{
return match($this) {
self::PAYMENT_DECLINED => 'Contact card issuer or try different payment method',
self::INSUFFICIENT_FUNDS => 'Add funds or use different payment method',
self::GATEWAY_ERROR => 'Retry transaction or contact support',
self::INVALID_CARD => 'Verify card details or use different card',
};
}
public function isRecoverable(): bool
{
return true;
}
public function getRetryAfterSeconds(): ?int
{
return match($this) {
self::GATEWAY_ERROR => 60,
default => null,
};
}
}

View File

@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
use App\Framework\Exception\ErrorCode;
/**
* Performance error codes
*
* Covers performance degradation, resource exhaustion, and optimization issues
*/
enum PerformanceErrorCode: string implements ErrorCode
{
case MEMORY_LIMIT_EXCEEDED = 'PERF001';
case EXECUTION_TIME_EXCEEDED = 'PERF002';
case SLOW_QUERY_DETECTED = 'PERF003';
case HIGH_CPU_USAGE = 'PERF004';
case RESOURCE_EXHAUSTION = 'PERF005';
public function getValue(): string
{
return $this->value;
}
public function getCategory(): string
{
return 'PERF';
}
public function getNumericCode(): int
{
return (int) substr($this->value, -3);
}
public function getSeverity(): ErrorSeverity
{
return match($this) {
self::MEMORY_LIMIT_EXCEEDED,
self::RESOURCE_EXHAUSTION => ErrorSeverity::CRITICAL,
self::EXECUTION_TIME_EXCEEDED,
self::HIGH_CPU_USAGE => ErrorSeverity::ERROR,
self::SLOW_QUERY_DETECTED => ErrorSeverity::WARNING,
};
}
public function getDescription(): string
{
return match($this) {
self::MEMORY_LIMIT_EXCEEDED => 'Memory limit exceeded',
self::EXECUTION_TIME_EXCEEDED => 'Execution time limit exceeded',
self::SLOW_QUERY_DETECTED => 'Slow database query detected',
self::HIGH_CPU_USAGE => 'High CPU usage detected',
self::RESOURCE_EXHAUSTION => 'System resources exhausted',
};
}
public function getRecoveryHint(): string
{
return match($this) {
self::MEMORY_LIMIT_EXCEEDED => 'Increase memory limit or optimize memory usage',
self::EXECUTION_TIME_EXCEEDED => 'Increase timeout or optimize execution',
self::SLOW_QUERY_DETECTED => 'Optimize query or add database indices',
self::HIGH_CPU_USAGE => 'Optimize algorithms or increase CPU resources',
self::RESOURCE_EXHAUSTION => 'Free up resources or increase system capacity',
};
}
public function isRecoverable(): bool
{
return match($this) {
self::MEMORY_LIMIT_EXCEEDED => false,
default => true,
};
}
public function getRetryAfterSeconds(): ?int
{
return match($this) {
self::RESOURCE_EXHAUSTION => 60,
default => null,
};
}
}

View File

@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
use App\Framework\Exception\ErrorCode;
/**
* Queue error codes
*
* Covers queue operations, job processing, and worker management
*/
enum QueueErrorCode: string implements ErrorCode
{
case QUEUE_FULL = 'QUEUE001';
case JOB_FAILED = 'QUEUE002';
case JOB_TIMEOUT = 'QUEUE003';
case WORKER_UNAVAILABLE = 'QUEUE004';
case SERIALIZATION_FAILED = 'QUEUE005';
case DEAD_LETTER_QUEUE_FULL = 'QUEUE006';
case JOB_NOT_FOUND = 'QUEUE007';
case CHAIN_NOT_FOUND = 'QUEUE008';
case INVALID_STATE = 'QUEUE009';
case CIRCULAR_DEPENDENCY = 'QUEUE010';
public function getValue(): string
{
return $this->value;
}
public function getCategory(): string
{
return 'QUEUE';
}
public function getNumericCode(): int
{
return (int) substr($this->value, -3);
}
public function getSeverity(): ErrorSeverity
{
return match($this) {
self::QUEUE_FULL,
self::DEAD_LETTER_QUEUE_FULL => ErrorSeverity::CRITICAL,
self::JOB_FAILED,
self::WORKER_UNAVAILABLE,
self::SERIALIZATION_FAILED,
self::JOB_NOT_FOUND,
self::CHAIN_NOT_FOUND,
self::CIRCULAR_DEPENDENCY => ErrorSeverity::ERROR,
self::JOB_TIMEOUT,
self::INVALID_STATE => ErrorSeverity::WARNING,
};
}
public function getDescription(): string
{
return match($this) {
self::QUEUE_FULL => 'Queue is full and cannot accept new jobs',
self::JOB_FAILED => 'Job execution failed',
self::JOB_TIMEOUT => 'Job execution timed out',
self::WORKER_UNAVAILABLE => 'No workers available to process job',
self::SERIALIZATION_FAILED => 'Job serialization or deserialization failed',
self::DEAD_LETTER_QUEUE_FULL => 'Dead letter queue is full',
self::JOB_NOT_FOUND => 'Job not found in queue or persistence layer',
self::CHAIN_NOT_FOUND => 'Job chain not found',
self::INVALID_STATE => 'Operation not allowed in current state',
self::CIRCULAR_DEPENDENCY => 'Circular dependency detected in job chain',
};
}
public function getRecoveryHint(): string
{
return match($this) {
self::QUEUE_FULL => 'Increase queue capacity or process existing jobs',
self::JOB_FAILED => 'Review job logs and fix underlying issue',
self::JOB_TIMEOUT => 'Increase job timeout or optimize job execution',
self::WORKER_UNAVAILABLE => 'Start more workers or wait for availability',
self::SERIALIZATION_FAILED => 'Check job payload and serialization format',
self::DEAD_LETTER_QUEUE_FULL => 'Process or clear dead letter queue',
self::JOB_NOT_FOUND => 'Verify job ID and check if job was already processed or expired',
self::CHAIN_NOT_FOUND => 'Verify chain ID and check chain existence',
self::INVALID_STATE => 'Check chain/job status and verify operation prerequisites',
self::CIRCULAR_DEPENDENCY => 'Review job dependencies and remove circular references',
};
}
public function isRecoverable(): bool
{
return match($this) {
self::SERIALIZATION_FAILED => false,
default => true,
};
}
public function getRetryAfterSeconds(): ?int
{
return match($this) {
self::QUEUE_FULL => 30,
self::WORKER_UNAVAILABLE => 60,
self::JOB_FAILED => 120,
default => null,
};
}
}

View File

@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
use App\Framework\Exception\ErrorCode;
/**
* Search error codes
*
* Covers search engine operations, indexing, and query processing
*/
enum SearchErrorCode: string implements ErrorCode
{
case INDEX_NOT_FOUND = 'SEARCH001';
case QUERY_FAILED = 'SEARCH002';
case INDEXING_FAILED = 'SEARCH003';
case ENGINE_UNAVAILABLE = 'SEARCH004';
case INVALID_QUERY_SYNTAX = 'SEARCH005';
public function getValue(): string
{
return $this->value;
}
public function getCategory(): string
{
return 'SEARCH';
}
public function getNumericCode(): int
{
return (int) substr($this->value, -3);
}
public function getSeverity(): ErrorSeverity
{
return match($this) {
self::ENGINE_UNAVAILABLE => ErrorSeverity::CRITICAL,
self::QUERY_FAILED,
self::INDEXING_FAILED => ErrorSeverity::ERROR,
self::INDEX_NOT_FOUND,
self::INVALID_QUERY_SYNTAX => ErrorSeverity::WARNING,
};
}
public function getDescription(): string
{
return match($this) {
self::INDEX_NOT_FOUND => 'Search index not found',
self::QUERY_FAILED => 'Search query execution failed',
self::INDEXING_FAILED => 'Document indexing failed',
self::ENGINE_UNAVAILABLE => 'Search engine is unavailable',
self::INVALID_QUERY_SYNTAX => 'Search query syntax is invalid',
};
}
public function getRecoveryHint(): string
{
return match($this) {
self::INDEX_NOT_FOUND => 'Create or rebuild search index',
self::QUERY_FAILED => 'Review query syntax and retry',
self::INDEXING_FAILED => 'Check document format and retry indexing',
self::ENGINE_UNAVAILABLE => 'Check search engine status and configuration',
self::INVALID_QUERY_SYNTAX => 'Correct query syntax according to documentation',
};
}
public function isRecoverable(): bool
{
return true;
}
public function getRetryAfterSeconds(): ?int
{
return match($this) {
self::ENGINE_UNAVAILABLE => 30,
default => null,
};
}
}

View File

@@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
use App\Framework\Exception\ErrorCode;
/**
* Security error codes
*
* Covers security violations, access control, encryption, and threat detection
*/
enum SecurityErrorCode: string implements ErrorCode
{
case ACCESS_DENIED = 'SEC001';
case CSRF_TOKEN_INVALID = 'SEC002';
case ENCRYPTION_FAILED = 'SEC003';
case DECRYPTION_FAILED = 'SEC004';
case SIGNATURE_INVALID = 'SEC005';
case SQL_INJECTION_DETECTED = 'SEC006';
case XSS_DETECTED = 'SEC007';
case PATH_TRAVERSAL_DETECTED = 'SEC008';
case RATE_LIMIT_ABUSE = 'SEC009';
case SUSPICIOUS_ACTIVITY = 'SEC010';
public function getValue(): string
{
return $this->value;
}
public function getCategory(): string
{
return 'SEC';
}
public function getNumericCode(): int
{
return (int) substr($this->value, -3);
}
public function getSeverity(): ErrorSeverity
{
return match($this) {
self::SQL_INJECTION_DETECTED,
self::XSS_DETECTED,
self::PATH_TRAVERSAL_DETECTED,
self::ENCRYPTION_FAILED => ErrorSeverity::CRITICAL,
self::ACCESS_DENIED,
self::CSRF_TOKEN_INVALID,
self::SIGNATURE_INVALID,
self::RATE_LIMIT_ABUSE,
self::SUSPICIOUS_ACTIVITY => ErrorSeverity::ERROR,
self::DECRYPTION_FAILED => ErrorSeverity::WARNING,
};
}
public function getDescription(): string
{
return match($this) {
self::ACCESS_DENIED => 'Access to resource denied',
self::CSRF_TOKEN_INVALID => 'CSRF token is invalid or expired',
self::ENCRYPTION_FAILED => 'Data encryption operation failed',
self::DECRYPTION_FAILED => 'Data decryption operation failed',
self::SIGNATURE_INVALID => 'Digital signature verification failed',
self::SQL_INJECTION_DETECTED => 'SQL injection attempt detected',
self::XSS_DETECTED => 'Cross-site scripting attempt detected',
self::PATH_TRAVERSAL_DETECTED => 'Path traversal attempt detected',
self::RATE_LIMIT_ABUSE => 'Rate limit abuse detected',
self::SUSPICIOUS_ACTIVITY => 'Suspicious activity pattern detected',
};
}
public function getRecoveryHint(): string
{
return match($this) {
self::ACCESS_DENIED => 'Request proper authorization or permissions',
self::CSRF_TOKEN_INVALID => 'Refresh page to obtain new CSRF token',
self::ENCRYPTION_FAILED => 'Check encryption configuration and keys',
self::DECRYPTION_FAILED => 'Verify encryption key and data integrity',
self::SIGNATURE_INVALID => 'Verify signature and signing key',
self::SQL_INJECTION_DETECTED => 'Input sanitization required - contact support',
self::XSS_DETECTED => 'Input sanitization required - contact support',
self::PATH_TRAVERSAL_DETECTED => 'Use valid resource paths only',
self::RATE_LIMIT_ABUSE => 'Reduce request frequency significantly',
self::SUSPICIOUS_ACTIVITY => 'Contact support if you believe this is an error',
};
}
public function isRecoverable(): bool
{
return match($this) {
self::SQL_INJECTION_DETECTED,
self::XSS_DETECTED,
self::PATH_TRAVERSAL_DETECTED => false,
default => true,
};
}
public function getRetryAfterSeconds(): ?int
{
return match($this) {
self::CSRF_TOKEN_INVALID => 0,
self::RATE_LIMIT_ABUSE => 300,
default => null,
};
}
}

View File

@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
use App\Framework\Exception\ErrorCode;
/**
* Service error codes
*
* Covers service layer failures, business logic errors, and service communication
*/
enum ServiceErrorCode: string implements ErrorCode
{
case SERVICE_UNAVAILABLE = 'SVC001';
case OPERATION_FAILED = 'SVC002';
case TIMEOUT = 'SVC003';
case CIRCUIT_BREAKER_OPEN = 'SVC004';
public function getValue(): string
{
return $this->value;
}
public function getCategory(): string
{
return 'SVC';
}
public function getNumericCode(): int
{
return (int) substr($this->value, -3);
}
public function getSeverity(): ErrorSeverity
{
return match($this) {
self::SERVICE_UNAVAILABLE => ErrorSeverity::CRITICAL,
self::OPERATION_FAILED,
self::TIMEOUT,
self::CIRCUIT_BREAKER_OPEN => ErrorSeverity::ERROR,
};
}
public function getDescription(): string
{
return match($this) {
self::SERVICE_UNAVAILABLE => 'Service is unavailable',
self::OPERATION_FAILED => 'Service operation failed',
self::TIMEOUT => 'Service operation timed out',
self::CIRCUIT_BREAKER_OPEN => 'Circuit breaker is open for this service',
};
}
public function getRecoveryHint(): string
{
return match($this) {
self::SERVICE_UNAVAILABLE => 'Check service health and retry later',
self::OPERATION_FAILED => 'Review operation parameters and retry',
self::TIMEOUT => 'Increase timeout or optimize operation',
self::CIRCUIT_BREAKER_OPEN => 'Wait for circuit breaker reset or use fallback',
};
}
public function isRecoverable(): bool
{
return true;
}
public function getRetryAfterSeconds(): ?int
{
return match($this) {
self::SERVICE_UNAVAILABLE => 60,
self::TIMEOUT => 30,
self::CIRCUIT_BREAKER_OPEN => 120,
default => null,
};
}
}

View File

@@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
use App\Framework\Exception\ErrorCode;
/**
* System-level error codes
*
* Covers system configuration, dependencies, and initialization
*/
enum SystemErrorCode: string implements ErrorCode
{
case CONFIG_MISSING = 'SYS001';
case CONFIG_INVALID = 'SYS002';
case DEPENDENCY_MISSING = 'SYS003';
case RESOURCE_EXHAUSTED = 'SYS004';
case INITIALIZATION_FAILED = 'SYS005';
public function getValue(): string
{
return $this->value;
}
public function getCategory(): string
{
return 'SYS';
}
public function getNumericCode(): int
{
return (int) substr($this->value, -3);
}
public function getSeverity(): ErrorSeverity
{
return match($this) {
self::RESOURCE_EXHAUSTED,
self::INITIALIZATION_FAILED => ErrorSeverity::CRITICAL,
self::CONFIG_MISSING,
self::CONFIG_INVALID,
self::DEPENDENCY_MISSING => ErrorSeverity::ERROR,
};
}
public function getDescription(): string
{
return match($this) {
self::CONFIG_MISSING => 'Required configuration is missing',
self::CONFIG_INVALID => 'Configuration contains invalid values',
self::DEPENDENCY_MISSING => 'Required system dependency is not available',
self::RESOURCE_EXHAUSTED => 'System resources are exhausted',
self::INITIALIZATION_FAILED => 'System initialization failed',
};
}
public function getRecoveryHint(): string
{
return match($this) {
self::CONFIG_MISSING => 'Check configuration files and environment variables',
self::CONFIG_INVALID => 'Review configuration values and fix invalid entries',
self::DEPENDENCY_MISSING => 'Install missing dependencies or check system requirements',
self::RESOURCE_EXHAUSTED => 'Free up system resources or increase limits',
self::INITIALIZATION_FAILED => 'Check system startup logs and fix initialization issues',
};
}
public function isRecoverable(): bool
{
return match($this) {
self::CONFIG_MISSING,
self::CONFIG_INVALID,
self::DEPENDENCY_MISSING => false,
default => true,
};
}
public function getRetryAfterSeconds(): ?int
{
return match($this) {
self::RESOURCE_EXHAUSTED => 60,
default => null,
};
}
}

View File

@@ -0,0 +1,107 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
use App\Framework\Exception\ErrorCode;
/**
* Template error codes
*
* Covers template rendering, processing, and view operations
*/
enum TemplateErrorCode: string implements ErrorCode
{
case TEMPLATE_NOT_FOUND = 'TPL001';
case RENDERING_FAILED = 'TPL002';
case SYNTAX_ERROR = 'TPL003';
case PROCESSOR_FAILED = 'TPL004';
case COMPONENT_NOT_FOUND = 'TPL005';
case INVALID_PLACEHOLDER = 'TPL006';
case SLOT_NOT_FOUND = 'TPL007';
case LAYOUT_NOT_FOUND = 'TPL008';
case CIRCULAR_INCLUSION = 'TPL009';
case DATA_BINDING_FAILED = 'TPL010';
public function getValue(): string
{
return $this->value;
}
public function getCategory(): string
{
return 'TPL';
}
public function getNumericCode(): int
{
return (int) substr($this->value, -3);
}
public function getSeverity(): ErrorSeverity
{
return match($this) {
self::RENDERING_FAILED,
self::CIRCULAR_INCLUSION => ErrorSeverity::CRITICAL,
self::SYNTAX_ERROR,
self::PROCESSOR_FAILED,
self::DATA_BINDING_FAILED => ErrorSeverity::ERROR,
self::TEMPLATE_NOT_FOUND,
self::COMPONENT_NOT_FOUND,
self::INVALID_PLACEHOLDER,
self::SLOT_NOT_FOUND,
self::LAYOUT_NOT_FOUND => ErrorSeverity::WARNING,
};
}
public function getDescription(): string
{
return match($this) {
self::TEMPLATE_NOT_FOUND => 'Template file not found',
self::RENDERING_FAILED => 'Template rendering failed',
self::SYNTAX_ERROR => 'Template syntax error detected',
self::PROCESSOR_FAILED => 'Template processor execution failed',
self::COMPONENT_NOT_FOUND => 'Template component not found',
self::INVALID_PLACEHOLDER => 'Invalid placeholder syntax or reference',
self::SLOT_NOT_FOUND => 'Template slot not found',
self::LAYOUT_NOT_FOUND => 'Layout template not found',
self::CIRCULAR_INCLUSION => 'Circular template inclusion detected',
self::DATA_BINDING_FAILED => 'Template data binding failed',
};
}
public function getRecoveryHint(): string
{
return match($this) {
self::TEMPLATE_NOT_FOUND => 'Check template path and file existence',
self::RENDERING_FAILED => 'Review template syntax and data structure',
self::SYNTAX_ERROR => 'Fix template syntax according to documentation',
self::PROCESSOR_FAILED => 'Check processor logs and template content',
self::COMPONENT_NOT_FOUND => 'Verify component name and registration',
self::INVALID_PLACEHOLDER => 'Use correct placeholder syntax {variable}',
self::SLOT_NOT_FOUND => 'Define required slots in template',
self::LAYOUT_NOT_FOUND => 'Create layout template or fix layout path',
self::CIRCULAR_INCLUSION => 'Remove circular template inclusion chain',
self::DATA_BINDING_FAILED => 'Ensure all required data is provided',
};
}
public function isRecoverable(): bool
{
return match($this) {
self::TEMPLATE_NOT_FOUND,
self::COMPONENT_NOT_FOUND,
self::LAYOUT_NOT_FOUND,
self::CIRCULAR_INCLUSION => false,
default => true,
};
}
public function getRetryAfterSeconds(): ?int
{
return null;
}
}

View File

@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
use App\Framework\Exception\ErrorCode;
/**
* Validation error codes
*
* Covers input validation, business rule violations, and data integrity checks
*/
enum ValidationErrorCode: string implements ErrorCode
{
case INVALID_INPUT = 'VAL001';
case REQUIRED_FIELD_MISSING = 'VAL002';
case INVALID_FORMAT = 'VAL003';
case VALUE_OUT_OF_RANGE = 'VAL004';
case DUPLICATE_VALUE = 'VAL005';
case BUSINESS_RULE_VIOLATION = 'VAL006';
case INVALID_STATE_TRANSITION = 'VAL007';
case VALIDATION_FAILED = 'VAL008';
public function getValue(): string
{
return $this->value;
}
public function getCategory(): string
{
return 'VAL';
}
public function getNumericCode(): int
{
return (int) substr($this->value, -3);
}
public function getSeverity(): ErrorSeverity
{
return match($this) {
self::BUSINESS_RULE_VIOLATION,
self::INVALID_STATE_TRANSITION => ErrorSeverity::ERROR,
self::INVALID_INPUT,
self::REQUIRED_FIELD_MISSING,
self::INVALID_FORMAT,
self::VALUE_OUT_OF_RANGE,
self::DUPLICATE_VALUE,
self::VALIDATION_FAILED => ErrorSeverity::WARNING,
};
}
public function getDescription(): string
{
return match($this) {
self::INVALID_INPUT => 'Input data is invalid',
self::REQUIRED_FIELD_MISSING => 'Required field is missing',
self::INVALID_FORMAT => 'Data format is invalid',
self::VALUE_OUT_OF_RANGE => 'Value is outside acceptable range',
self::DUPLICATE_VALUE => 'Duplicate value detected',
self::BUSINESS_RULE_VIOLATION => 'Business rule violation occurred',
self::INVALID_STATE_TRANSITION => 'Invalid state transition attempted',
self::VALIDATION_FAILED => 'Validation check failed',
};
}
public function getRecoveryHint(): string
{
return match($this) {
self::INVALID_INPUT => 'Check input data format and values',
self::REQUIRED_FIELD_MISSING => 'Provide all required fields',
self::INVALID_FORMAT => 'Correct data format according to specification',
self::VALUE_OUT_OF_RANGE => 'Provide value within acceptable range',
self::DUPLICATE_VALUE => 'Use unique value or update existing entry',
self::BUSINESS_RULE_VIOLATION => 'Review business rules and adjust input',
self::INVALID_STATE_TRANSITION => 'Check current state and allowed transitions',
self::VALIDATION_FAILED => 'Review validation errors and correct input',
};
}
public function isRecoverable(): bool
{
return true;
}
public function getRetryAfterSeconds(): ?int
{
return null;
}
}

View File

@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
use App\Framework\Exception\ErrorCode;
/**
* Value Object error codes
*
* Covers value object validation, construction, and transformation
*/
enum ValueObjectErrorCode: string implements ErrorCode
{
case INVALID_VALUE = 'VO001';
case VALIDATION_FAILED = 'VO002';
case TRANSFORMATION_FAILED = 'VO003';
case IMMUTABILITY_VIOLATION = 'VO004';
public function getValue(): string
{
return $this->value;
}
public function getCategory(): string
{
return 'VO';
}
public function getNumericCode(): int
{
return (int) substr($this->value, -3);
}
public function getSeverity(): ErrorSeverity
{
return match($this) {
self::IMMUTABILITY_VIOLATION => ErrorSeverity::CRITICAL,
self::INVALID_VALUE,
self::VALIDATION_FAILED,
self::TRANSFORMATION_FAILED => ErrorSeverity::ERROR,
};
}
public function getDescription(): string
{
return match($this) {
self::INVALID_VALUE => 'Value object contains invalid value',
self::VALIDATION_FAILED => 'Value object validation failed',
self::TRANSFORMATION_FAILED => 'Value object transformation failed',
self::IMMUTABILITY_VIOLATION => 'Attempt to modify immutable value object',
};
}
public function getRecoveryHint(): string
{
return match($this) {
self::INVALID_VALUE => 'Provide valid value according to value object constraints',
self::VALIDATION_FAILED => 'Review validation rules and correct input',
self::TRANSFORMATION_FAILED => 'Check transformation logic and input format',
self::IMMUTABILITY_VIOLATION => 'Create new value object instance instead of modifying',
};
}
public function isRecoverable(): bool
{
return match($this) {
self::IMMUTABILITY_VIOLATION => false,
default => true,
};
}
public function getRetryAfterSeconds(): ?int
{
return null;
}
}

View File

@@ -21,7 +21,7 @@ class DatabaseException extends FrameworkException
$message = $details ? "Database connection failed: $details" : null;
return static::create(
ErrorCode::DB_CONNECTION_FAILED,
Core\DatabaseErrorCode::CONNECTION_FAILED,
$message,
$context,
$previous
@@ -38,7 +38,7 @@ class DatabaseException extends FrameworkException
->withDebug(['query_length' => strlen($sql)]);
return static::create(
ErrorCode::DB_QUERY_FAILED,
Core\DatabaseErrorCode::QUERY_FAILED,
"Query execution failed: $error",
$context,
$previous
@@ -59,7 +59,7 @@ class DatabaseException extends FrameworkException
]);
return static::create(
ErrorCode::DB_CONSTRAINT_VIOLATION,
Core\DatabaseErrorCode::CONSTRAINT_VIOLATION,
"Database constraint violation: $constraint" . ($table ? " on table $table" : ''),
$context,
$previous
@@ -75,7 +75,7 @@ class DatabaseException extends FrameworkException
->withData(['details' => $details, 'operations' => $operations]);
return static::create(
ErrorCode::DB_TRANSACTION_FAILED,
Core\DatabaseErrorCode::TRANSACTION_FAILED,
"Transaction failed: $details",
$context,
$previous
@@ -95,7 +95,7 @@ class DatabaseException extends FrameworkException
]);
return static::create(
ErrorCode::DB_POOL_EXHAUSTED,
Core\DatabaseErrorCode::POOL_EXHAUSTED,
"Connection pool exhausted ($currentConnections/$maxConnections)",
$context,
$previous
@@ -116,7 +116,7 @@ class DatabaseException extends FrameworkException
]);
return static::create(
ErrorCode::DB_TIMEOUT,
Core\DatabaseErrorCode::TIMEOUT,
"Database operation '$operation' timed out after {$timeoutSeconds}s",
$context,
$previous
@@ -137,7 +137,7 @@ class DatabaseException extends FrameworkException
]);
return static::create(
ErrorCode::DB_MIGRATION_FAILED,
Core\DatabaseErrorCode::MIGRATION_FAILED,
"Migration '$migrationName' failed: $error",
$context,
$previous

View File

@@ -4,592 +4,56 @@ declare(strict_types=1);
namespace App\Framework\Exception;
use App\Framework\Exception\Core\ErrorSeverity;
/**
* Systematische Error Codes für bessere Fehlerklassifizierung
* Error code interface for standardized error classification
*
* Provides consistent error handling across all error categories:
* - Severity classification for retention and alerting
* - Human-readable descriptions
* - Recovery hints for troubleshooting
* - Retry strategies for recoverable errors
*/
enum ErrorCode: string
interface ErrorCode
{
// System-Level Errors (SYS)
case SYSTEM_CONFIG_MISSING = 'SYS001';
case SYSTEM_CONFIG_INVALID = 'SYS002';
case SYSTEM_DEPENDENCY_MISSING = 'SYS003';
case SYSTEM_RESOURCE_EXHAUSTED = 'SYS004';
case SYSTEM_INITIALIZATION_FAILED = 'SYS005';
// Database Errors (DB)
case DB_CONNECTION_FAILED = 'DB001';
case DB_QUERY_FAILED = 'DB002';
case DB_CONSTRAINT_VIOLATION = 'DB003';
case DB_TRANSACTION_FAILED = 'DB004';
case DB_MIGRATION_FAILED = 'DB005';
case DB_MIGRATION_ROLLBACK_FAILED = 'DB006';
case DB_MIGRATION_TABLE_CREATION_FAILED = 'DB007';
case DB_MIGRATION_DEPENDENCY_ERROR = 'DB008';
case DB_MIGRATION_PREFLIGHT_FAILED = 'DB009';
case DB_POOL_EXHAUSTED = 'DB010';
case DB_TIMEOUT = 'DB011';
// Authentication Errors (AUTH)
case AUTH_CREDENTIALS_INVALID = 'AUTH001';
case AUTH_TOKEN_EXPIRED = 'AUTH002';
case AUTH_TOKEN_INVALID = 'AUTH003';
case AUTH_USER_LOCKED = 'AUTH004';
case AUTH_SESSION_EXPIRED = 'AUTH005';
case AUTH_INSUFFICIENT_PRIVILEGES = 'AUTH006';
case AUTH_UNAUTHORIZED = 'AUTH007';
// Validation Errors (VAL)
case VAL_REQUIRED_FIELD_MISSING = 'VAL001';
case VAL_INVALID_FORMAT = 'VAL002';
case VAL_OUT_OF_RANGE = 'VAL003';
case VAL_DUPLICATE_VALUE = 'VAL004';
case VAL_BUSINESS_RULE_VIOLATION = 'VAL005';
case VAL_INVALID_INPUT = 'VAL006';
case VAL_UNSUPPORTED_OPERATION = 'VAL007';
case VAL_INVALID_ARGUMENT = 'VAL008';
// HTTP Errors (HTTP)
case HTTP_NOT_FOUND = 'HTTP001';
case HTTP_METHOD_NOT_ALLOWED = 'HTTP002';
case HTTP_RATE_LIMIT_EXCEEDED = 'HTTP003';
case HTTP_PAYLOAD_TOO_LARGE = 'HTTP004';
case HTTP_UNSUPPORTED_MEDIA_TYPE = 'HTTP005';
// Security Errors (SEC)
case SEC_XSS_ATTEMPT = 'SEC001';
case SEC_SQL_INJECTION_ATTEMPT = 'SEC002';
case SEC_PATH_TRAVERSAL_ATTEMPT = 'SEC003';
case SEC_CSRF_TOKEN_MISMATCH = 'SEC004';
case SEC_UNAUTHORIZED_ACCESS = 'SEC005';
case SEC_SUSPICIOUS_ACTIVITY = 'SEC006';
case SEC_FILE_UPLOAD_REJECTED = 'SEC007';
// Cache Errors (CACHE)
case CACHE_CONNECTION_FAILED = 'CACHE001';
case CACHE_KEY_NOT_FOUND = 'CACHE002';
case CACHE_SERIALIZATION_FAILED = 'CACHE003';
case CACHE_EVICTION_FAILED = 'CACHE004';
// File System Errors (FS)
case FS_FILE_NOT_FOUND = 'FS001';
case FS_PERMISSION_DENIED = 'FS002';
case FS_DISK_FULL = 'FS003';
case FS_DIRECTORY_NOT_WRITABLE = 'FS004';
case FS_UPLOAD_FAILED = 'FS005';
// External API Errors (API)
case API_SERVICE_UNAVAILABLE = 'API001';
case API_RATE_LIMIT_EXCEEDED = 'API002';
case API_AUTHENTICATION_FAILED = 'API003';
case API_INVALID_RESPONSE = 'API004';
case API_TIMEOUT = 'API005';
// Payment Errors (PAY)
case PAYMENT_GATEWAY_ERROR = 'PAY001';
case PAYMENT_INSUFFICIENT_FUNDS = 'PAY002';
case PAYMENT_CARD_DECLINED = 'PAY003';
case PAYMENT_PROCESSING_FAILED = 'PAY004';
// Service Errors (SVC)
case SERVICE_CIRCUIT_OPEN = 'SVC001';
case SERVICE_CIRCUIT_HALF_OPEN = 'SVC002';
case SERVICE_HEALTH_CHECK_FAILED = 'SVC003';
case SERVICE_DEGRADED = 'SVC004';
// Business Logic Errors (BIZ)
case BIZ_WORKFLOW_VIOLATION = 'BIZ001';
case BIZ_INSUFFICIENT_BALANCE = 'BIZ002';
case BIZ_OPERATION_NOT_ALLOWED = 'BIZ003';
case BIZ_QUOTA_EXCEEDED = 'BIZ004';
// Search Errors (SEARCH)
case SEARCH_INDEX_FAILED = 'SEARCH001';
case SEARCH_UPDATE_FAILED = 'SEARCH002';
case SEARCH_DELETE_FAILED = 'SEARCH003';
case SEARCH_CONFIG_INVALID = 'SEARCH004';
case SEARCH_ENGINE_UNAVAILABLE = 'SEARCH005';
// Console Application Errors (CON)
case CON_COMMAND_NOT_FOUND = 'CON001';
case CON_INVALID_ARGUMENTS = 'CON002';
case CON_COMMAND_EXECUTION_FAILED = 'CON003';
case CON_SIGNAL_INTERRUPTED = 'CON004';
case CON_ARGUMENT_TOO_LONG = 'CON005';
case CON_INVALID_COMMAND_STRUCTURE = 'CON006';
case CON_PERMISSION_DENIED = 'CON007';
case CON_TIMEOUT = 'CON008';
// Entity/Resource Errors (ENT)
case ENTITY_NOT_FOUND = 'ENT001';
case ENTITY_ALREADY_EXISTS = 'ENT002';
case ENTITY_VALIDATION_FAILED = 'ENT003';
case ENTITY_READONLY = 'ENT004';
case ENTITY_RELATIONSHIP_VIOLATION = 'ENT005';
case ENTITY_STATE_INVALID = 'ENT006';
// DI Container Errors (DI)
case DI_BINDING_NOT_FOUND = 'DI001';
case DI_CIRCULAR_DEPENDENCY = 'DI002';
case DI_INSTANTIATION_FAILED = 'DI003';
case DI_INVALID_BINDING = 'DI004';
case DI_SINGLETON_VIOLATION = 'DI005';
// MCP Integration Errors (MCP)
case MCP_SERVER_UNAVAILABLE = 'MCP001';
case MCP_TOOL_NOT_FOUND = 'MCP002';
case MCP_INVALID_REQUEST = 'MCP003';
case MCP_RESOURCE_NOT_FOUND = 'MCP004';
case MCP_PROTOCOL_ERROR = 'MCP005';
case MCP_TIMEOUT = 'MCP006';
// Queue/Background Job Errors (QUEUE)
case QUEUE_CONNECTION_FAILED = 'QUEUE001';
case QUEUE_JOB_FAILED = 'QUEUE002';
case QUEUE_SERIALIZATION_FAILED = 'QUEUE003';
case QUEUE_MAX_RETRIES_EXCEEDED = 'QUEUE004';
case QUEUE_WORKER_UNAVAILABLE = 'QUEUE005';
case QUEUE_TIMEOUT = 'QUEUE006';
// Performance/Monitoring Errors (PERF)
case PERF_MEMORY_LIMIT_EXCEEDED = 'PERF001';
case PERF_EXECUTION_TIMEOUT = 'PERF002';
case PERF_CIRCUIT_BREAKER_OPEN = 'PERF003';
case PERF_METRIC_COLLECTION_FAILED = 'PERF004';
case PERF_THRESHOLD_EXCEEDED = 'PERF005';
// Discovery System Errors (DISC)
case DISC_ATTRIBUTE_SCAN_FAILED = 'DISC001';
case DISC_INVALID_ATTRIBUTE = 'DISC002';
case DISC_CACHE_CORRUPTION = 'DISC003';
case DISC_REFLECTION_FAILED = 'DISC004';
case DISC_REGISTRATION_FAILED = 'DISC005';
// Event System Errors (EVENT)
case EVENT_HANDLER_NOT_FOUND = 'EVENT001';
case EVENT_DISPATCH_FAILED = 'EVENT002';
case EVENT_LISTENER_FAILED = 'EVENT003';
case EVENT_SERIALIZATION_FAILED = 'EVENT004';
case EVENT_TIMEOUT = 'EVENT005';
// Template/View Errors (TPL)
case TPL_TEMPLATE_NOT_FOUND = 'TPL001';
case TPL_SYNTAX_ERROR = 'TPL002';
case TPL_VARIABLE_NOT_FOUND = 'TPL003';
case TPL_COMPILATION_FAILED = 'TPL004';
case TPL_RENDERING_FAILED = 'TPL005';
case TPL_CACHE_FAILED = 'TPL006';
case TPL_CONTENT_LOADING_FAILED = 'TPL007';
case TPL_PROCESSOR_FAILED = 'TPL008';
case TPL_ASSET_NOT_FOUND = 'TPL009';
case TPL_INVALID_TEMPLATE_TYPE = 'TPL010';
// Value Object Errors (VO)
case VO_INVALID_VALUE = 'VO001';
case VO_TRANSFORMATION_FAILED = 'VO002';
case VO_COMPARISON_FAILED = 'VO003';
case VO_SERIALIZATION_FAILED = 'VO004';
case SYS_INITIALIZATION_FAILED = 'SYS006';
public function getCategory(): string
{
return substr($this->value, 0, strpos($this->value, '0') ?: 3);
}
public function getNumericCode(): int
{
return (int) substr($this->value, -3);
}
public function getDescription(): string
{
return match($this) {
// System Errors
self::SYSTEM_CONFIG_MISSING => 'Required configuration is missing',
self::SYSTEM_CONFIG_INVALID => 'Configuration contains invalid values',
self::SYSTEM_DEPENDENCY_MISSING => 'Required system dependency is not available',
self::SYSTEM_RESOURCE_EXHAUSTED => 'System resources are exhausted',
self::SYSTEM_INITIALIZATION_FAILED => 'System initialization failed',
// Console Errors
self::CON_COMMAND_NOT_FOUND => 'Console command not found',
self::CON_INVALID_ARGUMENTS => 'Invalid command arguments provided',
self::CON_COMMAND_EXECUTION_FAILED => 'Command execution failed',
self::CON_SIGNAL_INTERRUPTED => 'Command interrupted by signal',
self::CON_ARGUMENT_TOO_LONG => 'Command argument exceeds maximum length',
self::CON_INVALID_COMMAND_STRUCTURE => 'Invalid command structure or configuration',
self::CON_PERMISSION_DENIED => 'Insufficient permissions to execute command',
self::CON_TIMEOUT => 'Command execution timed out',
// Entity Errors
self::ENTITY_NOT_FOUND => 'Requested entity not found',
self::ENTITY_ALREADY_EXISTS => 'Entity with this identifier already exists',
self::ENTITY_VALIDATION_FAILED => 'Entity validation failed',
self::ENTITY_READONLY => 'Entity is readonly and cannot be modified',
self::ENTITY_RELATIONSHIP_VIOLATION => 'Entity relationship constraint violation',
self::ENTITY_STATE_INVALID => 'Entity is in invalid state for this operation',
// DI Container Errors
self::DI_BINDING_NOT_FOUND => 'No binding found for requested service',
self::DI_CIRCULAR_DEPENDENCY => 'Circular dependency detected',
self::DI_INSTANTIATION_FAILED => 'Failed to instantiate requested service',
self::DI_INVALID_BINDING => 'Invalid service binding configuration',
self::DI_SINGLETON_VIOLATION => 'Singleton constraint violation',
// MCP Integration Errors
self::MCP_SERVER_UNAVAILABLE => 'MCP server is unavailable',
self::MCP_TOOL_NOT_FOUND => 'MCP tool not found',
self::MCP_INVALID_REQUEST => 'Invalid MCP request format',
self::MCP_RESOURCE_NOT_FOUND => 'MCP resource not found',
self::MCP_PROTOCOL_ERROR => 'MCP protocol error',
self::MCP_TIMEOUT => 'MCP request timed out',
// Queue Errors
self::QUEUE_CONNECTION_FAILED => 'Queue connection failed',
self::QUEUE_JOB_FAILED => 'Background job execution failed',
self::QUEUE_SERIALIZATION_FAILED => 'Job serialization failed',
self::QUEUE_MAX_RETRIES_EXCEEDED => 'Maximum job retries exceeded',
self::QUEUE_WORKER_UNAVAILABLE => 'No queue workers available',
self::QUEUE_TIMEOUT => 'Queue operation timed out',
// Performance Errors
self::PERF_MEMORY_LIMIT_EXCEEDED => 'Memory limit exceeded',
self::PERF_EXECUTION_TIMEOUT => 'Execution timeout exceeded',
self::PERF_CIRCUIT_BREAKER_OPEN => 'Circuit breaker is open',
self::PERF_METRIC_COLLECTION_FAILED => 'Performance metric collection failed',
self::PERF_THRESHOLD_EXCEEDED => 'Performance threshold exceeded',
// Discovery System Errors
self::DISC_ATTRIBUTE_SCAN_FAILED => 'Attribute scanning failed',
self::DISC_INVALID_ATTRIBUTE => 'Invalid attribute configuration',
self::DISC_CACHE_CORRUPTION => 'Discovery cache corruption detected',
self::DISC_REFLECTION_FAILED => 'Reflection operation failed',
self::DISC_REGISTRATION_FAILED => 'Component registration failed',
// Event System Errors
self::EVENT_HANDLER_NOT_FOUND => 'Event handler not found',
self::EVENT_DISPATCH_FAILED => 'Event dispatch failed',
self::EVENT_LISTENER_FAILED => 'Event listener execution failed',
self::EVENT_SERIALIZATION_FAILED => 'Event serialization failed',
self::EVENT_TIMEOUT => 'Event processing timed out',
// Template Errors
self::TPL_TEMPLATE_NOT_FOUND => 'Template file not found',
self::TPL_SYNTAX_ERROR => 'Template syntax error',
self::TPL_VARIABLE_NOT_FOUND => 'Template variable not found',
self::TPL_COMPILATION_FAILED => 'Template compilation failed',
self::TPL_RENDERING_FAILED => 'Template rendering failed',
self::TPL_CACHE_FAILED => 'Template cache operation failed',
self::TPL_CONTENT_LOADING_FAILED => 'Template content loading failed',
self::TPL_PROCESSOR_FAILED => 'Template processor execution failed',
self::TPL_ASSET_NOT_FOUND => 'Template asset file not found',
self::TPL_INVALID_TEMPLATE_TYPE => 'Invalid template type or format',
// Value Object Errors
self::VO_INVALID_VALUE => 'Invalid value for value object',
self::VO_TRANSFORMATION_FAILED => 'Value object transformation failed',
self::VO_COMPARISON_FAILED => 'Value object comparison failed',
self::VO_SERIALIZATION_FAILED => 'Value object serialization failed',
// Database Errors
self::DB_CONNECTION_FAILED => 'Database connection could not be established',
self::DB_QUERY_FAILED => 'Database query execution failed',
self::DB_CONSTRAINT_VIOLATION => 'Database constraint violation occurred',
self::DB_TRANSACTION_FAILED => 'Database transaction failed',
self::DB_MIGRATION_FAILED => 'Database migration failed',
self::DB_MIGRATION_DEPENDENCY_ERROR => 'Migration dependency validation failed',
self::DB_POOL_EXHAUSTED => 'Database connection pool exhausted',
self::DB_TIMEOUT => 'Database operation timed out',
// Authentication Errors
self::AUTH_CREDENTIALS_INVALID => 'Provided credentials are invalid',
self::AUTH_TOKEN_EXPIRED => 'Authentication token has expired',
self::AUTH_TOKEN_INVALID => 'Authentication token is invalid',
self::AUTH_USER_LOCKED => 'User account is locked',
self::AUTH_SESSION_EXPIRED => 'User session has expired',
self::AUTH_INSUFFICIENT_PRIVILEGES => 'Insufficient privileges for this operation',
self::AUTH_UNAUTHORIZED => 'Unauthorized access attempt',
// Validation Errors
self::VAL_REQUIRED_FIELD_MISSING => 'Required field is missing',
self::VAL_INVALID_FORMAT => 'Field has invalid format',
self::VAL_OUT_OF_RANGE => 'Value is out of allowed range',
self::VAL_DUPLICATE_VALUE => 'Value already exists',
self::VAL_BUSINESS_RULE_VIOLATION => 'Business rule violation',
self::VAL_INVALID_INPUT => 'Invalid input data provided',
// HTTP Errors
self::HTTP_NOT_FOUND => 'Requested resource not found',
self::HTTP_METHOD_NOT_ALLOWED => 'HTTP method not allowed',
self::HTTP_RATE_LIMIT_EXCEEDED => 'Rate limit exceeded',
self::HTTP_PAYLOAD_TOO_LARGE => 'Request payload too large',
self::HTTP_UNSUPPORTED_MEDIA_TYPE => 'Unsupported media type',
// Security Errors
self::SEC_XSS_ATTEMPT => 'Cross-site scripting attempt detected',
self::SEC_SQL_INJECTION_ATTEMPT => 'SQL injection attempt detected',
self::SEC_PATH_TRAVERSAL_ATTEMPT => 'Path traversal attempt detected',
self::SEC_CSRF_TOKEN_MISMATCH => 'CSRF token mismatch',
self::SEC_UNAUTHORIZED_ACCESS => 'Unauthorized access attempt',
self::SEC_SUSPICIOUS_ACTIVITY => 'Suspicious activity detected',
self::SEC_FILE_UPLOAD_REJECTED => 'File upload rejected by security validation',
// Cache Errors
self::CACHE_CONNECTION_FAILED => 'Cache connection failed',
self::CACHE_KEY_NOT_FOUND => 'Cache key not found',
self::CACHE_SERIALIZATION_FAILED => 'Cache serialization failed',
self::CACHE_EVICTION_FAILED => 'Cache eviction failed',
// File System Errors
self::FS_FILE_NOT_FOUND => 'File not found',
self::FS_PERMISSION_DENIED => 'File permission denied',
self::FS_DISK_FULL => 'Disk space exhausted',
self::FS_DIRECTORY_NOT_WRITABLE => 'Directory is not writable',
self::FS_UPLOAD_FAILED => 'File upload failed',
// External API Errors
self::API_SERVICE_UNAVAILABLE => 'External service unavailable',
self::API_RATE_LIMIT_EXCEEDED => 'API rate limit exceeded',
self::API_AUTHENTICATION_FAILED => 'API authentication failed',
self::API_INVALID_RESPONSE => 'Invalid API response received',
self::API_TIMEOUT => 'API request timed out',
// Business Logic Errors
self::BIZ_WORKFLOW_VIOLATION => 'Business workflow violation',
self::BIZ_INSUFFICIENT_BALANCE => 'Insufficient balance for operation',
self::BIZ_OPERATION_NOT_ALLOWED => 'Operation not allowed in current state',
self::BIZ_QUOTA_EXCEEDED => 'Usage quota exceeded',
// Service Errors
self::SERVICE_CIRCUIT_OPEN => 'Service circuit breaker is open',
self::SERVICE_CIRCUIT_HALF_OPEN => 'Service circuit breaker is half-open',
self::SERVICE_HEALTH_CHECK_FAILED => 'Service health check failed',
self::SERVICE_DEGRADED => 'Service is in degraded state',
// Search Errors
self::SEARCH_INDEX_FAILED => 'Failed to index document in search engine',
self::SEARCH_UPDATE_FAILED => 'Failed to update document in search engine',
self::SEARCH_DELETE_FAILED => 'Failed to delete document from search engine',
self::SEARCH_CONFIG_INVALID => 'Invalid search configuration',
self::SEARCH_ENGINE_UNAVAILABLE => 'Search engine is unavailable',
};
}
public function getRecoveryHint(): string
{
return match($this) {
// System Errors
self::SYSTEM_CONFIG_MISSING => 'Check configuration files and environment variables',
self::SYSTEM_CONFIG_INVALID => 'Review configuration values and fix invalid entries',
self::SYSTEM_DEPENDENCY_MISSING => 'Install missing dependencies or check system requirements',
self::SYSTEM_RESOURCE_EXHAUSTED => 'Free up system resources or increase limits',
self::SYSTEM_INITIALIZATION_FAILED => 'Check system startup logs and fix initialization issues',
// Console Errors
self::CON_COMMAND_NOT_FOUND => 'Check command name spelling or use help to list available commands',
self::CON_INVALID_ARGUMENTS => 'Review command usage and provide valid arguments',
self::CON_COMMAND_EXECUTION_FAILED => 'Check command implementation and logs for errors',
self::CON_SIGNAL_INTERRUPTED => 'Command was interrupted, restart if needed',
self::CON_ARGUMENT_TOO_LONG => 'Reduce argument length to under 4096 characters',
self::CON_INVALID_COMMAND_STRUCTURE => 'Review command class and method configuration',
self::CON_PERMISSION_DENIED => 'Run with appropriate permissions or contact administrator',
self::CON_TIMEOUT => 'Optimize command performance or increase timeout limits',
// Entity Errors
self::ENTITY_NOT_FOUND => 'Verify entity identifier and check if entity exists',
self::ENTITY_ALREADY_EXISTS => 'Use different identifier or update existing entity',
self::ENTITY_VALIDATION_FAILED => 'Fix validation errors in entity data',
self::ENTITY_READONLY => 'Entity cannot be modified in current state',
self::ENTITY_RELATIONSHIP_VIOLATION => 'Fix relationship constraints before proceeding',
self::ENTITY_STATE_INVALID => 'Ensure entity is in valid state for operation',
// DI Container Errors
self::DI_BINDING_NOT_FOUND => 'Register service binding in container configuration',
self::DI_CIRCULAR_DEPENDENCY => 'Review service dependencies and remove circular references',
self::DI_INSTANTIATION_FAILED => 'Check service constructor requirements and dependencies',
self::DI_INVALID_BINDING => 'Review binding configuration and ensure correct syntax',
self::DI_SINGLETON_VIOLATION => 'Ensure singleton services are properly configured',
// MCP Integration Errors
self::MCP_SERVER_UNAVAILABLE => 'Check MCP server status and connection settings',
self::MCP_TOOL_NOT_FOUND => 'Verify MCP tool name and availability',
self::MCP_INVALID_REQUEST => 'Review MCP request format and parameters',
self::MCP_RESOURCE_NOT_FOUND => 'Check MCP resource path and availability',
self::MCP_PROTOCOL_ERROR => 'Review MCP protocol implementation and version compatibility',
self::MCP_TIMEOUT => 'Increase timeout or optimize MCP operation',
// Queue Errors
self::QUEUE_CONNECTION_FAILED => 'Check queue server status and connection settings',
self::QUEUE_JOB_FAILED => 'Review job implementation and error logs',
self::QUEUE_SERIALIZATION_FAILED => 'Ensure job data is serializable',
self::QUEUE_MAX_RETRIES_EXCEEDED => 'Fix underlying issue or increase retry limit',
self::QUEUE_WORKER_UNAVAILABLE => 'Start queue workers or check worker health',
self::QUEUE_TIMEOUT => 'Optimize job performance or increase timeout',
// Performance Errors
self::PERF_MEMORY_LIMIT_EXCEEDED => 'Optimize memory usage or increase memory limit',
self::PERF_EXECUTION_TIMEOUT => 'Optimize code performance or increase timeout',
self::PERF_CIRCUIT_BREAKER_OPEN => 'Wait for circuit breaker to close or fix underlying issue',
self::PERF_METRIC_COLLECTION_FAILED => 'Check monitoring system and metric collectors',
self::PERF_THRESHOLD_EXCEEDED => 'Optimize performance or adjust thresholds',
// Discovery System Errors
self::DISC_ATTRIBUTE_SCAN_FAILED => 'Check class files and attribute syntax',
self::DISC_INVALID_ATTRIBUTE => 'Review attribute configuration and parameters',
self::DISC_CACHE_CORRUPTION => 'Clear discovery cache and regenerate',
self::DISC_REFLECTION_FAILED => 'Check class structure and accessibility',
self::DISC_REGISTRATION_FAILED => 'Review component registration process',
// Event System Errors
self::EVENT_HANDLER_NOT_FOUND => 'Register event handler or check handler name',
self::EVENT_DISPATCH_FAILED => 'Review event dispatcher configuration',
self::EVENT_LISTENER_FAILED => 'Check event listener implementation',
self::EVENT_SERIALIZATION_FAILED => 'Ensure event data is serializable',
self::EVENT_TIMEOUT => 'Optimize event processing or increase timeout',
// Template Errors
self::TPL_TEMPLATE_NOT_FOUND => 'Check template path and file existence',
self::TPL_SYNTAX_ERROR => 'Review template syntax and fix errors',
self::TPL_VARIABLE_NOT_FOUND => 'Provide required template variables',
self::TPL_COMPILATION_FAILED => 'Check template compiler configuration',
self::TPL_RENDERING_FAILED => 'Review template data and rendering context',
self::TPL_CACHE_FAILED => 'Check template cache permissions and storage availability',
self::TPL_CONTENT_LOADING_FAILED => 'Verify template file permissions and filesystem access',
self::TPL_PROCESSOR_FAILED => 'Check template processor configuration and dependencies',
self::TPL_ASSET_NOT_FOUND => 'Verify asset file path and build process',
self::TPL_INVALID_TEMPLATE_TYPE => 'Use supported template format or check template extension',
// Value Object Errors
self::VO_INVALID_VALUE => 'Provide valid value according to value object constraints',
self::VO_TRANSFORMATION_FAILED => 'Check transformation logic and input data',
self::VO_COMPARISON_FAILED => 'Ensure value objects are comparable',
self::VO_SERIALIZATION_FAILED => 'Review serialization implementation',
// Database Errors
self::DB_CONNECTION_FAILED => 'Check database server status and connection settings',
self::DB_QUERY_FAILED => 'Review query syntax and database schema',
self::DB_CONSTRAINT_VIOLATION => 'Check data integrity and constraint definitions',
self::DB_TRANSACTION_FAILED => 'Retry transaction or check for deadlocks',
self::DB_MIGRATION_FAILED => 'Review migration scripts and database state',
self::DB_MIGRATION_DEPENDENCY_ERROR => 'Add missing dependency migrations or review dependency chains',
self::DB_POOL_EXHAUSTED => 'Increase connection pool size or optimize queries',
self::DB_TIMEOUT => 'Optimize query performance or increase timeout limits',
// Authentication Errors
self::AUTH_CREDENTIALS_INVALID => 'Verify username and password',
self::AUTH_TOKEN_EXPIRED => 'Refresh authentication token',
self::AUTH_TOKEN_INVALID => 'Obtain new authentication token',
self::AUTH_USER_LOCKED => 'Contact administrator to unlock account',
self::AUTH_SESSION_EXPIRED => 'Log in again to create new session',
self::AUTH_INSUFFICIENT_PRIVILEGES => 'Request appropriate permissions from administrator',
self::AUTH_UNAUTHORIZED => 'Authenticate with valid credentials and proper authorization',
// Validation Errors
self::VAL_REQUIRED_FIELD_MISSING => 'Provide value for required field',
self::VAL_INVALID_FORMAT => 'Correct field format according to requirements',
self::VAL_OUT_OF_RANGE => 'Provide value within allowed range',
self::VAL_DUPLICATE_VALUE => 'Use unique value that does not already exist',
self::VAL_BUSINESS_RULE_VIOLATION => 'Follow business rules and constraints',
self::VAL_INVALID_INPUT => 'Validate and correct input data format and content',
// HTTP Errors
self::HTTP_NOT_FOUND => 'Check URL and ensure resource exists',
self::HTTP_METHOD_NOT_ALLOWED => 'Use correct HTTP method for this endpoint',
self::HTTP_RATE_LIMIT_EXCEEDED => 'Reduce request frequency or wait before retrying',
self::HTTP_PAYLOAD_TOO_LARGE => 'Reduce request payload size',
self::HTTP_UNSUPPORTED_MEDIA_TYPE => 'Use supported content type',
// Security Errors
self::SEC_XSS_ATTEMPT => 'Review input validation and output encoding',
self::SEC_SQL_INJECTION_ATTEMPT => 'Use parameterized queries and input validation',
self::SEC_PATH_TRAVERSAL_ATTEMPT => 'Validate and sanitize file paths',
self::SEC_CSRF_TOKEN_MISMATCH => 'Include valid CSRF token in request',
self::SEC_UNAUTHORIZED_ACCESS => 'Authenticate and ensure proper authorization',
self::SEC_SUSPICIOUS_ACTIVITY => 'Review security logs and investigate activity',
self::SEC_FILE_UPLOAD_REJECTED => 'Check file type, size, and content requirements',
// Search Errors
self::SEARCH_INDEX_FAILED => 'Check search engine connectivity and document format',
self::SEARCH_UPDATE_FAILED => 'Verify document exists and search engine is operational',
self::SEARCH_DELETE_FAILED => 'Ensure document exists in search index',
self::SEARCH_CONFIG_INVALID => 'Review search configuration and field mappings',
self::SEARCH_ENGINE_UNAVAILABLE => 'Wait for search engine to become available or use fallback',
// Other errors have generic recovery hint
default => 'Check logs for more details and contact support if needed',
};
}
public function isRecoverable(): bool
{
return match($this) {
// Non-recoverable errors
self::SYSTEM_CONFIG_MISSING,
self::SYSTEM_CONFIG_INVALID,
self::SYSTEM_DEPENDENCY_MISSING,
self::FS_PERMISSION_DENIED,
self::AUTH_USER_LOCKED,
self::BIZ_WORKFLOW_VIOLATION,
self::CON_PERMISSION_DENIED,
self::CON_INVALID_COMMAND_STRUCTURE,
self::ENTITY_READONLY,
self::DI_CIRCULAR_DEPENDENCY,
self::DISC_CACHE_CORRUPTION,
self::TPL_SYNTAX_ERROR,
self::VO_INVALID_VALUE => false,
// Recoverable errors (can be retried or handled gracefully)
default => true,
};
}
public function getRetryAfterSeconds(): ?int
{
return match($this) {
self::DB_CONNECTION_FAILED => 30,
self::DB_TIMEOUT => 60,
self::API_SERVICE_UNAVAILABLE => 300,
self::API_RATE_LIMIT_EXCEEDED => 60,
self::API_TIMEOUT => 30,
self::CACHE_CONNECTION_FAILED => 10,
self::HTTP_RATE_LIMIT_EXCEEDED => 60,
self::SEARCH_ENGINE_UNAVAILABLE => 60,
self::SEARCH_INDEX_FAILED => 5,
self::SEARCH_UPDATE_FAILED => 5,
// Console retry intervals
self::CON_COMMAND_EXECUTION_FAILED => 5,
self::CON_TIMEOUT => 30,
// MCP retry intervals
self::MCP_SERVER_UNAVAILABLE => 15,
self::MCP_TIMEOUT => 10,
// Queue retry intervals
self::QUEUE_CONNECTION_FAILED => 30,
self::QUEUE_JOB_FAILED => 60,
self::QUEUE_WORKER_UNAVAILABLE => 120,
self::QUEUE_TIMEOUT => 30,
// Performance retry intervals
self::PERF_CIRCUIT_BREAKER_OPEN => 300,
self::PERF_METRIC_COLLECTION_FAILED => 60,
// Discovery retry intervals
self::DISC_ATTRIBUTE_SCAN_FAILED => 10,
self::DISC_REFLECTION_FAILED => 5,
// Event retry intervals
self::EVENT_DISPATCH_FAILED => 5,
self::EVENT_TIMEOUT => 30,
// Template retry intervals
self::TPL_CACHE_FAILED => 10,
self::TPL_CONTENT_LOADING_FAILED => 5,
self::TPL_PROCESSOR_FAILED => 15,
self::TPL_RENDERING_FAILED => 5,
default => null,
};
}
/**
* Get the error code value (e.g., 'DB001', 'AUTH002')
*/
public function getValue(): string;
/**
* Get error category prefix (e.g., 'DB', 'AUTH', 'VAL')
*/
public function getCategory(): string;
/**
* Get numeric portion of error code (e.g., 1, 2, 3)
*/
public function getNumericCode(): int;
/**
* Get error severity level for retention and alerting
*/
public function getSeverity(): ErrorSeverity;
/**
* Get human-readable error description
*/
public function getDescription(): string;
/**
* Get recovery hint for troubleshooting
*/
public function getRecoveryHint(): string;
/**
* Check if this error is recoverable (can be retried)
*/
public function isRecoverable(): bool;
/**
* Get suggested retry interval in seconds (null if not retryable)
*/
public function getRetryAfterSeconds(): ?int;
}

View File

@@ -0,0 +1,159 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception;
/**
* Exception metadata for behavior control and lifecycle tracking
*
* Immutable value object containing exception-specific metadata:
* - Integration flags (skip aggregation/reporting)
* - Lifecycle state (reported)
* - Retry configuration
*
* This separates exception behavior from domain context (ExceptionContext)
* and error categorization (ErrorCode).
*/
final readonly class ExceptionMetadata
{
public function __construct(
public bool $skipAggregation = false,
public bool $skipReporting = false,
public bool $reported = false,
public ?int $retryAfter = null,
) {}
/**
* Create default metadata with all flags disabled
*/
public static function default(): self
{
return new self();
}
/**
* Create metadata with custom retry configuration
*/
public static function withRetry(int $retryAfter): self
{
return new self(
retryAfter: $retryAfter
);
}
/**
* Mark exception to skip aggregation
*/
public function withSkipAggregation(): self
{
return new self(
skipAggregation: true,
skipReporting: $this->skipReporting,
reported: $this->reported,
retryAfter: $this->retryAfter
);
}
/**
* Mark exception to skip reporting
*/
public function withSkipReporting(): self
{
return new self(
skipAggregation: $this->skipAggregation,
skipReporting: true,
reported: $this->reported,
retryAfter: $this->retryAfter
);
}
/**
* Mark exception to skip both aggregation and reporting
*/
public function withSkipAll(): self
{
return new self(
skipAggregation: true,
skipReporting: true,
reported: $this->reported,
retryAfter: $this->retryAfter
);
}
/**
* Mark exception as reported
*/
public function markAsReported(): self
{
return new self(
skipAggregation: $this->skipAggregation,
skipReporting: $this->skipReporting,
reported: true,
retryAfter: $this->retryAfter
);
}
/**
* Update retry configuration
*/
public function withRetryAfter(?int $retryAfter): self
{
return new self(
skipAggregation: $this->skipAggregation,
skipReporting: $this->skipReporting,
reported: $this->reported,
retryAfter: $retryAfter
);
}
/**
* Check if exception should be aggregated
*/
public function shouldAggregate(): bool
{
return !$this->skipAggregation;
}
/**
* Check if exception should be reported
*/
public function shouldReport(): bool
{
return !$this->skipReporting && !$this->reported;
}
/**
* Check if exception was already reported
*/
public function wasReported(): bool
{
return $this->reported;
}
/**
* Convert to array for storage/serialization
*/
public function toArray(): array
{
return [
'skip_aggregation' => $this->skipAggregation,
'skip_reporting' => $this->skipReporting,
'reported' => $this->reported,
'retry_after' => $this->retryAfter,
];
}
/**
* Create from array (deserialization)
*/
public static function fromArray(array $data): self
{
return new self(
skipAggregation: $data['skip_aggregation'] ?? false,
skipReporting: $data['skip_reporting'] ?? false,
reported: $data['reported'] ?? false,
retryAfter: $data['retry_after'] ?? null
);
}
}

View File

@@ -4,13 +4,15 @@ declare(strict_types=1);
namespace App\Framework\Exception;
class FrameworkException extends \RuntimeException
use RuntimeException;
class FrameworkException extends RuntimeException
{
protected ExceptionContext $context;
protected ?ErrorCode $errorCode;
protected ?int $retryAfter;
protected ExceptionMetadata $metadata;
public function __construct(
string $message,
@@ -18,12 +20,20 @@ class FrameworkException extends \RuntimeException
int $code = 0,
?\Throwable $previous = null,
?ErrorCode $errorCode = null,
?int $retryAfter = null
?ExceptionMetadata $metadata = null
) {
parent::__construct($message, $code, $previous);
$this->context = $context;
$this->errorCode = $errorCode;
$this->retryAfter = $retryAfter ?? $errorCode?->getRetryAfterSeconds();
// Initialize metadata with retry from ErrorCode if available
$retryAfter = $metadata?->retryAfter ?? $errorCode?->getRetryAfterSeconds();
$this->metadata = $metadata ?? ExceptionMetadata::default();
// Update metadata with retry if not already set
if ($retryAfter !== null && $this->metadata->retryAfter === null) {
$this->metadata = $this->metadata->withRetryAfter($retryAfter);
}
}
public function getContext(): ExceptionContext
@@ -31,6 +41,11 @@ class FrameworkException extends \RuntimeException
return $this->context;
}
public function getMetadata(): ExceptionMetadata
{
return $this->metadata;
}
public function __clone(): void
{
// Allow cloning of the exception for immutable modifications
@@ -44,7 +59,19 @@ class FrameworkException extends \RuntimeException
$this->getCode(),
$this->getPrevious(),
$this->errorCode,
$this->retryAfter
$this->metadata
);
}
public function withMetadata(ExceptionMetadata $metadata): self
{
return new static(
$this->getMessage(),
$this->context,
$this->getCode(),
$this->getPrevious(),
$this->errorCode,
$metadata
);
}
@@ -69,13 +96,77 @@ class FrameworkException extends \RuntimeException
);
}
public function withMetadata(array $metadata): self
public function withContextMetadata(array $metadata): self
{
return $this->withContext(
$this->context->withMetadata($metadata)
);
}
// === Integration Control Methods (NEW) ===
/**
* Skip automatic error aggregation for this exception
*/
public function skipAggregation(): self
{
return $this->withMetadata(
$this->metadata->withSkipAggregation()
);
}
/**
* Skip automatic error reporting for this exception
*/
public function skipReporting(): self
{
return $this->withMetadata(
$this->metadata->withSkipReporting()
);
}
/**
* Skip both aggregation and reporting
*/
public function skipAll(): self
{
return $this->withMetadata(
$this->metadata->withSkipAll()
);
}
/**
* Mark exception as reported (used by ErrorHandler)
*/
public function markAsReported(): void
{
$this->metadata = $this->metadata->markAsReported();
}
/**
* Check if exception should be aggregated
*/
public function shouldAggregate(): bool
{
return $this->metadata->shouldAggregate();
}
/**
* Check if exception should be reported
*/
public function shouldReport(): bool
{
return $this->metadata->shouldReport();
}
/**
* Check if exception was already reported
*/
public function wasReported(): bool
{
return $this->metadata->wasReported();
}
public function toArray(): array
{
$array = [
@@ -85,6 +176,7 @@ class FrameworkException extends \RuntimeException
'file' => $this->getFile(),
'line' => $this->getLine(),
'context' => $this->context->toArray(),
'metadata' => $this->metadata->toArray(),
'trace' => $this->getTraceAsString(),
];
@@ -95,7 +187,6 @@ class FrameworkException extends \RuntimeException
$array['description'] = $this->errorCode->getDescription();
$array['recovery_hint'] = $this->errorCode->getRecoveryHint();
$array['is_recoverable'] = $this->errorCode->isRecoverable();
$array['retry_after'] = $this->retryAfter;
}
return $array;
@@ -192,7 +283,7 @@ class FrameworkException extends \RuntimeException
public function getRetryAfter(): ?int
{
return $this->retryAfter;
return $this->metadata->retryAfter;
}
public function isRecoverable(): bool
@@ -205,6 +296,14 @@ class FrameworkException extends \RuntimeException
return $this->errorCode?->getRecoveryHint();
}
/**
* Get severity from ErrorCode (NEW)
*/
public function getSeverity(): ?Core\ErrorSeverity
{
return $this->errorCode?->getSeverity();
}
// === ErrorCode Utility Methods ===
/**
@@ -228,13 +327,17 @@ class FrameworkException extends \RuntimeException
*/
public function withErrorCode(ErrorCode $errorCode): self
{
$metadata = $this->metadata->withRetryAfter(
$errorCode->getRetryAfterSeconds()
);
return new static(
message: $this->getMessage(),
context: $this->context,
code: $this->getCode(),
previous: $this->getPrevious(),
errorCode: $errorCode,
retryAfter: $errorCode->getRetryAfterSeconds()
metadata: $metadata
);
}
@@ -243,13 +346,8 @@ class FrameworkException extends \RuntimeException
*/
public function withRetryAfter(int $seconds): self
{
return new static(
message: $this->getMessage(),
context: $this->context,
code: $this->getCode(),
previous: $this->getPrevious(),
errorCode: $this->errorCode,
retryAfter: $seconds
return $this->withMetadata(
$this->metadata->withRetryAfter($seconds)
);
}

View File

@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Framework\Exception\Http;
use App\Framework\Exception\ErrorCode;
use App\Framework\Exception\Core\HttpErrorCode;
use App\Framework\Exception\ExceptionContext;
use App\Framework\Exception\FrameworkException;
@@ -57,7 +57,7 @@ final class InvalidContentTypeException extends FrameworkException
context: $context,
code: 415, // Unsupported Media Type
previous: $previous,
errorCode: ErrorCode::HTTP_UNSUPPORTED_MEDIA_TYPE
errorCode: HttpErrorCode::BAD_REQUEST
);
}

View File

@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Framework\Exception\Http;
use App\Framework\Exception\ErrorCode;
use App\Framework\Exception\Core\HttpErrorCode;
use App\Framework\Exception\ExceptionContext;
use App\Framework\Exception\FrameworkException;
@@ -57,7 +57,7 @@ final class MalformedJsonException extends FrameworkException
context: $context,
code: 400, // Bad Request
previous: $previous,
errorCode: ErrorCode::HTTP_MALFORMED_REQUEST
errorCode: HttpErrorCode::BAD_REQUEST
);
}

View File

@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Framework\Exception\Http;
use App\Framework\Exception\ErrorCode;
use App\Framework\Exception\Core\HttpErrorCode;
use App\Framework\Exception\ExceptionContext;
use App\Framework\Exception\FrameworkException;
@@ -56,7 +56,7 @@ final class OversizedRequestException extends FrameworkException
context: $context,
code: 413, // Payload Too Large
previous: $previous,
errorCode: ErrorCode::HTTP_REQUEST_TOO_LARGE
errorCode: HttpErrorCode::BAD_REQUEST
);
}

View File

@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Framework\Exception\Http;
use App\Framework\Exception\ErrorCode;
use App\Framework\Exception\Core\HttpErrorCode;
use App\Framework\Exception\ExceptionContext;
use App\Framework\Exception\FrameworkException;
@@ -61,7 +61,7 @@ final class RateLimitExceededException extends FrameworkException
context: $context,
code: 429, // Too Many Requests
previous: $previous,
errorCode: ErrorCode::HTTP_RATE_LIMIT_EXCEEDED,
errorCode: HttpErrorCode::RATE_LIMIT_EXCEEDED,
retryAfter: $this->retryAfterSeconds
);
}

View File

@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Framework\Exception\Http;
use App\Framework\Exception\ErrorCode;
use App\Framework\Exception\Core\HttpErrorCode;
use App\Framework\Exception\ExceptionContext;
use App\Framework\Exception\FrameworkException;
@@ -57,7 +57,7 @@ final class RouteNotFoundException extends FrameworkException
context: $context,
code: 404, // Not Found
previous: $previous,
errorCode: ErrorCode::HTTP_ROUTE_NOT_FOUND
errorCode: HttpErrorCode::NOT_FOUND
);
}

View File

@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Security;
use App\Framework\Exception\Core\SecurityErrorCode;
use App\Framework\Exception\ExceptionContext;
use App\Framework\Exception\FrameworkException;
/**
* CSRF Token Validation Failed Exception
*/
final class CsrfValidationFailedException extends FrameworkException
{
public static function tokenValidationFailed(string $formId): self
{
$context = ExceptionContext::forOperation('security.csrf', 'CsrfMiddleware')
->withData([
'form_id' => $formId,
'validation_type' => 'token_mismatch',
'client_ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown',
])
->withMetadata([
'security_threat' => 'potential_csrf_attack',
'requires_investigation' => true,
]);
return self::fromContext(
message: 'CSRF token validation failed. This may indicate a security threat.',
context: $context,
errorCode: SecurityErrorCode::CSRF_TOKEN_INVALID
);
}
public static function missingTokenOrFormId(bool $missingFormId, bool $missingToken): self
{
$missing = [];
if ($missingFormId) {
$missing[] = 'form_id';
}
if ($missingToken) {
$missing[] = 'csrf_token';
}
$context = ExceptionContext::forOperation('security.csrf', 'CsrfMiddleware')
->withData([
'missing_fields' => $missing,
'validation_type' => 'missing_required_fields',
]);
return self::fromContext(
message: 'CSRF protection requires both form ID and token',
context: $context,
errorCode: SecurityErrorCode::CSRF_TOKEN_INVALID
);
}
public static function invalidTokenFormat(string $error): self
{
$context = ExceptionContext::forOperation('security.csrf', 'CsrfMiddleware')
->withData([
'validation_type' => 'invalid_format',
'format_error' => $error,
]);
return self::fromContext(
message: "Invalid CSRF token format: {$error}",
context: $context,
errorCode: SecurityErrorCode::CSRF_TOKEN_INVALID
);
}
}

View File

@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Security;
use App\Framework\Exception\ErrorCode;
use App\Framework\Exception\ExceptionContext;
use App\Framework\Exception\SecurityException;
use App\Framework\Security\Events\BotDetectedEvent;
/**
* Exception for honeypot spam protection triggers
*/
final class HoneypotTriggeredException extends SecurityException
{
public static function missingHoneypotName(): self
{
$event = new BotDetectedEvent(
reason: 'Missing honeypot field in form submission',
ip: $_SERVER['REMOTE_ADDR'] ?? 'unknown',
userAgent: $_SERVER['HTTP_USER_AGENT'] ?? 'unknown'
);
$context = ExceptionContext::forOperation('security.honeypot', 'HoneypotMiddleware')
->withData([
'validation_type' => 'missing_honeypot_name',
'risk_level' => 'high',
]);
return new self(
securityEvent: $event,
message: 'Spam protection triggered: Missing honeypot field',
additionalContext: $context
);
}
public static function honeypotFilled(string $fieldName, string $value): self
{
$event = new BotDetectedEvent(
reason: "Honeypot field '{$fieldName}' was filled",
ip: $_SERVER['REMOTE_ADDR'] ?? 'unknown',
userAgent: $_SERVER['HTTP_USER_AGENT'] ?? 'unknown'
);
$context = ExceptionContext::forOperation('security.honeypot', 'HoneypotMiddleware')
->withData([
'validation_type' => 'honeypot_filled',
'field_name' => $fieldName,
'field_value_length' => strlen($value),
'risk_level' => 'high',
])
->withDebug([
'field_value' => substr($value, 0, 100), // Limit für Logging
]);
return new self(
securityEvent: $event,
message: 'Spam protection triggered: Honeypot field was filled',
additionalContext: $context
);
}
public static function submittedTooQuickly(int $elapsedSeconds, int $minimumSeconds = 2): self
{
$event = new BotDetectedEvent(
reason: "Form submitted too quickly ({$elapsedSeconds}s < {$minimumSeconds}s)",
ip: $_SERVER['REMOTE_ADDR'] ?? 'unknown',
userAgent: $_SERVER['HTTP_USER_AGENT'] ?? 'unknown'
);
$context = ExceptionContext::forOperation('security.honeypot', 'HoneypotMiddleware')
->withData([
'validation_type' => 'submission_too_fast',
'elapsed_seconds' => $elapsedSeconds,
'minimum_seconds' => $minimumSeconds,
'risk_level' => 'medium',
]);
return new self(
securityEvent: $event,
message: 'Spam protection triggered: Form submitted too quickly',
additionalContext: $context
);
}
}

View File

@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Framework\Exception\Security;
use App\Framework\Exception\ErrorCode;
use App\Framework\Exception\Core\SecurityErrorCode;
use App\Framework\Exception\ExceptionContext;
use App\Framework\Exception\FrameworkException;
@@ -56,7 +56,7 @@ final class PathTraversalAttemptException extends FrameworkException
context: $context,
code: 400, // Bad Request
previous: $previous,
errorCode: ErrorCode::SECURITY_PATH_TRAVERSAL
errorCode: SecurityErrorCode::PATH_TRAVERSAL_DETECTED
);
}

View File

@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Framework\Exception\Security;
use App\Framework\Exception\ErrorCode;
use App\Framework\Exception\Core\SecurityErrorCode;
use App\Framework\Exception\ExceptionContext;
use App\Framework\Exception\FrameworkException;
@@ -61,7 +61,7 @@ final class SqlInjectionAttemptException extends FrameworkException
context: $context,
code: 400, // Bad Request
previous: $previous,
errorCode: ErrorCode::SECURITY_SQL_INJECTION
errorCode: SecurityErrorCode::SQL_INJECTION_DETECTED
);
}

View File

@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Framework\Exception\Security;
use App\Framework\Exception\ErrorCode;
use App\Framework\Exception\Core\SecurityErrorCode;
use App\Framework\Exception\ExceptionContext;
use App\Framework\Exception\FrameworkException;
@@ -61,7 +61,7 @@ final class XssAttemptException extends FrameworkException
context: $context,
code: 400, // Bad Request
previous: $previous,
errorCode: ErrorCode::SECURITY_XSS_ATTEMPT
errorCode: SecurityErrorCode::XSS_DETECTED
);
}

View File

@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace App\Framework\Exception\SecurityEvent;
use App\Framework\Exception\SecurityLogLevel;
/**
* Bot Detection Event (Honeypot, Rate Limiting, etc.)
*/
final readonly class BotDetectedEvent implements SecurityEventInterface
{
public function __construct(
public string $reason,
public string $ip,
public string $userAgent,
public ?string $detectionMethod = 'honeypot'
) {
}
public function getEventIdentifier(): string
{
return "bot_detected:{$this->detectionMethod}:{$this->ip}";
}
public function getDescription(): string
{
return "Bot activity detected: {$this->reason}";
}
public function getLogLevel(): SecurityLogLevel
{
return SecurityLogLevel::WARN;
}
public function getCategory(): string
{
return 'bot_protection';
}
public function requiresAlert(): bool
{
// Honeypot triggers sollten geloggt werden, aber nicht sofort alertieren
// (außer bei ungewöhnlich vielen Versuchen von der gleichen IP)
return false;
}
public function toArray(): array
{
return [
'reason' => $this->reason,
'ip' => $this->ip,
'user_agent' => $this->userAgent,
'detection_method' => $this->detectionMethod,
'event_identifier' => $this->getEventIdentifier(),
'category' => $this->getCategory(),
'log_level' => $this->getLogLevel()->value,
'requires_alert' => $this->requiresAlert(),
];
}
}