fix: Gitea Traefik routing and connection pool optimization
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
This commit is contained in:
645
docs/features/error-handling/guide.md
Normal file
645
docs/features/error-handling/guide.md
Normal file
@@ -0,0 +1,645 @@
|
||||
# Error Handling & Debugging
|
||||
|
||||
This guide covers error handling patterns and debugging strategies in the framework.
|
||||
|
||||
## Exception Handling
|
||||
|
||||
All custom exceptions in the framework must extend `FrameworkException` to ensure consistent error handling, logging, and recovery mechanisms.
|
||||
|
||||
### The FrameworkException System
|
||||
|
||||
The framework provides a sophisticated exception system with:
|
||||
- **ExceptionContext**: Rich context information for debugging
|
||||
- **ErrorCode**: Categorized error codes with recovery hints
|
||||
- **RetryAfter**: Support for recoverable operations
|
||||
- **Fluent Interface**: Easy context building
|
||||
|
||||
### Creating Custom Exceptions
|
||||
|
||||
```php
|
||||
namespace App\Domain\User\Exceptions;
|
||||
|
||||
use App\Framework\Exception\FrameworkException;
|
||||
use App\Framework\Exception\Core\DatabaseErrorCode;
|
||||
use App\Framework\Exception\ExceptionContext;
|
||||
|
||||
final class UserNotFoundException extends FrameworkException
|
||||
{
|
||||
public static function byId(UserId $id): self
|
||||
{
|
||||
return self::create(
|
||||
DatabaseErrorCode::ENTITY_NOT_FOUND,
|
||||
"User with ID '{$id->toString()}' not found"
|
||||
)->withData([
|
||||
'user_id' => $id->toString(),
|
||||
'search_type' => 'by_id'
|
||||
]);
|
||||
}
|
||||
|
||||
public static function byEmail(Email $email): self
|
||||
{
|
||||
$context = ExceptionContext::forOperation('user.lookup', 'UserRepository')
|
||||
->withData(['email' => $email->getMasked()]);
|
||||
|
||||
return self::fromContext(
|
||||
"User with email not found",
|
||||
$context,
|
||||
DatabaseErrorCode::ENTITY_NOT_FOUND
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Using ErrorCode Enums
|
||||
|
||||
The framework provides category-specific error code enums for better organization and type safety:
|
||||
|
||||
```php
|
||||
use App\Framework\Exception\Core\DatabaseErrorCode;
|
||||
use App\Framework\Exception\Core\AuthErrorCode;
|
||||
use App\Framework\Exception\Core\HttpErrorCode;
|
||||
use App\Framework\Exception\Core\SecurityErrorCode;
|
||||
use App\Framework\Exception\Core\ValidationErrorCode;
|
||||
|
||||
// Database errors
|
||||
DatabaseErrorCode::CONNECTION_FAILED
|
||||
DatabaseErrorCode::QUERY_FAILED
|
||||
DatabaseErrorCode::TRANSACTION_FAILED
|
||||
DatabaseErrorCode::CONSTRAINT_VIOLATION
|
||||
|
||||
// Authentication errors
|
||||
AuthErrorCode::CREDENTIALS_INVALID
|
||||
AuthErrorCode::TOKEN_EXPIRED
|
||||
AuthErrorCode::SESSION_EXPIRED
|
||||
AuthErrorCode::ACCOUNT_LOCKED
|
||||
|
||||
// HTTP errors
|
||||
HttpErrorCode::BAD_REQUEST
|
||||
HttpErrorCode::NOT_FOUND
|
||||
HttpErrorCode::METHOD_NOT_ALLOWED
|
||||
HttpErrorCode::RATE_LIMIT_EXCEEDED
|
||||
|
||||
// Security errors
|
||||
SecurityErrorCode::CSRF_TOKEN_INVALID
|
||||
SecurityErrorCode::SQL_INJECTION_DETECTED
|
||||
SecurityErrorCode::XSS_DETECTED
|
||||
SecurityErrorCode::PATH_TRAVERSAL_DETECTED
|
||||
|
||||
// Validation errors
|
||||
ValidationErrorCode::INVALID_INPUT
|
||||
ValidationErrorCode::REQUIRED_FIELD_MISSING
|
||||
ValidationErrorCode::BUSINESS_RULE_VIOLATION
|
||||
ValidationErrorCode::INVALID_FORMAT
|
||||
|
||||
// Using error codes in exceptions:
|
||||
throw FrameworkException::create(
|
||||
DatabaseErrorCode::QUERY_FAILED,
|
||||
"Failed to execute user query"
|
||||
)->withContext(
|
||||
ExceptionContext::forOperation('user.find', 'UserRepository')
|
||||
->withData(['query' => 'SELECT * FROM users WHERE id = ?'])
|
||||
->withDebug(['bind_params' => [$userId]])
|
||||
);
|
||||
```
|
||||
|
||||
### Exception Context Building
|
||||
|
||||
```php
|
||||
// Method 1: Using factory methods
|
||||
$exception = FrameworkException::forOperation(
|
||||
'payment.process',
|
||||
'PaymentService',
|
||||
'Payment processing failed',
|
||||
HttpErrorCode::BAD_GATEWAY
|
||||
)->withData([
|
||||
'amount' => $amount->toArray(),
|
||||
'gateway' => 'stripe',
|
||||
'customer_id' => $customerId
|
||||
])->withMetadata([
|
||||
'attempt' => 1,
|
||||
'idempotency_key' => $idempotencyKey
|
||||
]);
|
||||
|
||||
// Method 2: Building context separately
|
||||
$context = ExceptionContext::empty()
|
||||
->withOperation('order.validate', 'OrderService')
|
||||
->withData([
|
||||
'order_id' => $orderId,
|
||||
'total' => $total->toDecimal()
|
||||
])
|
||||
->withDebug([
|
||||
'validation_rules' => ['min_amount', 'max_items'],
|
||||
'failed_rule' => 'min_amount'
|
||||
]);
|
||||
|
||||
throw FrameworkException::fromContext(
|
||||
'Order validation failed',
|
||||
$context,
|
||||
ValidationErrorCode::BUSINESS_RULE_VIOLATION
|
||||
);
|
||||
```
|
||||
|
||||
### Recoverable Exceptions
|
||||
|
||||
```php
|
||||
// Creating recoverable exceptions with retry hints
|
||||
final class RateLimitException extends FrameworkException
|
||||
{
|
||||
public static function exceeded(int $retryAfter): self
|
||||
{
|
||||
return self::create(
|
||||
HttpErrorCode::RATE_LIMIT_EXCEEDED,
|
||||
'API rate limit exceeded'
|
||||
)->withRetryAfter($retryAfter)
|
||||
->withData(['retry_after_seconds' => $retryAfter]);
|
||||
}
|
||||
}
|
||||
|
||||
// Using in code
|
||||
try {
|
||||
$response = $apiClient->request($endpoint);
|
||||
} catch (RateLimitException $e) {
|
||||
if ($e->isRecoverable()) {
|
||||
$waitTime = $e->getRetryAfter();
|
||||
// Schedule retry after $waitTime seconds
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
```
|
||||
|
||||
### Exception Categories
|
||||
|
||||
```php
|
||||
// Check exception category for handling strategies
|
||||
try {
|
||||
$result = $operation->execute();
|
||||
} catch (FrameworkException $e) {
|
||||
if ($e->isCategory('AUTH')) {
|
||||
// Handle authentication errors
|
||||
return $this->redirectToLogin();
|
||||
}
|
||||
|
||||
if ($e->isCategory('VAL')) {
|
||||
// Handle validation errors
|
||||
return $this->validationErrorResponse($e);
|
||||
}
|
||||
|
||||
if ($e->isErrorCode(DatabaseErrorCode::CONNECTION_FAILED)) {
|
||||
// Handle specific database connection errors
|
||||
$this->notifyOps($e);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
```
|
||||
|
||||
### Simple Exceptions for Quick Use
|
||||
|
||||
```php
|
||||
// When you don't need the full context system
|
||||
throw FrameworkException::simple('Quick error message');
|
||||
|
||||
// With previous exception
|
||||
} catch (\PDOException $e) {
|
||||
throw FrameworkException::simple(
|
||||
'Database operation failed',
|
||||
$e,
|
||||
500
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Exception Data Sanitization
|
||||
|
||||
The framework automatically sanitizes sensitive data in exceptions:
|
||||
|
||||
```php
|
||||
// Sensitive keys are automatically redacted
|
||||
$exception->withData([
|
||||
'username' => 'john@example.com',
|
||||
'password' => 'secret123', // Will be logged as '[REDACTED]'
|
||||
'api_key' => 'sk_live_...' // Will be logged as '[REDACTED]'
|
||||
]);
|
||||
```
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Always extend FrameworkException** for custom exceptions
|
||||
2. **Use ErrorCode enum** for categorizable errors
|
||||
3. **Provide rich context** with operation, component, and data
|
||||
4. **Use factory methods** for consistent exception creation
|
||||
5. **Sanitize sensitive data** (automatic for common keys)
|
||||
6. **Make exceptions domain-specific** (UserNotFoundException vs generic NotFoundException)
|
||||
7. **Include recovery hints** for recoverable errors
|
||||
|
||||
## Unified Error Kernel Architecture
|
||||
|
||||
The framework uses a **context-aware error handling system** centered around the `ErrorKernel` class that automatically detects execution context (CLI vs HTTP) and handles errors accordingly.
|
||||
|
||||
### ErrorKernel Overview
|
||||
|
||||
**Location**: `src/Framework/ExceptionHandling/ErrorKernel.php`
|
||||
|
||||
**Key Features**:
|
||||
- Automatic context detection (CLI vs HTTP)
|
||||
- Colored console output for CLI errors
|
||||
- HTTP Response objects for web errors
|
||||
- Integration with OWASP Security Event System
|
||||
- Unified error logging via LogReporter
|
||||
|
||||
```php
|
||||
use App\Framework\ExceptionHandling\ErrorKernel;
|
||||
|
||||
final readonly class ErrorKernel
|
||||
{
|
||||
public function __construct(
|
||||
private ErrorRendererFactory $rendererFactory = new ErrorRendererFactory,
|
||||
private ?ExecutionContext $executionContext = null,
|
||||
private ?ConsoleOutput $consoleOutput = null
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Context-aware exception handler
|
||||
* - CLI: Colored console output
|
||||
* - HTTP: Logs error (middleware creates response)
|
||||
*/
|
||||
public function handle(Throwable $e, array $context = []): mixed
|
||||
{
|
||||
// Automatic logging
|
||||
$log = new LogReporter();
|
||||
$log->report($e);
|
||||
|
||||
// Context-aware handling
|
||||
$executionContext = $this->executionContext ?? ExecutionContext::detect();
|
||||
|
||||
if ($executionContext->isCli()) {
|
||||
$this->handleCliException($e);
|
||||
return null;
|
||||
}
|
||||
|
||||
// HTTP context - middleware will create response
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create HTTP Response from exception (for middleware recovery)
|
||||
*/
|
||||
public function createHttpResponse(
|
||||
Throwable $exception,
|
||||
?ExceptionContextProvider $contextProvider = null,
|
||||
bool $isDebugMode = false
|
||||
): Response {
|
||||
$renderer = new ResponseErrorRenderer($isDebugMode);
|
||||
return $renderer->createResponse($exception, $contextProvider);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### CLI Error Handling
|
||||
|
||||
**CliErrorHandler** registers global PHP error handlers for CLI context:
|
||||
|
||||
```php
|
||||
use App\Framework\ExceptionHandling\CliErrorHandler;
|
||||
use App\Framework\Console\ConsoleOutput;
|
||||
|
||||
// Registration in AppBootstrapper
|
||||
$output = new ConsoleOutput();
|
||||
$cliErrorHandler = new CliErrorHandler($output);
|
||||
$cliErrorHandler->register();
|
||||
|
||||
// Automatic colored output for errors:
|
||||
// - Red for uncaught exceptions
|
||||
// - Yellow for warnings
|
||||
// - Cyan for notices
|
||||
// - Full stack traces in CLI
|
||||
```
|
||||
|
||||
### HTTP Error Handling
|
||||
|
||||
**ExceptionHandlingMiddleware** catches exceptions in HTTP request pipeline:
|
||||
|
||||
```php
|
||||
use App\Framework\Http\Middlewares\ExceptionHandlingMiddleware;
|
||||
|
||||
#[MiddlewarePriorityAttribute(MiddlewarePriority::ERROR_HANDLING)]
|
||||
final readonly class ExceptionHandlingMiddleware implements HttpMiddleware
|
||||
{
|
||||
public function __invoke(
|
||||
MiddlewareContext $context,
|
||||
Next $next,
|
||||
RequestStateManager $stateManager
|
||||
): MiddlewareContext {
|
||||
try {
|
||||
return $next($context);
|
||||
} catch (\Throwable $e) {
|
||||
// Log exception
|
||||
$this->logger->error('Unhandled exception in HTTP request', [
|
||||
'exception' => get_class($e),
|
||||
'message' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
// Create HTTP Response
|
||||
$errorKernel = new ErrorKernel();
|
||||
$response = $errorKernel->createHttpResponse(
|
||||
$e,
|
||||
null,
|
||||
isDebugMode: false
|
||||
);
|
||||
|
||||
return $context->withResponse($response);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### OWASP Security Event Integration
|
||||
|
||||
Exceptions can trigger OWASP security events for audit logging:
|
||||
|
||||
```php
|
||||
use App\Application\Security\OWASPSecurityEventLogger;
|
||||
use App\Application\Security\OWASPEventIdentifier;
|
||||
|
||||
// Automatic security logging
|
||||
try {
|
||||
$this->authenticateUser($credentials);
|
||||
} catch (AuthenticationException $e) {
|
||||
// ErrorKernel logs exception
|
||||
$this->errorKernel->handle($e);
|
||||
|
||||
// OWASP event for security audit trail
|
||||
$this->eventDispatcher->dispatch(
|
||||
new AuthenticationFailedEvent(
|
||||
OWASPEventIdentifier::AUTHN_LOGIN_FAILURE,
|
||||
$credentials->username,
|
||||
$e->getMessage()
|
||||
)
|
||||
);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
```
|
||||
|
||||
### Legacy ErrorHandling Module Removed
|
||||
|
||||
**IMPORTANT**: The legacy `ErrorHandling` module (`src/Framework/ErrorHandling/`) has been **completely removed** as of the unified exception architecture migration.
|
||||
|
||||
**Migration Path**:
|
||||
- All error handling now uses `ErrorKernel` and `FrameworkException`
|
||||
- CLI errors: `CliErrorHandler` → `ErrorKernel`
|
||||
- HTTP errors: `ExceptionHandlingMiddleware` → `ErrorKernel`
|
||||
- Security events: Direct event dispatch via `EventDispatcher`
|
||||
|
||||
**Old Pattern** (removed):
|
||||
```php
|
||||
// ❌ Legacy - NO LONGER EXISTS
|
||||
use App\Framework\ErrorHandling\ErrorHandler;
|
||||
use App\Framework\ErrorHandling\SecurityEventLogger;
|
||||
|
||||
$errorHandler = new ErrorHandler();
|
||||
$errorHandler->register();
|
||||
```
|
||||
|
||||
**New Pattern** (current):
|
||||
```php
|
||||
// ✅ Unified - ErrorKernel
|
||||
use App\Framework\ExceptionHandling\ErrorKernel;
|
||||
|
||||
$errorKernel = new ErrorKernel();
|
||||
$errorKernel->handle($exception);
|
||||
```
|
||||
|
||||
## Logging Best Practices
|
||||
|
||||
### Automatic Exception Logging
|
||||
|
||||
All exceptions handled by `ErrorKernel` are automatically logged via `LogReporter`:
|
||||
|
||||
```php
|
||||
// Automatic logging happens in ErrorKernel::handle()
|
||||
$log = new LogReporter();
|
||||
$log->report($exception);
|
||||
|
||||
// Logs include:
|
||||
// - Exception class and message
|
||||
// - Stack trace
|
||||
// - File and line number
|
||||
// - Context data
|
||||
```
|
||||
|
||||
### Manual Logging
|
||||
|
||||
```php
|
||||
use App\Framework\Logging\Logger;
|
||||
|
||||
// Log exceptions with context
|
||||
try {
|
||||
$user = $this->userRepository->find($userId);
|
||||
} catch (UserNotFoundException $e) {
|
||||
Logger::error('User lookup failed', [
|
||||
'user_id' => $userId,
|
||||
'exception' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// Log levels
|
||||
Logger::debug('Debugging information');
|
||||
Logger::info('Informational message');
|
||||
Logger::warning('Warning condition');
|
||||
Logger::error('Error condition');
|
||||
Logger::critical('Critical failure');
|
||||
```
|
||||
|
||||
## Debug Strategies
|
||||
|
||||
### Development vs Production
|
||||
|
||||
**Development** (`APP_DEBUG=true`):
|
||||
- Full stack traces displayed
|
||||
- Detailed error messages
|
||||
- Debug data in responses
|
||||
- SQL query logging
|
||||
|
||||
**Production** (`APP_DEBUG=false`):
|
||||
- Generic error messages
|
||||
- Stack traces hidden from users
|
||||
- Errors logged server-side
|
||||
- Security-safe responses
|
||||
|
||||
### Debugging Tools
|
||||
|
||||
```php
|
||||
// Enable debug mode in ErrorKernel
|
||||
$errorKernel = new ErrorKernel(
|
||||
executionContext: ExecutionContext::cli(),
|
||||
consoleOutput: new ConsoleOutput()
|
||||
);
|
||||
|
||||
// HTTP Response with debug mode
|
||||
$response = $errorKernel->createHttpResponse(
|
||||
$exception,
|
||||
$contextProvider,
|
||||
isDebugMode: true // Shows stack trace in response
|
||||
);
|
||||
```
|
||||
|
||||
### Error Context Providers
|
||||
|
||||
```php
|
||||
use App\Framework\ExceptionHandling\Context\ExceptionContextProvider;
|
||||
|
||||
// Attach custom context to exceptions
|
||||
$contextProvider = new ExceptionContextProvider();
|
||||
$contextProvider->attachContext($exception, [
|
||||
'request_id' => $requestId,
|
||||
'user_id' => $userId,
|
||||
'operation' => 'payment.process'
|
||||
]);
|
||||
|
||||
$response = $errorKernel->createHttpResponse(
|
||||
$exception,
|
||||
$contextProvider,
|
||||
isDebugMode: false
|
||||
);
|
||||
```
|
||||
|
||||
## Error Recovery Patterns
|
||||
|
||||
### Graceful Degradation
|
||||
|
||||
```php
|
||||
// Try primary service, fallback to secondary
|
||||
try {
|
||||
return $this->primaryCache->get($key);
|
||||
} catch (CacheException $e) {
|
||||
Logger::warning('Primary cache failed, using fallback', [
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
|
||||
return $this->fallbackCache->get($key);
|
||||
}
|
||||
```
|
||||
|
||||
### Circuit Breaker Pattern
|
||||
|
||||
```php
|
||||
use App\Framework\Resilience\CircuitBreaker;
|
||||
|
||||
$circuitBreaker = new CircuitBreaker(
|
||||
failureThreshold: 5,
|
||||
timeout: Duration::fromSeconds(60)
|
||||
);
|
||||
|
||||
try {
|
||||
return $circuitBreaker->call(function() {
|
||||
return $this->externalApi->request($endpoint);
|
||||
});
|
||||
} catch (CircuitOpenException $e) {
|
||||
// Circuit is open - use cached response
|
||||
return $this->cachedResponse;
|
||||
}
|
||||
```
|
||||
|
||||
### Retry with Exponential Backoff
|
||||
|
||||
```php
|
||||
use App\Framework\Queue\ValueObjects\RetryStrategy;
|
||||
|
||||
$retryStrategy = new ExponentialBackoffStrategy(
|
||||
maxAttempts: 3,
|
||||
baseDelaySeconds: 60
|
||||
);
|
||||
|
||||
$attempt = 0;
|
||||
while ($attempt < $retryStrategy->getMaxAttempts()) {
|
||||
try {
|
||||
return $this->performOperation();
|
||||
} catch (TransientException $e) {
|
||||
$attempt++;
|
||||
if (!$retryStrategy->shouldRetry($attempt)) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$delay = $retryStrategy->getDelay($attempt);
|
||||
sleep($delay->toSeconds());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Common Error Scenarios
|
||||
|
||||
### 1. Database Connection Failure
|
||||
|
||||
```php
|
||||
try {
|
||||
$connection = $this->connectionPool->getConnection();
|
||||
} catch (ConnectionException $e) {
|
||||
// Log error
|
||||
$this->errorKernel->handle($e);
|
||||
|
||||
// Return cached data or error response
|
||||
return $this->getCachedData() ?? $this->errorResponse();
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Validation Errors
|
||||
|
||||
```php
|
||||
try {
|
||||
$user = User::create($email, $name);
|
||||
} catch (ValidationException $e) {
|
||||
// Return validation errors to user
|
||||
return new JsonResult([
|
||||
'errors' => $e->getErrors()
|
||||
], status: Status::UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Authentication Failures
|
||||
|
||||
```php
|
||||
try {
|
||||
$user = $this->authenticator->authenticate($credentials);
|
||||
} catch (AuthenticationException $e) {
|
||||
// Log security event
|
||||
$this->eventDispatcher->dispatch(
|
||||
new AuthenticationFailedEvent($credentials->username)
|
||||
);
|
||||
|
||||
// Return 401 Unauthorized
|
||||
return new JsonResult([
|
||||
'error' => 'Invalid credentials'
|
||||
], status: Status::UNAUTHORIZED);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Resource Not Found
|
||||
|
||||
```php
|
||||
try {
|
||||
$order = $this->orderRepository->find($orderId);
|
||||
} catch (OrderNotFoundException $e) {
|
||||
// Return 404 Not Found
|
||||
return new JsonResult([
|
||||
'error' => 'Order not found'
|
||||
], status: Status::NOT_FOUND);
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Rate Limit Exceeded
|
||||
|
||||
```php
|
||||
try {
|
||||
$this->rateLimiter->checkLimit($userId);
|
||||
} catch (RateLimitException $e) {
|
||||
// Return 429 Too Many Requests with retry hint
|
||||
return new JsonResult([
|
||||
'error' => 'Rate limit exceeded',
|
||||
'retry_after' => $e->getRetryAfter()
|
||||
], status: Status::TOO_MANY_REQUESTS);
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user