jrm/request-bundle

使您轻松地将自己的请求作为控制器参数获取

安装次数: 2,121

依赖项: 0

建议者: 0

安全: 0

星标: 2

关注者: 1

分支: 0

开放问题: 0

类型:symfony-bundle

0.3.2 2024-08-18 16:29 UTC

This package is not auto-updated.

Last update: 2024-09-29 16:58:38 UTC


README

这是从 symfony 请求中提取数据的实现,用于特定控制器/操作的窄请求。

安装

  1. 在您的应用程序中需要此包
composer require jrm/request-bundle
  1. 在您的应用程序中启用此包
return [
    # ...
    Jrm\RequestBundle\JrmRequestBundle::class => ['all' => true],
    # ...
];

用法

使用某些数据源创建请求

  • Body(从请求体或表单中获取数据)
  • Collection(用于填充某些子对象的集合)
  • Cookie(从cookie中获取数据)
  • EmbeddableRequest(用于填充某些子对象)
  • File(从文件中获取数据)
  • Header(从头部中获取数据)
  • PathAttribute(从路径属性中获取数据)
  • Query(从查询字符串中获取数据)

请求

use Jrm\RequestBundle\Model\Source;
use Jrm\RequestBundle\Attribute\Collection;
use Jrm\RequestBundle\Attribute\Header;
use Jrm\RequestBundle\Attribute\PathAttribute;

final class MyRequest
{
    public function __construct(
        #[PathAttribute()]
        public readonly int $id,
        #[Body('pos_id')]
        private readonly string $posId,
        #[Header('Content-Type')]
        public readonly string $contentType,
        #[Assert\Valid]
        #[Collection(
            type: ProductItem::class,
            source: Source::BODY,
            path: 'products',
        )]
        public readonly array $products,
    ) {
    }
}

控制器

此示例使用可调用的控制器,但您也可以与常规控制器一起使用。

use Jrm\RequestBundle\MapRequest;

#[Route(
    '/do-something/{id}',
    name: 'app.do.something',
    methods: [Request::METHOD_POST],
)]
final class MyAction
{
    public function __invoke(#[MapRequest] MyRequest $request): JsonResponse
    {
        //do something

        return new JsonResponse(null);
    }
}

嵌套字段

您的数据,例如请求体,可能存在某些嵌套。

{
  "request": {
    "some_field": "some_value",
    "next_field": "next_value"
  }
}

您可以传递此字段的路径。

use Jrm\RequestBundle\Attribute\Body;

final class MyRequest
{
    public function __construct(
        #[Body('request.some_field')]
        public readonly string $someField,
        #[Body('request.next_field')]
        public readonly string $nextField,
    ) {
    }
}

验证

您可以使用 symfony 约束来验证您的请求,如果验证失败,Jrm\RequestBundle\Listener\RequestValidationFailedExceptionListener 将发送包含所有失败字段和错误信息的响应

use Jrm\RequestBundle\Attribute\Query;
use Symfony\Component\Validator\Constraints as Assert;

final class MyRequest
{
    public function __construct(
        #[Assert\NotBlank]
        #[Query('some_field')]
        public readonly string $field,
    ) {
    }
}
{
  "message": "Validation failed.",
  "errors": [
    {
      "code": "48b70abd-a021-4ce7-9662-616cd58eeaee",
      "message": "This value should not be blank.",
      "parameters": [],
      "property_path": "some_field"
    }
  ]
}

集合

在某些情况下,您可能需要填充数据的集合,您可以使用 Collection 属性并“描述”此集合项作为单独的对象。

use Jrm\RequestBundle\Attribute\Internal\Item;
use Symfony\Component\Validator\Constraints as Assert;

final class MyCollectionItem
{
    public function __construct(
        #[Assert\Uuid]
        #[Item()]
        public readonly string $id,
    ) {
    }
}
use Jrm\RequestBundle\Model\Source;
use Jrm\RequestBundle\Attribute\Collection;
use Symfony\Component\Validator\Constraints as Assert;

final class MyRequest
{
    public function __construct(
        #[Assert\Valid]
        #[Collection(
            type: MyCollectionItem::class,
            source: Source::BODY,
            path: 'sub_ids',
        )]
        public readonly array $items,
    ) {
    }
}

注意: 您应该使用 #[Assert\Valid] 对您的集合进行验证,如上例所示,因为如果没有此约束,symfony 验证器将忽略它

自定义解析器

您可以创建自定义解析器来定义获取请求数据的新方式
为此,您需要创建

参数

use Jrm\RequestBundle\Attribute\RequestAttribute;

#[Attribute(Attribute::TARGET_PARAMETER, Attribute::TARGET_PROPERTY)]
final class UserId implements RequestAttribute
{
    /**
     * @return class-string<UserIdResolver>
     */
    public function resolvedBy(): string
    {
        return UserIdResolver::class;
    }
}

参数解析器

use App\Domain\User\Exception\UserNotAuthorizedException;
use App\Domain\User\Model\User;
use Jrm\RequestBundle\Exception\UnexpectedAttributeException;
use Jrm\RequestBundle\Model\Metadata;
use Jrm\RequestBundle\Attribute\RequestAttribute;
use Jrm\RequestBundle\Attribute\ValueResolver;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

final class UserIdResolver implements ValueResolver
{
    public function __construct(
        private readonly TokenStorageInterface $tokenStorage,
    ) {
    }

    public function resolve(
        Request $request,
        Metadata $metadata,
        RequestAttribute $attribute,
    ): int {
        if (!$attribute instanceof UserId) {
            throw new UnexpectedAttributeException(UserId::class, $attribute::class);
        }

        try {
            $user = $this->tokenStorage->getToken()?->getUser();

            if ($user === null) {
                throw new UserNotAuthorizedException();
            }
    
            return $user->id();
        } catch (Throwable $throwable) {
            if ($parameter->isOptional()) {
                return $parameter->defaultValue();
            }

            throw $throwable;
        }
    }
}

计划

  • 实现自动化转换到 Open Api 文档
  • Item 属性设置为可选
  • 添加验证测试,确保所有请求都是具有支持属性和类型的有效类
  • 修复当您的请求没有任何必需参数时的验证问题
  • 将包添加到 symfony flex
  • 添加更多单元和集成测试