phpro/api-problem

RFC7807 问题详情实现

1.7.0 2023-11-24 08:41 UTC

This package is auto-updated.

Last update: 2024-08-24 10:10:58 UTC


README

Travis Installs Packagist

Api Problem

本包提供了RFC7807 问题详情实现。它可以集成到代码的任何地方,并应生成适用于 HTTP API 的一般错误响应格式。

本包只提供通用接口、异常类和一些内置的 API 问题消息。由于异常处理由框架负责,以下列出了已知的框架集成列表

安装

composer require phpro/api-problem

使用

本包提供创建 ApiProblem 值对象的通用接口。

use Phpro\ApiProblem\Exception;

throw new ApiProblemException(
    new HttpApiProblem(418, ['detail' => 'Did you know 4,000 people are injured by teapots every year?!'])
);

内置问题

ExceptionApiProblem

可调试:在调试环境中才会添加 exception 部分!

use Phpro\ApiProblem\Http\ExceptionApiProblem;

new ExceptionApiProblem(new \Exception('message', 500));
{
  "status": 500,
  "type": "http:\/\/www.w3.org\/Protocols\/rfc2616\/rfc2616-sec10.html",
  "title": "Internal Server Error",
  "detail": "message",
  "exception": {
    "message": "message",
    "type": "RuntimeException",
    "code": 500,
    "line": 23,
    "file": "exception.php",
    "trace": [
         "#0 [internal function]: ...",
         "#1 [internal function]: ...",
         "#3 [internal function]: ...",
         "..."
    ],
    "previous": [
      {
        "message": "previous",
        "type": "InvalidArgumentException",
        "code": 0,
        "line": 20,
        "file": "exception.php",
        "trace": [
            "#0 [internal function]: ...",
            "#1 [internal function]: ...",
            "#3 [internal function]: ...",
            "..."
        ]
      }
    ]
  }
}

HttpApiProblem

use Phpro\ApiProblem\Http\HttpApiProblem;

new HttpApiProblem(404, ['detail' => 'The book could not be found.']);
{
    "status": 404,
    "type": "http:\/\/www.w3.org\/Protocols\/rfc2616\/rfc2616-sec10.html",
    "title": "Not found",
    "detail": "The book could not be found."
}

验证ApiProblem

composer require symfony/validator:^4.1
use Phpro\ApiProblem\Http\ValidationApiProblem;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;

new ValidationApiProblem(new ConstraintViolationList([
    new ConstraintViolation('Invalid email', '', [], '', 'email', '', null, '8615ecd9-afcb-479a-9c78-8bcfe260cf2a'),
]));
{
    "status": 400,
    "type": "https:\/\/symfony.com\/errors\/validation",
    "title": "Validation Failed",
    "detail": "email: Invalid Email",
    "violations": [
        {
            "propertyPath": "email",
            "title": "Invalid email",
            "type": "urn:uuid:8615ecd9-afcb-479a-9c78-8bcfe260cf2a"
        }
    ]
}

BadRequestProblem

use Phpro\ApiProblem\Http\BadRequestProblem;

new BadRequestProblem('Bad request. Bad!.');
{
    "status": 400,
    "type": "http:\/\/www.w3.org\/Protocols\/rfc2616\/rfc2616-sec10.html",
    "title": "Bad Request",
    "detail": "Bad request. Bad!"
}

UnauthorizedProblem

use Phpro\ApiProblem\Http\UnauthorizedProblem;

new UnauthorizedProblem('You are not authorized to access X.');
{
    "status": 401,
    "type": "http:\/\/www.w3.org\/Protocols\/rfc2616\/rfc2616-sec10.html",
    "title": "Unauthorized",
    "detail": "You are not authenticated. Please login."
}

ForbiddenProblem

use Phpro\ApiProblem\Http\ForbiddenProblem;

new ForbiddenProblem('Not authorized to access gold.');
{
  "status": 403,
  "type": "http:\/\/www.w3.org\/Protocols\/rfc2616\/rfc2616-sec10.html",
  "title": "Forbidden",
  "detail": "Not authorized to access gold."
}

NotFoundProblem

use Phpro\ApiProblem\Http\NotFoundProblem;

new NotFoundProblem('The book with ID 20 could not be found.');
{
    "status": 404,
    "type": "http:\/\/www.w3.org\/Protocols\/rfc2616\/rfc2616-sec10.html",
    "title": "Not found",
    "detail": "The book with ID 20 could not be found."
}

MethodNotAllowedProblem

use Phpro\ApiProblem\Http\MethodNotAllowedProblem;

