今天,Symfony是市场上最成熟和最强大的框架之一,因此,它用于徒劳的项目,包括创建API。最近,Symfony包括法律功能,例如mapear os dados da Request para um Objeto的功能,该功能在6.3中出现。
这样,我们将利用PHP的最后一个版本的一些最佳功能,这是对attributes和atributos readonly的支持,并为Symfony Repands创建验证。
为此,我们将使用Symfony Validation组件。
我没有病人,告诉我路!
好吧,好吧!如果您没有耐心地阅读本文,我将在下面的链接上实施本文的测试项目。
https://github.com/joubertredrat/symfony-request-validator
基本示例
在文档的重现之后,只需创建一个我们将用来映射请求值的类,如下示例。
<?php declare(strict_types=1);
namespace App\Dto;
use App\Validator\CreditCard;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Positive;
use Symfony\Component\Validator\Constraints\Range;
use Symfony\Component\Validator\Constraints\Type;
class CreateTransactionDto
{
public function __construct(
#[NotBlank(message: 'I dont like this field empty')]
#[Type('string')]
public readonly string $firstName,
#[NotBlank(message: 'I dont like this field empty')]
#[Type('string')]
public readonly string $lastName,
#[NotBlank()]
#[Type('string')]
#[CreditCard()]
public readonly string $cardNumber,
#[NotBlank()]
#[Positive()]
public readonly int $amount,
#[NotBlank()]
#[Type('int')]
#[Range(
min: 1,
max: 12,
notInRangeMessage: 'Expected to be between {{ min }} and {{ max }}, got {{ value }}',
)]
public readonly int $installments,
#[Type('string')]
public ?string $description = null,
) {
}
}
这样,只需使用注释#[MapRequestPayload]
并准备就绪的控制器,将值自动映射到对象。
。
。
<?php declare(strict_types=1);
namespace App\Controller;
use App\Dto\CreateTransactionDto;
use DateTimeImmutable;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
use Symfony\Component\Routing\Annotation\Route;
class TransactionController extends AbstractController
{
#[Route('/api/v1/transactions', name: 'app_api_create_transaction_v1', methods: ['POST'])]
public function v1Create(#[MapRequestPayload] CreateTransactionDto $createTransaction): JsonResponse
{
return $this->json([
'resonse' => 'ok',
'datetime' => (new DateTimeImmutable('now'))->format('Y-m-d H:i:s'),
'firstName' => $createTransaction->firstName,
'lastName' => $createTransaction->lastName,
'amount' => $createTransaction->amount,
'installments' => $createTransaction->installments,
'description' => $createTransaction->description,
]);
}
}
这样,只需提出请求并查看结果即可。
curl --request POST \
--url http://127.0.0.1:8001/api/v1/transactions \
--header 'Content-Type: application/json' \
--data '{
"firstName": "Joubert",
"lastName": "RedRat",
"cardNumber": "4130731304267489",
"amount": 35011757,
"installments": 2
}'
< HTTP/1.1 200 OK
< Content-Type: application/json
{
"resonse": "ok",
"datetime": "2023-07-04 19:36:37",
"firstName": "Joubert",
"lastName": "RedRat",
"cardNumber": "4130731304267489",
"amount": 35011757,
"installments": 2,
"description": null
}
在上面的示例中,如果未根据定义规则正确填充值,则将触发一个例外,我们收到一个带有发现错误的答案。
部分这是ValidationFailedException
标准的例外,当我们构建API时,需要一个json形状的响应。
考虑到这一点,我们可以尝试一种将要解释的方法。
抽象请求类
Symfony的巨大优势之一是通过其强大的注射依赖性容器在Autowire Support的情况下对“ Dependency inversion principle”的巨大支持。
这样,我们将创建我们的摘要类,该类将包含负责执行paraspit和验证的整个时间,如下示例。
<?php declare(strict_types=1);
namespace App\Request;
use Fig\Http\Message\StatusCodeInterface;
use Jawira\CaseConverter\Convert;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Validator\Validator\ValidatorInterface;
abstract class AbstractJsonRequest
{
public function __construct(
protected ValidatorInterface $validator,
protected RequestStack $requestStack,
) {
$this->populate();
$this->validate();
}
public function getRequest(): Request
{
return $this->requestStack->getCurrentRequest();
}
protected function populate(): void
{
$request = $this->getRequest();
$reflection = new \ReflectionClass($this);
foreach ($request->toArray() as $property => $value) {
$attribute = self::camelCase($property);
if (property_exists($this, $attribute)) {
$reflectionProperty = $reflection->getProperty($attribute);
$reflectionProperty->setValue($this, $value);
}
}
}
protected function validate(): void
{
$errors = $this->validator->validate($this);
$messages = [];
foreach ($errors as $message) {
$messages[] = [
'property' => self::snakeCase($message->getPropertyPath()),
'value' => $message->getInvalidValue(),
'message' => $message->getMessage(),
];
}
if (count($messages) > 0) {
$response = new JsonResponse(['errors' => $messages], StatusCodeInterface::STATUS_BAD_REQUEST);
$response->send();
exit;
}
}
private static function camelCase(string $attribute): string
{
return (new Convert($attribute))->toCamel();
}
private static function snakeCase(string $attribute): string
{
return (new Convert($attribute))->toSnake();
}
}
在上面的类中,我们可以看到它接收ValidatorInterface
和koud3作为依赖性,并且对构建器进行了属性的完成和验证。
©m,可以在属性和错误中看到snake_case
和camelCase
模式之间的对话,这是因为有一个约定,json的领域必须是snake_case
,而PSR-2和PSR-12建议camelCase
5用于类属性名称,因此进行了此对话。为此,使用了Abiaoqian Library13。
por©m,值得记住的是,这不是绝对规则,如果您想使用与JSON中的snake_case
不同的模式,则可以。
请求类带有验证属性的类
通过负责所有验证的摘要类,现在我们将创建验证类,如下示例。
<?php declare(strict_types=1);
namespace App\Request;
use App\Validator\CreditCard;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Positive;
use Symfony\Component\Validator\Constraints\Range;
use Symfony\Component\Validator\Constraints\Type;
class CreateTransactionRequest extends AbstractJsonRequest
{
#[NotBlank(message: 'I dont like this field empty')]
#[Type('string')]
public readonly string $firstName;
#[NotBlank(message: 'I dont like this field empty')]
#[Type('string')]
public readonly string $lastName;
#[NotBlank()]
#[Type('string')]
#[CreditCard()]
public readonly string $cardNumber;
#[NotBlank()]
#[Positive()]
public readonly int $amount;
#[NotBlank()]
#[Type('int')]
#[Range(
min: 1,
max: 12,
notInRangeMessage: 'Expected to be between {{ min }} and {{ max }}, got {{ value }}',
)]
public readonly int $installments;
#[Type('string')]
public ?string $description = null;
}
上一类的最大优势在于,请求o的所有义务属性都是状态readonly
,并且有可能确保数据的不变性。另一个有趣的观点是能够使用Symfony验证属性进行必要的验证,甚至创建自定义验证。
在路线上使用请求类
准备好课程,现在它将其用作您要进行验证的路由的依赖,值得记住的是,与上一个示例不同,这里不需要注释#[MapRequestPayload]
,例如下面的示例。
<?php declare(strict_types=1);
namespace App\Controller;
use App\Request\CreateTransactionRequest;
use DateTimeImmutable;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
class TransactionController extends AbstractController
{
#[Route('/api/v2/transactions', name: 'app_api_create_transaction_v2', methods: ['POST'])]
public function v2Create(CreateTransactionRequest $request): JsonResponse
{
return $this->json([
'resonse' => 'ok',
'datetime' => (new DateTimeImmutable('now'))->format('Y-m-d H:i:s'),
'first_name' => $request->firstName,
'last_name' => $request->lastName,
'amount' => $request->amount,
'installments' => $request->installments,
'description' => $request->description,
'headers' => [
'Content-Type' => $request
->getRequest()
->headers
->get('Content-Type')
,
],
]);
}
}
在上面的控制器中,有可能看到我们不使用传统的httppountation11,是的,我们的CreateTransactionRequest
类是依赖的,并且有母亲,因为所有必要的依赖都将被注入,并将执行验证。<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /p>
这种方法的优势
关于基本示例,此方法具有两个很大的优势。
-
- 可以根据需要自定义JSON的结构和状态响应代码。
-
- 可以访问已注入依赖的Smyfony请求类,因此可以从请求中访问任何信息,例如标题。在基本示例中,这是不可能的,除非您还将请求类放在路由上,这很奇怪,因为它将具有两个不同的数据源。 /p> < /li>
测试时间!
准备好实施后,让我们去测试。
示例的请求是有目的的错误,我们可以看到验证的答案。
curl --request POST \
--url http://127.0.0.1:8001/api/v2/transactions \
--header 'Content-Type: application/json' \
--data '{
"last_name": "RedRat",
"card_number": "1130731304267489",
"amount": -4,
"installments": 16
}'
< HTTP/1.1 400 Bad Request
< Content-Type: application/json
{
"errors": [
{
"property": "first_name",
"value": null,
"message": "I dont like this field empty."
},
{
"property": "card_number",
"value": "1130731304267489",
"message": "Expected valid credit card number."
},
{
"property": "amount",
"value": -4,
"message": "This value should be positive."
},
{
"property": "installments",
"value": 16,
"message": "Expected to be between 1 and 12, got 16"
}
]
}
正如我们所看到的,验证成功发生并且字段没有填充,或者没有正确的值没有通过验证,我们对验证的响应。P>>
现在,我们将进行一次调查,并确保回应将取得成功,因为所有字段都将在我们想要的内部。
curl --request POST \
--url http://127.0.0.1:8001/api/v2/transactions \
--header 'Content-Type: application/json' \
--data '{
"first_name": "Joubert",
"last_name": "RedRat",
"card_number": "4130731304267489",
"amount": 35011757,
"installments": 2
}'
< HTTP/1.1 200 OK
< Content-Type: application/json
{
"resonse": "ok",
"datetime": "2023-07-01 16:39:48",
"first_name": "Joubert",
"last_name": "RedRat",
"card_number": "4130731304267489",
"amount": 35011757,
"installments": 2,
"description": null
}
lima㧵
可选字段不能是readonly
,就好像您想在没有该信息的情况下访问该信息一样,php将摄入异常。我现在正在使用这些情况的普通属性。
我仍在研究某些选项,以便能够在可选字段中使用readonly
,例如使用反射和接受的建议:)
最后,我感谢好朋友Vinícius Dias的感谢,他帮助我回顾了本文。
这也是如此,下一个!