methorz/http-problem-details 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

methorz/http-problem-details

最新稳定版本:v1.0.0

Composer 安装命令:

composer require methorz/http-problem-details

包简介

PSR-15 error handling middleware with RFC 7807 Problem Details support

README 文档

README

Comprehensive error handling middleware for PSR-15 applications with RFC 7807 Problem Details support

CI codecov PHPStan PHP Version License

Production-ready error handling with RFC 7807 Problem Details, environment-aware formatting, automatic logging, and developer-friendly stack traces. Zero configuration, works out-of-the-box.

✨ Features

  • 📋 RFC 7807 Compliance - Standardized error responses (Problem Details)
  • 🔍 Environment-Aware - Stack traces in dev, sanitized messages in production
  • 📝 Automatic Logging - PSR-3 logger integration with context
  • 🎯 Custom Exception Mapping - Map exceptions to HTTP status codes
  • 🔗 Exception Chaining - Captures and formats previous exception details
  • 💡 Developer-Friendly - Detailed debugging info in development mode
  • 🌍 Production-Safe - No sensitive data leaks in production
  • 🎨 Framework Agnostic - Works with any PSR-15 application

📦 Installation

composer require methorz/http-problem-details

🚀 Quick Start

Basic Usage

use MethorZ\ProblemDetails\Middleware\ErrorHandlerMiddleware;
use Nyholm\Psr7\Factory\Psr17Factory;

$middleware = new ErrorHandlerMiddleware(
    new Psr17Factory()
);

// Add to middleware pipeline (first!)
$app->pipe($middleware);

Production Response (500 Internal Server Error):

{
  "type": "about:blank",
  "title": "Internal Server Error",
  "status": 500,
  "detail": "An error occurred"
}

Development Response (with stack trace):

{
  "type": "about:blank",
  "title": "Internal Server Error",
  "status": 500,
  "detail": "Division by zero",
  "trace": "#0 /path/to/file.php(42): calculate()...",
  "file": "/path/to/file.php",
  "line": 42,
  "request_method": "POST",
  "request_uri": "https://api.example.com/calculate",
  "exception_class": "DivisionByZeroError"
}

📖 Detailed Usage

With Logger Integration

use MethorZ\ProblemDetails\Middleware\ErrorHandlerMiddleware;
use Nyholm\Psr7\Factory\Psr17Factory;
use Monolog\Logger;

$logger = new Logger('app');

$middleware = new ErrorHandlerMiddleware(
    new Psr17Factory(),
    $logger // PSR-3 logger
);

Logged Context:

[2024-11-26 10:30:00] app.ERROR: Exception caught: User not found {
    "exception_class": "App\\Exception\\NotFoundException",
    "exception_message": "User not found",
    "exception_code": 0,
    "exception_file": "/app/src/Service/UserService.php",
    "exception_line": 42,
    "request_method": "GET",
    "request_uri": "https://api.example.com/users/123"
}

Development vs Production Mode

// Development: Include stack traces and debug info
$devMiddleware = new ErrorHandlerMiddleware(
    $responseFactory,
    $logger,
    isDevelopment: true // ← Enable debug mode
);

// Production: Sanitized error messages
$prodMiddleware = new ErrorHandlerMiddleware(
    $responseFactory,
    $logger,
    isDevelopment: false // ← Production safe
);

Custom Exception Status Mapping

use App\Exception\NotFoundException;
use App\Exception\ValidationException;

$middleware = new ErrorHandlerMiddleware(
    $responseFactory,
    $logger,
    exceptionStatusMap: [
        NotFoundException::class => 404,
        ValidationException::class => 422,
        \InvalidArgumentException::class => 400,
    ]
);

Before:

  • NotFoundException → 500 Internal Server Error ❌

After:

  • NotFoundException → 404 Not Found ✅
  • ValidationException → 422 Unprocessable Entity ✅

🎯 RFC 7807 Problem Details

Building Custom Problem Details

use MethorZ\ProblemDetails\Response\ProblemDetails;
use Nyholm\Psr7\Response;

$problem = ProblemDetails::create(404, 'Not Found')
    ->withType('https://api.example.com/problems/user-not-found')
    ->withDetail('User with ID 123 does not exist')
    ->withInstance('/api/users/123')
    ->withAdditional('user_id', 123);

$response = $problem->toResponse(new Response());

Response:

{
  "type": "https://api.example.com/problems/user-not-found",
  "title": "Not Found",
  "status": 404,
  "detail": "User with ID 123 does not exist",
  "instance": "/api/users/123",
  "user_id": 123
}

Creating from Exception

$exception = new NotFoundException('User not found');

// Production mode
$problem = ProblemDetails::fromException($exception, includeTrace: false);

// Development mode
$problem = ProblemDetails::fromException($exception, includeTrace: true);

🔍 Environment-Aware Behavior

Development Mode (isDevelopment: true)

Response includes:

  • ✅ Full exception message
  • ✅ Stack trace
  • ✅ File path and line number
  • ✅ Exception class name
  • ✅ Request method and URI
  • ✅ Previous exception chain

Use when: Local development, staging, testing

Production Mode (isDevelopment: false)

