目前我正在使用 Symfony 7.1 开发 API 我决定使用 DTO 对象来处理和验证我的请求的参数。
为了检测并报告请求中是否不存在强制参数,我创建了一个自定义解析器。
POST 请求
curl --location 'http://dev.myproject/api/v1/authentication/user' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Authorization: ••••••' \
--data-raw '{
"username" : "username",
"password": "myPassword"
}'
控制器
#[Route('/user', name: 'auth_user', methods: ['POST'], format: 'json')]
public function authUser(
#[MapRequestPayload(
resolver: ApiAuthUserResolver::class
)] ApiAuthUserDto $apiAuthUserDto,
): JsonResponse
{
[...]
}
DTO 对象
namespace App\Dto\Api\Authentication;
use Symfony\Component\Validator\Constraints as Assert;
readonly class ApiAuthUserDto
{
public function __construct(
#[Assert\Type(
type : 'string',
message: 'Le champ username doit être du type string'
)]
#[Assert\NotBlank(message: 'Le champ username ne peut pas être vide')]
public string $username,
#[Assert\Type(
type : 'string',
message: 'Le champ password doit être du type string'
)]
#[Assert\NotBlank(message: 'Le champ password ne peut pas être vide')]
public string $password,
)
{
}
}
自定义解析器
namespace App\Resolver\Api;
use App\Dto\Api\Authentication\ApiAuthUserDto;
use App\Utils\Api\ApiParametersParser;
use App\Utils\Api\ApiParametersRef;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
use Symfony\Component\Validator\Validator\ValidatorInterface;
readonly class ApiAuthUserResolver implements ValueResolverInterface
{
public function __construct(
private ApiParametersParser $apiParametersParser,
private ValidatorInterface $validator
) {
}
/**
* @param Request $request
* @param ArgumentMetadata $argument
* @return iterable
*/
public function resolve(Request $request, ArgumentMetadata $argument): iterable
{
$data = json_decode($request->getContent(), true);
$return = $this->apiParametersParser->parse(ApiParametersRef::PARAMS_REF_AUTH_USER, $data);
if (!empty($return)) {
throw new HttpException(Response::HTTP_FORBIDDEN,implode(',', $return));
}
$dto = new ApiAuthUserDto(
$data['username'],
$data['password']
);
$errors = $this->validator->validate($dto);
if (count($errors) > 0) {
$nb = $errors->count();
$msg = [];
for ($i = 0; $i < $nb; $i++) {
$msg[] = $errors->get($i)->getMessage() . ' ';
}
throw new HttpException(Response::HTTP_FORBIDDEN,implode(',', $msg));
}
return [$dto];
}
}
我设置了优先级,以免与其他解析器出现问题
App\Resolver\Api\ApiAuthUserResolver:
tags:
- controller.argument_value_resolver:
priority: 50
代码运行良好并正确完成其工作。
我的问题是这样的:
自从我为我的 API 实现了这个自定义解析器以来,我的应用程序中的所有路由都被破坏了,因为我的自定义解析器被系统地调用,原因我不知道。
例如这段代码,我的项目中的一个非常简单的路线,它调用 2 个对象
#[Route('/dashboard/index', name: 'index')]
#[Route('/dashboard', 'index_3')]
#[Route('/', name: 'index_2')]
public function index(DashboardTranslate $dashboardTranslate, UserDataService $userDataService): Response
{[...]}
现在给我以下错误:
App\Utils\Api\ApiParametersParser::parse():参数 #2 ($apiParameters) 必须是数组类型,给定 null,在第 36 行的 \src\ValueResolver\Api\ApiAuthUserResolver.php 中调用
我不明白为什么它是系统地调用我的自定义解析器,即使它具有较低的优先级,并且我只是通过 MapRequestPayload 的解析器属性为操作定义它
我想做的是,这个自定义解析器仅在这种特定情况下使用,对于经典案例,Symfony 解析器的工作方式与之前的情况一样
我是否忘记了什么,错误配置了我的自定义解析器? 谢谢你的帮助
我想这与标签有关
- controller.argument_value_resolver:
priority: 50
您添加的。这基本上告诉 symfony - 如果您在路由中遇到参数,请尝试使用最高优先级的参数值解析器来解析它,支持它。
最后一部分是重要的。您需要一种方法来告诉框架,除了
AuthUser
之外,它不能将您的 ValueResolver 用于其他任何用途。正如文档中所声明的,您可以通过多种方式实现这一点。
如果这确实是问题所在,我建议您先尝试一下。您可以在
ApiAuthUserResolver::resolve
中指定初始条件。
这种情况看起来像这样:
public function resolve(Request $request, ArgumentMetadata $argument): iterable
{
$argumentType = $argument->getType();
if (
!$argumentType
|| !is_subclass_of($argumentType, ApiAuthUserDto::class, true)
) {
return [];
}
// Rest of the function body as specified in question
}
返回空数组告诉 symfony 使用不同的值解析器,优先级较低。希望这有帮助。