new MethodNotAllowedProblem('Only POST and GET allowed.');
{
    "status": 405,
    "type": "http:\/\/www.w3.org\/Protocols\/rfc2616\/rfc2616-sec10.html",
    "title": "Method Not Allowed",
    "detail": "Only POST and GET allowed."
}

ConflictProblem

use Phpro\ApiProblem\Http\ConflictProblem;
new ConflictProblem('Duplicated key for book with ID 20.');
{
    "status": 409,
    "type": "http:\/\/www.w3.org\/Protocols\/rfc2616\/rfc2616-sec10.html",
    "title": "Conflict",
    "detail": "Duplicated key for book with ID 20."
}

PreconditionFailedProblem

use Phpro\ApiProblem\Http\PreconditionFailedProblem;

new PreconditionFailedProblem('Incorrect entity tag provided.');
{
    "status": 412,
    "type": "http:\/\/www.w3.org\/Protocols\/rfc2616\/rfc2616-sec10.html",
    "title": "Precondition Failed",
    "detail": "Incorrect entity tag provided."
}

UnsupportedMediaTypeProblem

use Phpro\ApiProblem\Http\UnsupportedMediaTypeProblem;

new UnsupportedMediaTypeProblem('Please provide valid JSON.');
{
    "status": 415,
    "type": "http:\/\/www.w3.org\/Protocols\/rfc2616\/rfc2616-sec10.html",
    "title": "Unsupported Media Type",
    "detail": "Please provide valid JSON."
}

IAmATeapotProblem

use Phpro\ApiProblem\Http\IAmATeapotProblem;

new IAmATeapotProblem('More tea please.');
{
    "status": 418,
    "type": "http:\/\/www.w3.org\/Protocols\/rfc2616\/rfc2616-sec10.html",
    "title": "I'm a teapot",
    "detail": "More tea please."
}

UnprocessableEntityProblem

use Phpro\ApiProblem\Http\UnprocessableEntityProblem;

new UnprocessableEntityProblem('Unable to process the contained instructions.');
{
    "status": 422,
    "type": "http:\/\/www.w3.org\/Protocols\/rfc2616\/rfc2616-sec10.html",
    "title": "Unprocessable Entity",
    "detail": "Unable to process the contained instructions."
}

LockedProblem

use Phpro\ApiProblem\Http\LockedProblem;

new LockedProblem('This door is locked.');
{
    "status": 423,
    "type": "http:\/\/www.w3.org\/Protocols\/rfc2616\/rfc2616-sec10.html",
    "title": "Locked",
    "detail": "This door is locked."
}

PreconditionRequiredProblem

use Phpro\ApiProblem\Http\PreconditionRequiredProblem;

new PreconditionRequiredProblem('If-match header is required.');
{
    "status": 428,
    "type": "http:\/\/www.w3.org\/Protocols\/rfc2616\/rfc2616-sec10.html",
    "title": "Precondition Required",
    "detail": "If-match header is required."
}

创建自己的问题

创建问题听起来很可怕,对吧?由于 RFC 很宽松,我们使接口尽可能简单

use Phpro\ApiProblem\ApiProblemInterface;

class MyProblem implements ApiProblemInterface
{
    public function toArray(): array
    {
        return [
            'type' => 'about:blank',
            'status' => '99',
            'title' => 'Got 99 problems but a glitch aint one!',
        ];
    }
}

许多问题将在 HTTP 上下文中检测到。因此,我们还提供了一个基类 HttpApiProblem。这个类将根据 HTTP 状态码自动填充类型和标题部分。您唯一需要做的是添加一些附加数据

use Phpro\ApiProblem\Http\HttpApiProblem;

class MyProblem extends HttpApiProblem
{
    public function __construct(string $details)
    {
        parent::__construct(500, ['details' => $details]);
    }
}

如果您希望在调试环境中记录附加信息,可以实现一个额外的 DebuggableApiProblemInterface

use Phpro\ApiProblem\DebuggableApiProblemInterface;

class MyProblem implements DebuggableApiProblemInterface
{
    public function toArray(): array
    {
        return [
            'type' => 'about:blank',
            'status' => '99',
            'title' => 'Got 99 problems but a glitch ain\'t one!',
        ];
    }

    public function toDebuggableArray(): array
    {
        return array_merge(
            $this->toArray(),
            [
                'situation' => 'If you are having code problems, I feel bad for you son',
            ]
        );
    }
}

关于

提交错误和功能请求

错误和功能请求在 GitHub 上跟踪。在贡献代码之前,请查看我们的规则。

许可证

api-problem 在 MIT 许可证 下发布。