fusonic/http-kernel-extensions

此包已被弃用且不再维护。作者建议使用 fusonic/http-kernel-bundle 包代替。

Symfony HttpKernel 组件扩展。

5.3.4 2023-08-29 10:43 UTC

This package is auto-updated.

Last update: 2023-10-02 09:41:05 UTC


README

License Latest Version Total Downloads php 8.1+

⚠️ 停产通知 ⚠️

警告
由于有专门的 Symfony 扩展包位于 fusonic/http-kernel-bundle,因此已停用 fusonic/http-kernel-extensions 包(请参阅 fusonic/php-http-kernel-bundle)。

此包仅与及包含 Symfony 6.3 兼容,且不会接收任何其他功能更新。请考虑切换到新包以利用未来的更改。

请参阅 UPGRADE-1.0.md 以获取有关如何升级到新包的说明。

关于

此库包含了对 Symfony HttpKernel 组件 的各种扩展。以下将详细介绍此库提供的每个扩展及其工作方式。

目前主要开发在 Gitlab.com 的私有仓库进行。Github.com 上的项目会定期更新,但不包括 Gitlab 管理的问题。然而,我们也很高兴在 Github 上接受问题和拉取请求!请随意打开问题或合并请求。如果未来我们看到更广泛的社区参与,我们可能会考虑将主要开发转移到 Github。

安装

使用 composer 从 Packagist 安装库。

composer require fusonic/http-kernel-extensions

使用

RequestDtoResolver

在 Symfony 中存在一种称为 参数解析器 的东西。它们可以用于在动作被调用之前设置控制器动作参数的值。例如,存在 RequestValueResolver,它将当前请求作为参数注入到被调用的动作中。与此类似,我们创建了我们的参数解析器,但它比仅仅注入对象做了更多的事情。

它做什么?

我们的 RequestDtoResolver 可以用于将请求数据直接映射到对象。您无需手动从请求中获取所有信息并将其放入对象或 - 天哪 - 传递通用数据数组,此类将利用 Symfony Serializer 将请求映射到对象,并使您能够拥有自定义对象来传输请求数据(即数据传输对象)从控制器到您的业务逻辑。此外,如果您设置了验证注释,它还将使用 Symfony Validation 验证生成的对象。

  • 将对带有 Fusonic\HttpKernelExtensions\Attribute\FromRequest 属性 的参数执行映射。或者,此属性也可以设置在参数的类上(请参阅下面的示例)。
  • 在序列化过程中将强制执行 PUT、POST、PATCH 和 DELETE 的强类型检查,并且如果请求体中的类型与 DTO 中期望的类型不匹配,则将导致错误。
  • 对于所有其他请求,例如 GET 将作为查询参数始终以字符串形式传输,将禁用类型强制执行。
  • 请求体将与PUT、POST、PATCH和DELETE请求的路由参数合并(在这种情况下将忽略查询参数)。
  • 对于所有其他请求,查询参数将与路由参数合并(在这种情况下将忽略请求体)。
  • 路由参数将始终覆盖具有相同名称的查询参数或请求体值。
  • 将请求反序列化为对象之后,将进行验证。
  • 当以下情况发生时,将抛出BadRequestHttpException异常:
    • 生成的DTO对象根据Symfony验证无效
    • 请求体无法反序列化
    • 请求包含无效的JSON
    • 请求包含有效的JSON,但层次级别超过512
  • 如果您正在使用ConstraintViolationErrorHandler错误处理器,当您的对象验证失败时,将抛出ConstraintViolationException异常。您还可以通过实现ErrorHandlerInterface来自定义自己的处理器。
  • 根据给定的内容类型,它将解析请求体作为普通表单,或者如果设置了相应的内容类型,则将内容解析为JSON。

如何使用?

假设您正在使用完整的Symfony设置,您必须在您的services.yaml中将解析器注册为服务,如下所示,以便由Symfony调用。

    Fusonic\HttpKernelExtensions\Controller\RequestDtoResolver:
        tags:
            - { name: controller.argument_value_resolver, priority: 50 }

创建您的DTO,例如我们这里的UpdateFooDto。所有验证内容都是可选的,但序列化器需要getters和setters。

// ...

final class UpdateFooDto
{
    #[Assert\NotNull]
    #[Assert\Positive]
    private int $id;

    #[Assert\NotBlank]
    private string $clientVersion;

    #[Assert\NotNull]
    private array $browserInfo;

    public function getClientVersion(): string
    {
        return $this->clientVersion;
    }

    public function setClientVersion(string $clientVersion): void
    {
        $this->clientVersion = $clientVersion;
    }

    public function getBrowserInfo(): array
    {
        return $this->browserInfo;
    }

    public function setBrowserInfo(array $browserInfo): void
    {
        $this->browserInfo = $browserInfo;
    }

    public function getId(): int
    {
        return $this->id;
    }

    public function setId(int $id): void
    {
        $this->id = $id;
    }
}

参数属性

最后,将DTO添加到您的控制器操作中,使用RequestDtoArgument。当然,路由参数也是可选的。

// ...
use Fusonic\HttpKernelExtensions\Attribute\FromRequest;

