visual-craft/rest-base-bundle

Symfony 扩展包,为 REST API 应用提供基础框架

v0.3.0 2023-12-06 09:50 UTC

This package is auto-updated.

Last update: 2024-09-11 16:57:49 UTC


README

Symfony 扩展包,为 REST API 应用提供基础框架。特性包括:

  • 异常转换器:将异常和错误转换为结构化响应(JSON, XML)。
  • (自 v0.3 版本已弃用) 请求体反序列化器:对 HTTP 请求体和 Accept 头的 RESTful 解码。
  • (自 v0.3 版本已弃用) 失败验证器:与异常转换器集成的数据验证器。
  • 使用请求属性检查 API 区域。
  • 自 Symfony 6.3 支持:MapRequestPayload, MapQueryStringMapQueryParameter

安装

步骤 1:安装扩展包

$ composer require visual-craft/rest-base-bundle

步骤 2:启用扩展包

如果您不使用 Flex,您还必须在 app/AppKernel.php 中添加以下行来启用扩展包:

<?php
// AppKernel.php

// ...
use VisualCraft\RestBaseBundle\VisualCraftRestBaseBundle;

class AppKernel extends Kernel
{
    public function registerBundles()
    {
        return [
            // ...
            new VisualCraftRestBaseBundle(),
            // ...
        ];
    }
}

序列化器

默认情况下,序列化组件会忽略未映射到反序列化对象的其他附加属性。如果您希望在这种情况下抛出异常,请将 allow_extra_attributes 上下文选项设置为 false

# config/packages/framework.yaml

framework:
    serializer:
        default_context:
            allow_extra_attributes: false

错误

配置

使用区域配置,您可以指定启用错误转换器的应用程序区域。

示例

#config/packages/rest-base.php
visual_craft_rest_base:
    zone:
        -
            path: '^/api/'
            host: null
            methods: []
            ips: []

支持的异常

Symfony\Component\Security\Core\Exception\AuthenticationException

所有认证异常。

响应体

{
  "title": "Authentication error: An authentication exception occurred.",
  "statusCode": 401,
  "type": "authentication_error",
  "details": []
}

Symfony\Component\HttpKernel\Exception\HttpExceptionInterface

HTTP 错误异常。

响应体

{
  "title": "HTTP error: Not Found",
  "statusCode": 404,
  "type": "http_error",
  "details": []
}

在 HttpException 之前抛出的先前异常

  • Symfony\Component\Serializer\Exception\UnsupportedFormatException

响应体

{
  "title": "Invalid request content type",
  "statusCode": 400,
  "type": "invalid_request_content_type",
  "details": []
}
  • Symfony\Component\Validator\Exception\ValidationFailedException

响应体

{
  "title": "Validation error",
  "statusCode": 400,
  "type": "validation_error",
  "details": {
    "cause": "validation_error",
    "violations": {
      //serialized by symfony/serializer ConstraintViolationList
    }
  }
}

VisualCraft\RestBaseBundle\Problem\ExceptionToProblemConverters\InsufficientAuthenticationException

如果用户凭据不够可信时抛出。这种情况发生在用户是匿名用户并且要显示的资源有一个访问角色时。

响应体

{
  "title": "Insufficient authentication error: Not privileged to request the resource.",
  "statusCode": 401,
  "type": "insufficient_authentication_error",
  "details": []
}

(自 v0.3 版本已弃用) VisualCraft\RestBaseBundle\Exceptions\InvalidRequestException

如果请求体无效时抛出的基本异常。

响应体

{
  "title": "Invalid request",
  "statusCode": 400,
  "type": "invalid_request",
  "details": []
}

(自 v0.3 版本已弃用) VisualCraft\RestBaseBundle\Exceptions\InvalidRequestBodyFormatException

从 InvalidRequestException 继承。当 symfony/serializer 无法反序列化请求体时抛出。

响应体

{
  "title": "Invalid request body format",
  "statusCode": 400,
  "type": "invalid_request_body_format",
  "details": {
    "cause": "unexpected_value"
  }
}

"cause" 字段值

  • "unexpected_value" 如果数据字段有无效值
  • "extra_attributes" 如果请求体有额外属性

(自 v0.3 版本已弃用) VisualCraft\RestBaseBundle\Exceptions\InvalidRequestContentTypeException

从 InvalidRequestException 继承。当没有指向内容类型参数或者内容类型有不受支持值时抛出。

响应体

{
  "title": "Invalid request content type",
  "statusCode": 400,
  "type": "invalid_request_content_type",
  "details": {
    "code": "unsupported",
    "valid_content_types": ["application/json'"]
  }
}

"code" 字段值

  • "missing" : 如果没有指向内容类型
  • "unsupported" : 如果内容类型有不受支持的值

(自 v0.3 版本已弃用) VisualCraft\RestBaseBundle\Exceptions\ValidationErrorException

响应体

{
  "title": "Validation error",
  "statusCode": 400,
  "type": "validation_error",
  "details": {
    "violations": {
        //serialized by symfony/serializer ConstraintViolationList
    }
  }
}

Symfony\Component\Serializer\Exception\ExtraAttributesException

响应体

{
  "title": "Invalid request body format",
  "statusCode": 400,
  "type": "invalid_request_body_format",
  "details": {
    "cause": "extra_attributes"
  }
}

Symfony\Component\Serializer\Exception\UnexpectedValueException

响应体

{
  "title": "Invalid request body format",
  "statusCode": 400,
  "type": "invalid_request_body_format",
  "details": {
    "cause": "unexpected_value"
  }
}

启用安全异常支持

如果您为 API 使用单独的防火墙,请使用 VisualCraft\RestBaseBundle\Security\AuthenticationEntryPoint