Response includes:

  • ✅ HTTP status code
  • ✅ Generic title
  • ✅ Exception message (if safe)
  • ❌ NO stack traces
  • ❌ NO file paths
  • ❌ NO internal details

Use when: Production, public APIs

📊 HTTP Status Code Mapping

Exception Type Status Code Log Level
Client errors (4xx) 400-499 warning
Server errors (5xx) 500-599 error

Supported Status Codes:

400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
405 Method Not Allowed
408 Request Timeout
409 Conflict
422 Unprocessable Entity
429 Too Many Requests
500 Internal Server Error
501 Not Implemented
502 Bad Gateway
503 Service Unavailable
504 Gateway Timeout

🔗 Exception Chaining

Automatically captures and formats exception chains:

try {
    $db->connect(); // Throws PDOException
} catch (PDOException $e) {
    throw new DatabaseException('Failed to connect', 0, $e); // Wraps PDOException
}

Development Response (with previous_exception):

{
  "status": 500,
  "title": "Internal Server Error",
  "detail": "Failed to connect",
  "trace": "...",
  "previous_exception": {
    "class": "PDOException",
    "message": "SQLSTATE[HY000] [2002] Connection refused",
    "file": "/app/src/Database.php",
    "line": 25
  }
}

🧪 Testing

# Run tests
composer test

# Static analysis
composer analyze

# Code style
composer cs-check
composer cs-fix

Test Coverage: 21 tests, 59 assertions, 100% passing

🛠️ Use Cases

1. REST API Error Handling

// Global error handler (first middleware)
$app->pipe(new ErrorHandlerMiddleware(
    $responseFactory,
    $logger,
    isDevelopment: $_ENV['APP_ENV'] === 'development'
));

// All uncaught exceptions become RFC 7807 responses

2. Custom Application Exceptions

namespace App\Exception;

class UserNotFoundException extends \RuntimeException
{
    public function getStatusCode(): int
    {
        return 404; // ← Automatically used by middleware
    }
}

3. Validation Error Responses

$middleware = new ErrorHandlerMiddleware(
    $responseFactory,
    $logger,
    exceptionStatusMap: [
        ValidationException::class => 422,
    ]
);

throw new ValidationException('Email is required');
// → 422 Unprocessable Entity with Problem Details

4. Microservices Error Consistency

All services return the same RFC 7807 format:

{
  "type": "about:blank",
  "title": "Not Found",
  "status": 404,
  "detail": "Resource not found"
}

🔧 Configuration Examples

Mezzio / Laminas

// config/autoload/middleware.global.php
use MethorZ\ProblemDetails\Middleware\ErrorHandlerMiddleware;

return [
    'dependencies' => [
        'factories' => [
            ErrorHandlerMiddleware::class => function ($container): ErrorHandlerMiddleware {
                return new ErrorHandlerMiddleware(
                    $container->get(ResponseFactoryInterface::class),
                    $container->get(LoggerInterface::class),
                    isDevelopment: $_ENV['APP_ENV'] === 'development',
                    exceptionStatusMap: [
                        NotFoundException::class => 404,
                        ValidationException::class => 422,
                    ],
                );
            },
        ],
    ],
];

// config/pipeline.php
$app->pipe(ErrorHandlerMiddleware::class); // FIRST middleware!

Slim Framework

use MethorZ\ProblemDetails\Middleware\ErrorHandlerMiddleware;

$app->add(new ErrorHandlerMiddleware(
    $responseFactory,
    $logger,
    isDevelopment: $_ENV['DEBUG'] === 'true'
));

📚 Resources

🔗 Related Packages

This package is part of the MethorZ HTTP middleware ecosystem:

Package Description
methorz/http-dto Automatic HTTP ↔ DTO conversion with validation
methorz/http-problem-details RFC 7807 error handling (this package)
methorz/http-cache-middleware HTTP caching with ETag support
methorz/http-request-logger Structured logging with request tracking
methorz/openapi-generator Automatic OpenAPI spec generation

These packages work together seamlessly in PSR-15 applications.

💡 Best Practices

DO

  • ✅ Place error middleware FIRST in pipeline
  • ✅ Use isDevelopment based on environment variable
  • ✅ Map domain exceptions to appropriate HTTP status codes
  • ✅ Log exceptions with context for debugging
  • ✅ Use PSR-3 logger for centralized log management

DON'T

  • ❌ Don't expose stack traces in production (isDevelopment: false)
  • ❌ Don't return 500 for client errors (use 4xx instead)
  • ❌ Don't log sensitive data (passwords, tokens) in error context
  • ❌ Don't catch errors before error middleware (let it handle them)

🔒 Security Considerations

Sensitive Data

  • ✅ Production mode hides file paths and stack traces
  • ✅ Exception messages are still included (ensure they're safe!)
  • ✅ Logger context can be filtered/redacted
  • ❌ Don't include passwords, tokens, or PII in exception messages

Information Disclosure

// ❌ BAD: Leaks sensitive info
throw new Exception("DB connection failed: password='secret123'");

// ✅ GOOD: Generic message
throw new DatabaseException("Failed to connect to database");

📄 License

MIT License. See LICENSE for details.

🤝 Contributing

Contributions welcome! See CONTRIBUTING.md for guidelines.

🔗 Links

统计信息

  • 总下载量: 50
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 3
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-11-27