final class FooController extends AbstractController
{
    /**
     * @Route("/{id}/update", methods={"POST"}, requirements={"id"="\d+"})
     */
    public function updateAction(#[FromRequest] UpdateFooDto $dto): Response
    {
        // do something with your $dto here
    }
}

类属性

或者,您也可以将属性添加到DTO类本身,而不是控制器操作中的参数,如果您喜欢这种方式。

// ...
use Fusonic\HttpKernelExtensions\Attribute\FromRequest;

#[FromRequest]
final class UpdateFooDto
{
// ...
}
// ...

final class FooController extends AbstractController
{
    /**
     * @Route("/{id}/update", methods={"POST"}, requirements={"id"="\d+"})
     */
    public function updateAction(UpdateFooDto $dto): Response
    {
        // do something with your $dto here
    }
}

解析和收集模型数据

默认情况下,任何jsonform请求体类型将被相应解析。要覆盖此行为,您可以向Fusonic\HttpKernelExtensions\Request\RequestDataCollectorInterface的实施数据收集器中注入自己的请求体解析器(通过实现Fusonic\HttpKernelExtensions\Request\BodyParser\RequestBodyParserInterface),该数据收集器被注入到Fusonic\HttpKernelExtensions\Controller\RequestDtoResolver中。在RequestDataCollectorInterface中,您还可以修改如何以及从Request对象中获取哪些值的行为。

错误处理

该扩展提供默认的错误处理器(http-kernel-extensions/src/ErrorHandler/ConstraintViolationErrorHandler.php),该处理器处理常见的去规范化错误,这些错误应被视为类型错误。它将创建一个ConstraintViolationException异常,该异常可以与提供的ConstraintViolationExceptionNormalizer一起使用。此标准化器用于Symfony的内置Symfony\Component\Serializer\Normalizer\ConstraintViolationListNormalizer,并通过一些额外信息增强它:一个errorCode和一个messageTemplate。这两个都很有用,可以用于在客户端解析验证错误。如果这不满足您的需求,您可以简单地通过实现Fusonic\HttpKernelExtensions\ErrorHandler\ErrorHandlerInterface并传递它到RequestDtoResolver来提供自己的错误处理器。

您必须像这样注册标准化器作为服务:

  Fusonic\HttpKernelExtensions\Normalizer\ConstraintViolationExceptionNormalizer:
        arguments:
            - "@serializer.normalizer.constraint_violation_list"
        tags:
          - { name: serializer.normalizer }
使用异常监听器/订阅者

在Symfony中,您可以使用异常监听器或订阅者将ConstraintViolationException最终转换为实际响应,使用Fusonic\HttpKernelExtensions\Normalizer\ConstraintViolationExceptionNormalizer。例如:

use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Fusonic\HttpKernelExtensions\Exception\ConstraintViolationException;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;

final class ExceptionSubscriber implements EventSubscriberInterface {

    public function __construct(private NormalizerInterface $normalizer) 
    {
    }

    public static function getSubscribedEvents(): array
    {
        return [
            KernelEvents::EXCEPTION => 'onKernelException',
        ];
    }

    public function onKernelException(ExceptionEvent $event): void
    {
        $throwable = $event->getThrowable();
        
        if ($throwable instanceof ConstraintViolationException) {
            $data = $this->normalizer->normalize($throwable);
            $event->setResponse(new JsonResponse($data, 422));
        }
    }
}

请查看事件和事件监听器的详细信息。

上下文感知提供者

有些情况下,您希望在DTO中添加数据,但不是通过API的消费者,而是根据当前登录的用户,例如。您可以在控制器收到DTO后手动执行此操作,获取用户,为DTO设置用户,然后继续处理。由于您在DTO创建后设置它,您无法进行验证,还必须将其设置为可空。之后,您可能还需要在业务逻辑中进行一些额外的检查,以确保已设置所需的所有内容。

或者,您只需创建并注册一个提供者,实现(并测试)一次,然后完成。所有提供者都将由RequestDtoResolver调用,检索所需数据以支持DTO,将其设置在DTO中,然后进行验证。当您在控制器中收到它时,它是完整且经过验证的。您如何做到这一点?

  1. 创建一个提供者并实现ContextAwareProvideInterface的两个方法。
<?php

// ...

final class UserIdAwareProvider implements ContextAwareProviderInterface
{
    public function __construct(private UserProviderInterface $userProvider)
    {
    }

    public function supports(object $dto): bool
    {
        return $dto instanceof UserIdAwareInterface;
    }

    public function provide(object $dto): void
    {
        if(!($dto instanceof UserIdAwareInterface)){
            throw new \LogicException('Object is no instance of '.UserIdAwareInterface::class);
        }

        $user = $this->userProvider->getUser();
        $dto->withUserId($user->getId());
    }
}
  1. 创建一个接口以标记您支持的类并设置数据。
<?php

//... 

interface UserIdAwareInterface
{
    public function withUserId(int $id): void;
}
  1. 在DTO中实现该接口。
  2. 最后,将提供者传递给解析器。如果您使用Symfony,您将在services.yaml中执行此操作,其外观类似于此。
#...
    _instanceof:
        Fusonic\HttpKernelExtensions\Provider\ContextAwareProviderInterface:
            tags:
                - {name: fusonic.http_kernel_extensions.context_aware_provider}

    Fusonic\HttpKernelExtensions\Controller\RequestDtoResolver:
        tags:
            - { name: controller.argument_value_resolver, priority: 50 }
        arguments:
            - '@serializer'
            - '@validator'
            - null
            - !tagged_iterator fusonic.http_kernel_extensions.context_aware_provider