#config/packages/security.php
security:
    firewalls:
        main:
            entry_point: 'VisualCraft\RestBaseBundle\Security\AuthenticationEntryPoint'
            //..

如果您想使用自定义的入口点类,请按以下方式编辑您的类:

<?php

declare(strict_types=1);

namespace App\Security;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
use VisualCraft\RestBaseBundle\Constants;

class AuthenticationEntryPoint implements AuthenticationEntryPointInterface
{

      private ProblemResponseFactory $problemResponseFactory;

    public function __construct(ProblemResponseFactory $problemResponseFactory)
    {
        $this->problemResponseFactory = $problemResponseFactory;
    }

    public function start(Request $request, AuthenticationException $authException = null): Response
    {
        if ($request->attributes->get(Constants::API_ZONE_ATTRIBUTE)) {
             return $this->problemResponseFactory->create($authException ?? new AuthenticationException('Authentication required'));
        }

        // Not API zone handle
    }
}

如果 API 中使用 Authenticator,请使用 VisualCraft\RestBaseBundle\Security\AuthenticationFailureHandler 进行认证错误转换

#config/packages/security.php
security:
    firewalls:
        main:
            json_login:
                # ....
                failure_handler: 'VisualCraft\RestBaseBundle\Security\AuthenticationFailureHandler'

支持自定义异常

您可以创建并添加您自己的异常及其转换器。

  • 创建您的异常

    <?php
    
    declare(strict_types=1);
    
    namespace App\Exceptions;
    
    class CustomException extends \RuntimeException
    {
        private string $customField;
    
        public function __construct(string $customField, $message = "")
        {
            parent::__construct($message);
            $this->customField = $customField;
        }
    
        public function getCustomField(): string
        {
            return $this->customField;
        }
    }
  • 创建转换器(实现 VisualCraft\RestBaseBundle\Problem\ExceptionToProblemConverterInterface)

    <?php
    //src/Problem/ExceptionToProblemConverters/InvalidRequestBodyFormatExceptionConverter.php
    
    declare(strict_types=1);
    
    namespace VisualCraft\RestBaseBundle\Problem\ExceptionToProblemConverters;
    
    use Symfony\Component\HttpFoundation\Response;
    use VisualCraft\RestBaseBundle\Problem\ExceptionToProblemConverterInterface;
    use VisualCraft\RestBaseBundle\Problem\Problem;
    
    class CustomExceptionConverter implements ExceptionToProblemConverterInterface
    {
        public function convert(\Throwable $exception): ?Problem
        {
            if (!$exception instanceof CustomException) {
                return null;
            }
    
            $result = new Problem(
                'Custom exception title',
                Response::HTTP_BAD_REQUEST,
                'custom_exception_type'
            );
            $result->addDetails('customField', $exception->getCustomField());
    
            return $result;
        }
    }
  • 将您的类注册为服务,并用visual_craft.rest_base.exception_to_problem_converter标记。如果您使用自动配置,Symfony会自动添加此标记。

  • 抛出异常

    <?php
    //src/Controller
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\HttpFoundation\Request;
    
    class ThrowInvalidRequestExceptionController extends AbstractController
    {
        public function __invoke(Request $request): void
        {
            //..
            throw new CustomException('custom field value');
            //..
        }
    }
  • 响应体

    {
        "title" : "Custom exception title",
        "statusCode" : 400,
        "type" : "custom_exception_type",
        "details" : {
            "customField" : "custom field value"
        }
    }

请求体反序列化器

Api体反序列化器包含

  • 检测和检查反序列化格式
  • 使用symfony/serializer反序列化并处理异常
  • 使用失败的验证器进行验证

示例

<?php
// src/Controller/ProcessRequestController.php

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use VisualCraft\RestBaseBundle\Request\RequestBodyDeserializer;

class ProcessRequestController extends AbstractController
{
    private RequestBodyDeserializer $deserializer;

    public function __construct(RequestBodyDeserializer $deserializer)
    {
        $this->deserializer = $deserializer;
    }

    public function __invoke(Request $request): Response
    {
        //..
        $testDto = $this->deserializer->deserialize($request, Dto::class);
        //..
    }
}

内容类型配置

#config/packages/rest-base.php
visual_craft_rest_base:
    mimeTypes:
        json: 'application/json'
        //..

调试

要启用错误响应体中的异常堆栈跟踪,需要更改配置

#config/packages/rest-base.php
visual_craft_rest_base:
    debug: true

错误响应示例

{
    // ....
    "details": {
        "exception": {
            "class": "Namespace\\ExceptionClass",
            "message": "Exception message",
            "stack_trace": "Stack trace as a string"
        },
        "previous_exception_1": {
            "class": "Namespace\\PrevExceptionClass",
            "message": "Prev exception message",
            "stack_trace": "Prev stack trace as a string"
        }
        // ....
    }
}

失败的验证器

该Bundle还提供了失败的验证器,用于验证您的数据。在验证违规的情况下,它抛出异常,这些异常由异常转换器支持,因此当数据无效时,您将收到结构化响应

<?php
// src/Controller/ProcessRequestController.php

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use VisualCraft\RestBaseBundle\Request\RequestBodyDeserializer;

class ProcessRequestController extends AbstractController
{
    private FailingValidator $validator;

    public function __construct(
        FailingValidator $validator
    ) {
        $this->validator = $validator;
    }

    public function __invoke(Request $request): Response
    {
        //..
        $this->validator->validate($data);
        //..
    }
}

测试

$ vendor/bin/simple-phpunit install
$ vendor/bin/simple-phpunit

附加工具

$ composer install
$ vendor/bin/php-cs-fixer fix
$ vendor/bin/psalm

许可

此Bundle根据MIT许可证发布。请参阅文件中的完整许可证:LICENSE