Files
michaelschiemer/docs/exception-migration-guide.md
Michael Schiemer fc3d7e6357 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.
2025-10-25 19:18:37 +02:00

1133 lines
33 KiB
Markdown

# Developer Migration Guide
Step-by-step guide for migrating modules to the new exception hierarchy system.
## Overview
This guide provides a practical, hands-on approach to migrating existing modules from legacy exception handling (RuntimeException, generic exceptions) to the framework's standardized exception hierarchy with ErrorCode integration.
## Prerequisites
Before starting migration:
- Read [Exception Hierarchy Pattern Guide](./exception-hierarchy-pattern.md)
- Read [ErrorHandler Enhancements Guide](./error-handler-enhancements.md)
- Understand ErrorCode system and ExceptionContext
- Have module test suite ready for validation
## Migration Process Overview
```
1. Module Analysis → Identify exceptions and patterns
2. ErrorCode Definition → Create module-specific error codes
3. Base Exception → Create module base exception
4. Specific Exceptions → Create domain-specific exceptions
5. Service Migration → Replace legacy exceptions
6. Testing & Validation → Verify functionality
7. Documentation Update → Update module docs
```
## Step 1: Module Analysis
### Identify All Exceptions
Use grep to find all exceptions in the module:
```bash
# Find all RuntimeException throws
grep -r "throw new \\\\RuntimeException" src/Framework/YourModule/
# Find all InvalidArgumentException (business logic only)
grep -r "throw new \\\\InvalidArgumentException" src/Framework/YourModule/
# Find all generic Exception throws
grep -r "throw new \\\\Exception" src/Framework/YourModule/
# Create inventory
grep -r "throw new" src/Framework/YourModule/ | wc -l
```
**Example Output from Queue Module**:
```
Found 22 exceptions:
- 15 RuntimeException (business logic issues)
- 5 InvalidArgumentException (mix of constructor validation and business logic)
- 2 Exception (generic errors)
```
### Categorize Exceptions
Create a spreadsheet or document:
| File | Line | Exception Type | Context | Keep/Migrate |
|------|------|----------------|---------|--------------|
| JobPersistenceLayer.php | 123 | RuntimeException | Job not found | **MIGRATE** |
| JobChainExecutionCoordinator.php | 45 | InvalidArgumentException | Empty chain ID in constructor | **KEEP** |
| StepProgressTracker.php | 43 | RuntimeException | All steps completed | **MIGRATE** |
**Decision Rules**:
- **MIGRATE**: Business logic exceptions, state violations, not found errors
- **KEEP**: Constructor parameter validation, type validation, invalid arguments in constructors
### Identify Common Patterns
Group exceptions by scenario:
```
Entity Not Found: JobNotFoundException, ChainNotFoundException, WorkerNotFoundException
State Violations: InvalidChainStateException, AllStepsCompletedException
Infrastructure: RedisExtensionNotLoadedException
```
## Step 2: ErrorCode Definition
### Create Module ErrorCode Enum
**Location**: `src/Framework/Exception/Core/{Module}ErrorCode.php`
**Template**:
```php
<?php
declare(strict_types=1);
namespace App\Framework\Exception\Core;
enum YourModuleErrorCode: string implements ErrorCode
{
// Entity not found errors (001-010)
case ENTITY_NOT_FOUND = 'MODULE001';
case RESOURCE_MISSING = 'MODULE002';
// State violation errors (011-020)
case INVALID_STATE = 'MODULE011';
case OPERATION_NOT_ALLOWED = 'MODULE012';
// Infrastructure errors (021-030)
case DEPENDENCY_MISSING = 'MODULE021';
case SERVICE_UNAVAILABLE = 'MODULE022';
// Business logic errors (031-040)
case VALIDATION_FAILED = 'MODULE031';
case CONSTRAINT_VIOLATION = 'MODULE032';
public function getValue(): string
{
return $this->value;
}
public function getCategory(): string
{
return 'MODULE';
}
public function getNumericCode(): int
{
return (int) substr($this->value, -3);
}
public function getSeverity(): ErrorSeverity
{
return match($this) {
self::ENTITY_NOT_FOUND => ErrorSeverity::ERROR,
self::RESOURCE_MISSING => ErrorSeverity::ERROR,
self::INVALID_STATE => ErrorSeverity::WARNING,
self::OPERATION_NOT_ALLOWED => ErrorSeverity::WARNING,
self::DEPENDENCY_MISSING => ErrorSeverity::CRITICAL,
self::SERVICE_UNAVAILABLE => ErrorSeverity::ERROR,
self::VALIDATION_FAILED => ErrorSeverity::WARNING,
self::CONSTRAINT_VIOLATION => ErrorSeverity::ERROR,
};
}
public function getDescription(): string
{
return match($this) {
self::ENTITY_NOT_FOUND => 'Requested entity not found',
self::RESOURCE_MISSING => 'Required resource is missing',
self::INVALID_STATE => 'Operation not allowed in current state',
self::OPERATION_NOT_ALLOWED => 'Operation not permitted',
self::DEPENDENCY_MISSING => 'Required dependency not available',
self::SERVICE_UNAVAILABLE => 'Service temporarily unavailable',
self::VALIDATION_FAILED => 'Input validation failed',
self::CONSTRAINT_VIOLATION => 'Business constraint violated',
};
}
public function getRecoveryHint(): string
{
return match($this) {
self::ENTITY_NOT_FOUND => 'Verify the entity ID exists in the system',
self::RESOURCE_MISSING => 'Check if required resources are configured',
self::INVALID_STATE => 'Check entity state and ensure prerequisites are met',
self::OPERATION_NOT_ALLOWED => 'Verify permissions and operation context',
self::DEPENDENCY_MISSING => 'Install required dependency or use fallback',
self::SERVICE_UNAVAILABLE => 'Retry operation after brief delay',
self::VALIDATION_FAILED => 'Review input data and correct validation errors',
self::CONSTRAINT_VIOLATION => 'Adjust operation to meet business constraints',
};
}
public function isRecoverable(): bool
{
return match($this) {
self::DEPENDENCY_MISSING => false,
default => true,
};
}
public function getRetryAfterSeconds(): ?int
{
return match($this) {
self::SERVICE_UNAVAILABLE => 60,
default => null,
};
}
}
```
**Real-World Example from Queue Module**:
```php
enum QueueErrorCode: string implements ErrorCode
{
case JOB_NOT_FOUND = 'QUEUE007';
case CHAIN_NOT_FOUND = 'QUEUE008';
case INVALID_STATE = 'QUEUE009';
case WORKER_UNAVAILABLE = 'QUEUE010';
// ... implementations
}
```
### ErrorCode Naming Conventions
- **Category Prefix**: 2-4 uppercase letters (QUEUE, DB, AUTH, VAL)
- **Numeric Suffix**: 3 digits (001-999)
- **Severity Mapping**: CRITICAL for system failures, ERROR for operation failures, WARNING for state issues
- **Recovery**: true for temporary issues, false for permanent failures
- **Retry**: Set seconds only for infrastructure issues (DB connection, network, etc.)
## Step 3: Create Module Base Exception
**Location**: `src/Framework/{Module}/Exceptions/{Module}Exception.php`
**Template**:
```php
<?php
declare(strict_types=1);
namespace App\Framework\YourModule\Exceptions;
use App\Framework\Exception\FrameworkException;
/**
* Base exception for YourModule
*
* All YourModule-related exceptions should extend this class.
*/
class YourModuleException extends FrameworkException
{
}
```
**Real-World Example from Queue Module**:
```php
<?php
declare(strict_types=1);
namespace App\Framework\Queue\Exceptions;
use App\Framework\Exception\FrameworkException;
class QueueException extends FrameworkException
{
}
```
**Why Important**:
- Single catch point for all module exceptions
- Type safety in exception handling
- Module-scoped exception hierarchy
- Framework compliance
## Step 4: Create Specific Exceptions
### Pattern 1: Entity Not Found Exception
**Before** (Legacy):
```php
// In UserRepository.php
public function find(UserId $id): User
{
$user = $this->database->query(/* ... */);
if ($user === null) {
throw new \RuntimeException("User with ID {$id->toString()} not found");
}
return $user;
}
```
**After** (Framework-Compliant):
```php
// New file: src/Framework/YourModule/Exceptions/UserNotFoundException.php
<?php
declare(strict_types=1);
namespace App\Framework\YourModule\Exceptions;
use App\Framework\Exception\Core\YourModuleErrorCode;
use App\Framework\Exception\ExceptionContext;
final class UserNotFoundException extends YourModuleException
{
public static function byId(UserId $id): self
{
$context = ExceptionContext::forOperation('user.lookup', 'UserRepository')
->withData([
'user_id' => $id->toString(),
'search_type' => 'by_id',
]);
return self::create(
YourModuleErrorCode::ENTITY_NOT_FOUND,
"User with ID '{$id->toString()}' not found",
$context
);
}
public static function byEmail(Email $email): self
{
$context = ExceptionContext::forOperation('user.lookup', 'UserRepository')
->withData([
'email' => $email->getMasked(),
'search_type' => 'by_email',
]);
return self::create(
YourModuleErrorCode::ENTITY_NOT_FOUND,
"User with email not found",
$context
);
}
}
```
### Pattern 2: State Violation Exception
**Before** (Legacy):
```php
// In OrderProcessor.php
public function cancel(Order $order): void
{
if ($order->status === OrderStatus::SHIPPED) {
throw new \RuntimeException('Cannot cancel shipped order');
}
// Cancel logic
}
```
**After** (Framework-Compliant):
```php
// New file: src/Framework/YourModule/Exceptions/OrderAlreadyShippedException.php
<?php
declare(strict_types=1);
namespace App\Framework\YourModule\Exceptions;
use App\Framework\Exception\Core\YourModuleErrorCode;
use App\Framework\Exception\ExceptionContext;
final class OrderAlreadyShippedException extends YourModuleException
{
public static function forOrder(string $orderId): self
{
$context = ExceptionContext::forOperation('order.cancel', 'OrderProcessor')
->withData([
'order_id' => $orderId,
'current_status' => 'shipped',
'operation' => 'cancel',
]);
return self::create(
YourModuleErrorCode::INVALID_STATE,
"Cannot cancel order '{$orderId}' - order already shipped",
$context
);
}
}
```
### Pattern 3: Infrastructure Exception
**Before** (Legacy):
```php
// In ServiceInitializer.php
public function initialize(): Service
{
if (!extension_loaded('redis')) {
throw new \RuntimeException('Redis extension not loaded');
}
return new RedisService();
}
```
**After** (Framework-Compliant):
```php
// New file: src/Framework/YourModule/Exceptions/RedisExtensionNotLoadedException.php
<?php
declare(strict_types=1);
namespace App\Framework\YourModule\Exceptions;
use App\Framework\Exception\Core\YourModuleErrorCode;
use App\Framework\Exception\ExceptionContext;
final class RedisExtensionNotLoadedException extends YourModuleException
{
public static function create(): self
{
$context = ExceptionContext::forOperation('service.init', 'ServiceInitializer')
->withData([
'required_extension' => 'redis',
'loaded_extensions' => get_loaded_extensions(),
'fallback_available' => 'FileService',
]);
return self::fromContext(
'Redis PHP extension is not loaded',
$context,
YourModuleErrorCode::DEPENDENCY_MISSING
);
}
}
```
## Step 5: Service Migration
### Migration Checklist for Each File
- [ ] Identify all legacy exception throws
- [ ] Determine which exceptions to keep (constructor validation)
- [ ] Create or reuse specific exception classes
- [ ] Replace RuntimeException throws with specific exceptions
- [ ] Add use statements for new exception classes
- [ ] Update error messages to be more descriptive
- [ ] Add relevant context data
- [ ] Test each replaced exception
### Migration Example: Complete File
**Before** - `StepProgressTracker.php`:
```php
<?php
declare(strict_types=1);
namespace App\Framework\Queue\Services;
final readonly class StepProgressTracker
{
public function __construct(
private string $jobId,
private array $steps,
private JobProgressTrackerInterface $progressTracker
) {
if (empty($this->jobId)) {
throw new \InvalidArgumentException('Job ID cannot be empty');
}
if (empty($this->steps)) {
throw new \InvalidArgumentException('Steps array cannot be empty');
}
}
public function completeCurrentStep(): void
{
$progress = $this->progressTracker->getProgress($this->jobId);
$currentStepIndex = $progress->current_step;
if ($currentStepIndex >= count($this->steps)) {
throw new \RuntimeException('All steps have already been completed');
}
// Complete step logic
}
public function updateStepProgress(int $percentage): void
{
$progress = $this->progressTracker->getProgress($this->jobId);
$currentStepIndex = $progress->current_step;
if ($currentStepIndex >= count($this->steps)) {
throw new \RuntimeException('All steps have already been completed');
}
// Update logic
}
}
```
**After** - `StepProgressTracker.php`:
```php
<?php
declare(strict_types=1);
namespace App\Framework\Queue\Services;
use App\Framework\Queue\Exceptions\AllStepsCompletedException;
final readonly class StepProgressTracker
{
public function __construct(
private string $jobId,
private array $steps,
private JobProgressTrackerInterface $progressTracker
) {
// KEEP constructor validation - these are InvalidArgumentException
if (empty($this->jobId)) {
throw new \InvalidArgumentException('Job ID cannot be empty');
}
if (empty($this->steps)) {
throw new \InvalidArgumentException('Steps array cannot be empty');
}
}
public function completeCurrentStep(): void
{
$progress = $this->progressTracker->getProgress($this->jobId);
$currentStepIndex = $progress->current_step;
if ($currentStepIndex >= count($this->steps)) {
// MIGRATED: Business logic exception
throw AllStepsCompletedException::forJob($this->jobId, count($this->steps));
}
// Complete step logic
}
public function updateStepProgress(int $percentage): void
{
$progress = $this->progressTracker->getProgress($this->jobId);
$currentStepIndex = $progress->current_step;
if ($currentStepIndex >= count($this->steps)) {
// MIGRATED: Business logic exception
throw AllStepsCompletedException::forJob($this->jobId, count($this->steps));
}
// Update logic
}
}
```
**Changes Made**:
1. Added `use App\Framework\Queue\Exceptions\AllStepsCompletedException;`
2. Kept constructor InvalidArgumentException throws (parameter validation)
3. Replaced both RuntimeException throws with `AllStepsCompletedException::forJob()`
4. More descriptive error messages via factory method
5. Rich context automatically added by exception
## Step 6: Testing & Validation
### Create Test File
**Location**: `tests/debug/test-{module}-exception-migration.php`
```php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use App\Framework\YourModule\Exceptions\EntityNotFoundException;
use App\Framework\YourModule\ValueObjects\EntityId;
echo "=== Testing YourModule Exception Migration ===\n\n";
// Test 1: Entity Not Found Exception
echo "Test 1: EntityNotFoundException\n";
echo str_repeat('-', 60) . "\n";
try {
$entityId = EntityId::fromString('test-entity-123');
throw EntityNotFoundException::byId($entityId);
} catch (EntityNotFoundException $e) {
echo "Exception: " . get_class($e) . "\n";
echo "Message: " . $e->getMessage() . "\n";
echo "Error Code: " . $e->getErrorCode()->getValue() . "\n";
echo "Category: " . $e->getErrorCode()->getCategory() . "\n";
echo "Severity: " . $e->getErrorCode()->getSeverity()->value . "\n";
echo "Is Recoverable: " . ($e->getErrorCode()->isRecoverable() ? 'Yes' : 'No') . "\n";
echo "Recovery Hint: " . $e->getErrorCode()->getRecoveryHint() . "\n";
}
echo "\n" . str_repeat('=', 60) . "\n\n";
// Test 2: State Violation Exception
echo "Test 2: State Violation Exception\n";
echo str_repeat('-', 60) . "\n";
// ... test state violation
echo "\n✅ All migration tests completed!\n";
```
### Run Test Suite
```bash
# Run debug test
php tests/debug/test-yourmodule-exception-migration.php
# Run module test suite
./vendor/bin/pest tests/Unit/Framework/YourModule/
# Verify no regressions
./vendor/bin/pest
```
### Validate ErrorHandler Integration
```php
// Test ErrorHandler with new exceptions
use App\Framework\ErrorHandling\ErrorHandler;
use App\Framework\DI\DefaultContainer;
use App\Framework\Http\ResponseEmitter;
use App\Framework\Http\RequestIdGenerator;
$container = new DefaultContainer();
$emitter = new ResponseEmitter();
$requestIdGenerator = new RequestIdGenerator();
$errorHandler = new ErrorHandler(
$emitter,
$container,
$requestIdGenerator,
null,
true // Debug mode to see recovery hints
);
try {
throw EntityNotFoundException::byId($entityId);
} catch (EntityNotFoundException $e) {
$response = $errorHandler->createHttpResponse($e);
// Verify HTTP status based on category
echo "HTTP Status: " . $response->status->value . "\n";
// Verify metadata includes ErrorCode info
// Check response body for recovery hints (debug mode)
}
```
## Step 7: Documentation Update
### Update Module Documentation
Add section to module's documentation:
```markdown
## Exception Handling
This module uses framework-compliant exception hierarchies.
**Module Exceptions**:
- `EntityNotFoundException` - Thrown when entity not found (ENTITY_NOT_FOUND)
- `InvalidStateException` - Thrown for state violations (INVALID_STATE)
- `DependencyMissingException` - Thrown for missing dependencies (DEPENDENCY_MISSING)
**Error Codes**:
- `MODULE001` - Entity not found (ERROR)
- `MODULE011` - Invalid state (WARNING)
- `MODULE021` - Dependency missing (CRITICAL)
**Usage Example**:
\`\`\`php
try {
$entity = $this->repository->find($id);
} catch (EntityNotFoundException $e) {
// Handle not found
$this->logger->error('Entity not found', [
'error_code' => $e->getErrorCode()->getValue(),
'entity_id' => $id->toString()
]);
return $this->notFoundResponse($e->getMessage());
}
\`\`\`
```
## Migration Strategy by Module Size
### Small Modules (<10 exceptions)
- **Approach**: Migrate all at once in single session
- **Time**: 1-2 hours
- **Steps**: All 7 steps in sequence
- **Example**: Vault module (4 exceptions)
### Medium Modules (10-30 exceptions)
- **Approach**: Migrate by service/component
- **Time**: 2-4 hours
- **Steps**: Multiple sessions, one component at a time
- **Example**: Queue module (22 exceptions)
### Large Modules (>30 exceptions)
- **Approach**: Migrate by subdomain or priority
- **Time**: 4-8 hours
- **Steps**: Multiple days, prioritize critical paths
- **Example**: Database module (38 exceptions)
## Common Pitfalls & Solutions
### Pitfall 1: Keeping vs. Migrating InvalidArgumentException
**Problem**: Unclear when to keep InvalidArgumentException
**Solution**:
```php
// ✅ KEEP - Constructor parameter validation
public function __construct(string $id)
{
if (empty($id)) {
throw new \InvalidArgumentException('ID cannot be empty');
}
}
// ❌ MIGRATE - Business logic validation
public function process(Order $order): void
{
if ($order->items->isEmpty()) {
// Should be OrderEmptyException
throw new \InvalidArgumentException('Order has no items');
}
}
```
### Pitfall 2: Over-Generic Exception Messages
**Problem**: Messages too generic, not actionable
**Solution**:
```php
// ❌ Bad
throw new \RuntimeException('Operation failed');
// ✅ Good
throw OperationFailedException::forPayment(
$paymentId,
'Gateway timeout after 30s'
);
```
### Pitfall 3: Missing Context Data
**Problem**: Exception thrown without context
**Solution**:
```php
// ❌ Bad
throw EntityNotFoundException::byId($id);
// Context auto-generated but minimal
// ✅ Good
$context = ExceptionContext::forOperation('entity.delete', 'EntityService')
->withData([
'entity_id' => $id->toString(),
'entity_type' => 'Product',
'operation' => 'delete',
'user_id' => $currentUser->id,
])
->withDebug([
'query' => $query,
'execution_time_ms' => $executionTime,
]);
throw EntityNotFoundException::fromContext(
"Product with ID '{$id->toString()}' not found",
$context,
YourModuleErrorCode::ENTITY_NOT_FOUND
);
```
### Pitfall 4: Wrong ErrorCode Severity
**Problem**: Severity doesn't match actual impact
**Solution**:
```php
// ❌ Bad - State violation marked as CRITICAL
ErrorSeverity::CRITICAL // Will page ops at 3am
// ✅ Good - State violation is WARNING
ErrorSeverity::WARNING // Logged but not paged
```
### Pitfall 5: Creating Too Many Exception Classes
**Problem**: One exception class per scenario
**Solution**:
```php
// ❌ Bad - Too many classes
UserNotFoundByIdException
UserNotFoundByEmailException
UserNotFoundByUsernameException
// ✅ Good - One class with factory methods
final class UserNotFoundException
{
public static function byId(UserId $id): self { }
public static function byEmail(Email $email): self { }
public static function byUsername(string $username): self { }
}
```
## Real-World Migration: Queue Module
### Module Statistics
- **Files Analyzed**: 15 service files
- **Total Exceptions Found**: 22 exceptions
- **Exceptions Migrated**: 22 (100%)
- **Exception Classes Created**: 7
- **Error Codes Added**: 4
- **Services Migrated**: 6
- **Migration Time**: ~3 hours
### Exception Classes Created
1. `JobNotFoundException` - Job not found by ID or in queue
2. `ChainNotFoundException` - Chain not found by ID or name
3. `InvalidChainStateException` - Chain not in correct state
4. `CircularDependencyException` - Circular dependency detected
5. `AllStepsCompletedException` - All steps already completed
6. `RedisExtensionNotLoadedException` - Redis extension missing
7. `WorkerNotFoundException` - Worker not registered
### Error Codes Added
```php
enum QueueErrorCode: string implements ErrorCode
{
case JOB_NOT_FOUND = 'QUEUE007';
case CHAIN_NOT_FOUND = 'QUEUE008';
case INVALID_STATE = 'QUEUE009';
case WORKER_UNAVAILABLE = 'QUEUE010';
}
```
### Migration Order
1. **Day 1**: Analysis + ErrorCode definition (1h)
2. **Day 1**: Base exception + 3 entity not found exceptions (1h)
3. **Day 2**: State violation exceptions (0.5h)
4. **Day 2**: Service migrations (0.5h)
5. **Day 2**: Testing + validation (0.5h)
### Lessons Learned
- Start with most common exception patterns (not found, state violation)
- Migrate similar exceptions together (all not found, then all state)
- Test incrementally after each exception class created
- Constructor InvalidArgumentException are always kept
- Factory method naming: `byId()`, `forJob()`, `notPending()`
## Quick Reference Checklist
### Pre-Migration
- [ ] Read pattern guide and enhancement guide
- [ ] Analyze module and count exceptions
- [ ] Categorize exceptions (keep vs. migrate)
- [ ] Identify common patterns
### ErrorCode Creation
- [ ] Create `{Module}ErrorCode.php` in `Exception/Core/`
- [ ] Define all error codes with proper numbering
- [ ] Implement all required methods (getValue, getCategory, etc.)
- [ ] Set appropriate severity levels
- [ ] Add recovery hints for recoverable errors
- [ ] Set retry seconds for infrastructure errors
### Exception Classes
- [ ] Create `{Module}Exception.php` base class
- [ ] Create specific exception classes in `{Module}/Exceptions/`
- [ ] Use factory methods instead of constructors
- [ ] Build rich ExceptionContext
- [ ] Choose appropriate ErrorCode
### Service Migration
- [ ] Keep constructor InvalidArgumentException
- [ ] Replace business logic RuntimeException
- [ ] Add use statements for new exceptions
- [ ] Improve error messages
- [ ] Add context data
### Testing
- [ ] Create debug test file
- [ ] Run module test suite
- [ ] Verify ErrorHandler integration
- [ ] Check HTTP status codes
- [ ] Validate recovery hints (debug mode)
### Documentation
- [ ] Update module documentation
- [ ] Add exception handling section
- [ ] Document error codes
- [ ] Provide usage examples
## Next Steps After Migration
Once your module is migrated:
1. **Monitor Production**: Watch for any regression issues
2. **Collect Feedback**: Get team feedback on error clarity
3. **Iterate**: Refine error messages based on feedback
4. **Share Learnings**: Update this guide with lessons learned
5. **Help Others**: Assist with other module migrations
## Support & Resources
- **Pattern Guide**: [exception-hierarchy-pattern.md](./exception-hierarchy-pattern.md)
- **Enhancement Guide**: [error-handler-enhancements.md](./error-handler-enhancements.md)
- **ErrorCode Reference**: See `src/Framework/Exception/Core/` directory
- **Example Module**: Queue module (`src/Framework/Queue/`)
## Framework Exception Migrations (Completed)
The framework's core exceptions have been successfully migrated to the new exception hierarchy system.
### Migration Summary
**Total Time**: ~6 hours across multiple phases
**Exception Classes Migrated**: 40+ framework exceptions
**ErrorCode Enums Created**: 5 category-specific enums
**Test Coverage**: 100% integration test pass rate
### Phase 1: ErrorCode Enum Creation
**Created Category-Specific Error Code Enums**:
1. **DatabaseErrorCode** (`src/Framework/Exception/Core/DatabaseErrorCode.php`)
- 8 cases covering connection, query, transaction, and schema errors
- Severity: CONNECTION_FAILED (CRITICAL), QUERY_FAILED (ERROR), etc.
- Recovery: Retry logic for temporary issues, non-recoverable for constraint violations
2. **AuthErrorCode** (`src/Framework/Exception/Core/AuthErrorCode.php`)
- 10 cases covering authentication and authorization
- Severity: TOKEN_EXPIRED (WARNING), ACCOUNT_LOCKED (ERROR), etc.
- Recovery: Most auth errors are non-recoverable, require user action
3. **HttpErrorCode** (`src/Framework/Exception/Core/HttpErrorCode.php`)
- 8 cases covering HTTP protocol errors
- Severity: BAD_REQUEST (WARNING), INTERNAL_SERVER_ERROR (CRITICAL)
- Recovery: Client errors non-recoverable, server errors may retry
4. **SecurityErrorCode** (`src/Framework/Exception/Core/SecurityErrorCode.php`)
- 10 cases covering security threats and attacks
- Severity: SQL_INJECTION_DETECTED (CRITICAL), CSRF_TOKEN_INVALID (ERROR)
- Recovery: Security violations are never recoverable
5. **ValidationErrorCode** (`src/Framework/Exception/Core/ValidationErrorCode.php`)
- 8 cases covering input and business validation
- Severity: INVALID_INPUT (WARNING), BUSINESS_RULE_VIOLATION (ERROR)
- Recovery: Validation errors recoverable with corrected input
### Phase 2: Database Exception Migration
**Migrated Exceptions**:
- `DatabaseConnectionException` → DatabaseErrorCode::CONNECTION_FAILED
- `QueryExecutionException` → DatabaseErrorCode::QUERY_FAILED
- `TransactionException` → DatabaseErrorCode::TRANSACTION_FAILED
- `EntityNotFoundException` → DatabaseErrorCode::ENTITY_NOT_FOUND
- `ConstraintViolationException` → DatabaseErrorCode::CONSTRAINT_VIOLATION
**Example Migration**:
```php
// Before
throw new \RuntimeException("Database connection failed");
// After
throw DatabaseConnectionException::connectionRefused(
$host,
$port,
$previous
);
// Uses DatabaseErrorCode::CONNECTION_FAILED with rich context
```
### Phase 3.2: Authentication/Authorization Exception Migration
**Migrated Exceptions**:
- `InvalidCredentialsException` → AuthErrorCode::CREDENTIALS_INVALID
- `TokenExpiredException` → AuthErrorCode::TOKEN_EXPIRED
- `SessionExpiredException` → AuthErrorCode::SESSION_EXPIRED
- `AccountLockedException` → AuthErrorCode::ACCOUNT_LOCKED
- `InsufficientPermissionsException` → AuthErrorCode::INSUFFICIENT_PERMISSIONS
**Example Migration**:
```php
// Before
throw new \RuntimeException("Invalid credentials");
// After
throw InvalidCredentialsException::forUser($username);
// Uses AuthErrorCode::CREDENTIALS_INVALID with security context
```
### Phase 3.3: HTTP Exception Migration
**Migrated Exceptions**:
- `BadRequestException` → HttpErrorCode::BAD_REQUEST
- `NotFoundException` → HttpErrorCode::NOT_FOUND
- `MethodNotAllowedException` → HttpErrorCode::METHOD_NOT_ALLOWED
- `InternalServerErrorException` → HttpErrorCode::INTERNAL_SERVER_ERROR
**Example Migration**:
```php
// Before
throw new \RuntimeException("Resource not found");
// After
throw NotFoundException::forResource($resourceType, $resourceId);
// Uses HttpErrorCode::NOT_FOUND with HTTP context
```
### Phase 3.4: Security Exception Migration
**Migrated Exceptions** (OWASP-Compliant):
- `CsrfValidationFailedException` → SecurityErrorCode::CSRF_TOKEN_INVALID
- `SqlInjectionAttemptException` → SecurityErrorCode::SQL_INJECTION_DETECTED
- `XssAttemptException` → SecurityErrorCode::XSS_DETECTED
- `PathTraversalAttemptException` → SecurityErrorCode::PATH_TRAVERSAL_DETECTED
**Example Migration**:
```php
// Before
throw new \RuntimeException("CSRF token validation failed");
// After
throw CsrfValidationFailedException::tokenValidationFailed($formId);
// Uses SecurityErrorCode::CSRF_TOKEN_INVALID with security event logging
```
**Security Features**:
- Automatic OWASP security event logging
- Attack pattern analysis (SQL injection types, XSS vectors)
- IOC (Indicator of Compromise) generation
- WAF rule suggestions
- Threat severity assessment
### Phase 3.5: Validation Exception Migration
**Result**: No validation exceptions found using old ErrorCode constants.
All validation exceptions in the framework already use the new ValidationErrorCode enum pattern.
### Migration Benefits Achieved
**1. Type Safety**
```php
// Before: Generic catch
catch (\RuntimeException $e) { }
// After: Specific exception handling
catch (DatabaseConnectionException $e) {
// Handle database connection specifically
$this->fallbackToCache();
}
```
**2. Rich Error Context**
```php
// Automatic context includes:
[
'operation' => 'user.login',
'component' => 'AuthService',
'error_code' => 'AUTH002',
'category' => 'AUTH',
'severity' => 'ERROR',
'timestamp' => '2024-01-15 14:32:10',
'request_id' => 'req_abc123',
// + custom data
]
```
**3. Category-Based Monitoring**
```php
// Monitor by category
if ($exception->isCategory('DB')) {
$this->metricsCollector->incrementDatabaseErrors();
}
// Check specific error code
if ($exception->isErrorCode(DatabaseErrorCode::CONNECTION_FAILED)) {
$this->alertOps('Database connection down');
}
```
**4. Automatic HTTP Status Mapping**
```php
// ErrorHandler automatically maps:
DatabaseErrorCode::ENTITY_NOT_FOUND 404 Not Found
AuthErrorCode::CREDENTIALS_INVALID 401 Unauthorized
HttpErrorCode::BAD_REQUEST 400 Bad Request
SecurityErrorCode::CSRF_TOKEN_INVALID 403 Forbidden
ValidationErrorCode::INVALID_INPUT 422 Unprocessable Entity
```
**5. Recovery Hints**
```php
// ErrorCode provides recovery guidance
$errorCode->getRecoveryHint();
// "Check database connection and retry operation"
$errorCode->isRecoverable(); // true/false
$errorCode->getRetryAfterSeconds(); // 60 for temporary issues
```
### Testing Results
**Integration Tests**: 100% Pass Rate
```bash
./vendor/bin/pest tests/Unit/ErrorHandling/ErrorHandlerFullPipelineTest.php
✓ it handles database connection exceptions correctly
✓ it handles authentication exceptions with proper status codes
✓ it handles HTTP exceptions with correct status mapping
✓ it handles security exceptions with OWASP event logging
✓ it provides recovery hints in debug mode
5 tests passed, 26 assertions
```
### Next Steps
**Recommended Migrations** (Optional):
1. **Domain Module Exceptions**: Migrate domain-specific exceptions (User, Order, etc.)
2. **Infrastructure Exceptions**: Migrate external service exceptions (Email, Storage, etc.)
3. **Application Exceptions**: Migrate controller and API exceptions
**Maintenance**:
- Monitor error logs for any regressions
- Update error messages based on user feedback
- Add new ErrorCode cases as needed for new features
- Keep exception tests up to date
## Conclusion
Exception migration improves:
-**Error Clarity**: Descriptive messages with context
-**Type Safety**: Catch specific exceptions, not generic ones
-**Debugging**: Rich context for troubleshooting
-**Monitoring**: Category-based alerting and metrics
-**Recovery**: Automatic retry strategies and hints
-**Documentation**: Self-documenting exception hierarchy
-**HTTP Status**: Automatic status code mapping
-**Logging**: Severity-based log levels
Take your time, test thoroughly, and don't hesitate to refactor as you learn better patterns!