API Platform 负责验证客户端发送到 API 的数据(通常是通过表单输入的用户数据)。默认情况下,该框架依赖于强大的 Symfony 验证器组件执行此任务,但您可以根据需要将其替换为您喜欢的验证库,例如 PHP 过滤器扩展 。
验证提交的数据
验证提交的数据就像添加 Symfony 的内置约束一样简单 或直接在标有 #[ApiResource]
属性的类中自定义约束 :
<?php
// api/src/Entity/Product.php
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use App\Validator\Constraints\MinimalProperties; // A custom constraint
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert; // Symfony's built-in constraints
/**
* A product.
*
*/
#[ORM\Entity]
#[ApiResource]
class Product
{
#[ORM\Id, ORM\Column, ORM\GeneratedValue]
private ?int $id = null;
#[ORM\Column]
#[Assert\NotBlank]
public string $name;
/**
* @var string[] Describe the product
*/
#[MinimalProperties]
#[ORM\Column(type: 'json')]
public $properties;
// Getters and setters...
}
这是自定义 MinimalProperties
约束和相关的验证器:
<?php
// api/src/Validator/Constraints/MinimalProperties.php
namespace App\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
#[\Attribute]
class MinimalProperties extends Constraint
{
public $message = 'The product must have the minimal properties required ("description", "price")';
}
<?php
// api/src/Validator/Constraints/MinimalPropertiesValidator.php
namespace App\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
final class MinimalPropertiesValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint): void
{
if (array_diff(['description', 'price'], $value)) {
$this->context->buildViolation($constraint->message)->addViolation();
}
}
}
如果客户端提交的数据无效,则 HTTP 状态代码将设置为 422 Unprocessable Entity
,响应正文将包含以符合请求格式的格式序列化的违规列表。例如,如果请求的格式为 JSON-LD(默认值),则验证错误将如下所示:
{
"@context": "/contexts/ConstraintViolationList",
"@type": "ConstraintViolationList",
"title": "An error occurred",
"description": "properties: The product must have the minimal properties required (\"description\", \"price\")",
"violations": [
{
"propertyPath": "properties",
"message": "The product must have the minimal properties required (\"description\", \"price\")"
}
]
}
请查看错误处理指南 ,了解 API Platform 如何将验证错误等 PHP 异常转换为 HTTP 错误。
使用验证组
如果没有特定的配置,总是使用默认的验证组,但这种行为是可定制的:框架能够利用 Symfony 的验证组 。
您可以直接通过 ApiResource
属性配置验证时要使用的组:
<?php
// api/src/Entity/Book.php
use ApiPlatform\Metadata\ApiResource;
use Symfony\Component\Validator\Constraints as Assert;
#[ApiResource(validationContext: ['groups' => ['a', 'b']])]
class Book
{
#[Assert\NotBlank(groups: ['a'])]
public string $name;
#[Assert\NotNull(groups: ['b'])]
public string $author;
// ...
}
在前面的配置中,执行验证时将使用验证组 a
和 b
。
与序列化组一样,可以全局指定验证组,也可以按作指定验证组。
当然,如果您愿意,您可以使用 XML 或 YAML 配置格式而不是属性。
您也可以传入组序列来代替组名称数组。
在作中使用验证组
可以对与资源相关的每个作进行不同的验证。
<?php
// api/src/Entity/Book.php
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\Put;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\ApiResource;
use Symfony\Component\Validator\Constraints as Assert;
#[ApiResource]
#[Delete]
#[Get]
#[Put(validationContext: ['groups' => ['Default', 'putValidation']])]
#[GetCollection]
#[Post(validationContext: ['groups' => ['Default', 'postValidation']])]
class Book
{
#[Assert\Uuid]
private $id;
#[Assert\NotBlank(groups: ['postValidation'])]
public $name;
#[Assert\NotNull]
#[Assert\Length(min: 2, max: 50, groups: ['postValidation'])]
#[Assert\Length(min: 2, max: 70, groups: ['putValidation'])]
public $author;
// ...
}
使用此配置,有三个验证组:
默认包含
不属于其他组的约束。
postValidation
仅包含对 name 和 author(长度从 2 到 50)字段的约束。
putValidation
仅包含对作者(长度从 2 到 70)字段的约束。
动态验证组
如果您需要动态确定在不同场景中对实体使用哪些验证组,只需传入可调用。回调将接收实体对象作为其第一个参数,并应返回组名称数组或组序列。
在以下示例中,我们使用静态方法返回验证组:
<?php
// api/src/Entity/Book.php
use ApiPlatform\Metadata\ApiResource;
use Symfony\Component\Validator\Constraints as Assert;
#[ApiResource(
validationContext: ['groups' => [Book::class, 'validationGroups']]
)]
class Book
{
/**
* Return dynamic validation groups.
*
* @param self $book Contains the instance of Book to validate.
*
* @return string[]
*/
public static function validationGroups(self $book)
{
return ['a'];
}
#[Assert\NotBlank(groups: ['a'])]
public $name;
#[Assert\NotNull(groups: ['b'])]
public $author;
// ...
}
或者,您可以使用服务检索要使用的组:
<?php
// api/src/Validator/AdminGroupsGenerator.php
namespace App\Validator;
use ApiPlatform\Symfony\Validator\ValidationGroupsGeneratorInterface;
use App\Entity\Book;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
final class AdminGroupsGenerator implements ValidationGroupsGeneratorInterface
{
private $authorizationChecker;
public function __construct(AuthorizationCheckerInterface $authorizationChecker)
{
$this->authorizationChecker = $authorizationChecker;
}
public function __invoke($book): array
{
assert($book instanceof Book);
return $this->authorizationChecker->isGranted('ROLE_ADMIN', $book) ? ['a', 'b'] : ['a'];
}
}
此类根据当前用户的角色选择要应用的组:如果当前用户具有 ROLE_ADMIN
角色,则返回组 a
和 b
。在其他情况下,仅返回 a
。
由于 Symfony DependencyInjection 组件的自动连接功能 ,该类会自动注册为服务。
然后,将实体类配置为使用此服务检索验证组:
<?php
// api/src/Entity/Book.php
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use App\Validator\AdminGroupsGenerator;
use Symfony\Component\Validator\Constraints as Assert;
#[ApiResource(validationContext: ['groups' => AdminGroupsGenerator::class])
class Book
{
#[Assert\NotBlank(groups: ['a'])]
public $name;
#[Assert\NotNull(groups: ['b'])]
public $author;
// ...
}
顺序验证组
如果需要指定必须测试验证组的顺序,可以使用组序列。首先,您需要创建排序组。
<?php
namespace App\Validator;
use Symfony\Component\Validator\Constraints\GroupSequence;
class MySequencedGroup
{
public function __invoke()
{
return new GroupSequence(['first', 'second']); // now, no matter which is first in the class declaration, it will be tested in this order.
}
}
仅仅创建类是不够的,因为 Symfony 不认为该服务正在被使用。因此,若要防止删除服务,需要强制将其公开。
# api/config/services.yaml
services:
App\Validator\MySequencedGroup: ~
public: true
然后,您需要将您的类用作验证组。
<?php
// api/src/Entity/Greeting.php
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Post;
use App\Validator\One; // classic custom constraint
use App\Validator\Two; // classic custom constraint
use App\Validator\MySequencedGroup; // the sequence group to use
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ApiResource]
#[Post(validationContext: ['groups' => MySequencedGroup::class])]
class Greeting
{
#[ORM\Id, ORM\Column, ORM\GeneratedValue]
private ?int $id = null;
/**
* @var A nice person
*
* I want this "second" validation to be executed after the "first" one even though I wrote them in this order.
*/
#[One(groups: ['second'])]
#[Two(groups: ['first'])]
#[ORM\Column]
public string $name = '';
public function getId(): int
{
return $this->id;
}
}
验证删除作
缺省情况下,在 DELETE 作期间不会评估在 API 资源上指定的验证规则。如果需要,您需要在代码中触发验证。
假设您有以下使用自定义删除验证器的实体:
<?php
// api/src/Entity/MyEntity.php
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete;
use App\State\MyEntityRemoveProcessor;
use App\Validator\AssertCanDelete;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ApiResource(
operations: [
new Delete(validationContext: ['groups' => ['deleteValidation']], processor: MyEntityRemoveProcessor::class)
]
)]
#[AssertCanDelete(groups: ['deleteValidation'])]
class MyEntity
{
#[ORM\Id, ORM\Column, ORM\GeneratedValue]
private ?int $id = null;
#[ORM\Column]
public string $name = '';
}
创建一个处理器,该处理器接收默认处理器,您将在其中触发验证:
<?php
// api/src/State/MyEntityRemoveProcessor.php
namespace App\State;
use ApiPlatform\Doctrine\Common\State\RemoveProcessor as DoctrineRemoveProcessor;
use ApiPlatform\State\ProcessorInterface;
use ApiPlatform\Validator\ValidatorInterface;
use App\Entity\MyEntity;
final readonly class MyEntityRemoveProcessor implements ProcessorInterface
{
public function __construct(
private DoctrineRemoveProcessor $doctrineProcessor,
private ValidatorInterface $validator,
)
{
}
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = [])
{
$this->validator->validate($data, ['groups' => ['deleteValidation']]);
$this->doctrineProcessor->process($data, $operation, $uriVariables, $context);
}
}
错误级别和有效负载序列化
正如 Symfony 文档中所述,您可以使用 payload 字段来定义错误级别。您可以通过在 API Platform 配置中将 serialize_payload_fields
设置为空数组
来检索有效负载字段:
# api/config/packages/api_platform.yaml
api_platform:
validator:
serialize_payload_fields: ~
然后,序列化程序将在错误响应中返回所有有效负载值。
如果只想序列化某些有效负载字段,请在配置中定义它们,如下所示:
# api/config/packages/api_platform.yaml
api_platform:
validator:
serialize_payload_fields: [severity, anotherPayloadField]
在此示例中,将仅序列化 severity
和 anotherPayloadField
。
集合关系的验证
使用有效约束。
注意:这与集合关系非规范化有关。当尝试验证表示 Doctrine 的 ArrayCollection (toMany
) 的关系时,您可能会遇到问题。 使用属性 getter 修复非规范化。返回一个数组
而不是 ArrayCollection
,并使用 $collectionRelation->getValues()
。然后,在 getter 而不是属性上定义验证。
例如:
<getter property="cars">
<constraint name="Valid"/>
</getter>
<?php
// api/src/Entity/Brand.php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
final class Brand
{
// ...
public function __construct()
{
$this->cars = new ArrayCollection();
}
#[Assert\Valid]
public function getCars()
{
return $this->cars->getValues();
}
}
从验证元数据生成的开放词汇表
API Platform 会自动检测 Symfony 的内置验证器并相应地生成 schema.org IRI 元数据。这允许富客户端(如管理组件)推断大多数基本用例的字段类型。
涵盖以下验证约束:
约束 | 词汇 |
---|---|
Url | https://schema.org/url |
Email | https://schema.org/email |
Uuid | https://schema.org/identifier |
CardScheme | https://schema.org/identifier |
Bic | https://schema.org/identifier |
Iban | https://schema.org/identifier |
Date | https://schema.org/Date |
DateTime | https://schema.org/DateTime |
Time | https://schema.org/Time |
Image | https://schema.org/image |
File | https://schema.org/MediaObject |
Currency | https://schema.org/priceCurrency |
Isbn | https://schema.org/isbn |
Issn | https://schema.org/issn |
规范属性限制
API Platform 根据 Symfony 的内置验证器生成规范属性限制。
例如,从正则表达式
约束 API Platform 构建模式
限制。
要根据自定义验证约束构建自定义属性架构,您可以创建一个自定义类来生成属性方案限制。
要创建属性模式,您必须实现 PropertySchemaRestrictionMetadataInterface
.此接口仅定义 2 种方法:
create
:创建属性架构supports
:检查属性和约束是否受支持
下面是一个实现示例:
// api/src/PropertySchemaRestriction/CustomPropertySchemaRestriction.php
namespace App\PropertySchemaRestriction;
use ApiPlatform\Metadata\ApiProperty;
use Symfony\Component\Validator\Constraint;
use App\Validator\CustomConstraint;
final class CustomPropertySchemaRestriction implements PropertySchemaRestrictionMetadataInterface
{
public function supports(Constraint $constraint, ApiProperty $propertyMetadata): bool
{
return $constraint instanceof CustomConstraint;
}
public function create(Constraint $constraint, ApiProperty $propertyMetadata): array
{
// your logic to create property schema restriction based on constraint
return $restriction;
}
}
如果您使用自定义依赖注入配置,则需要注册相应的服务并将 api_platform.metadata.property_schema_restriction
标记。priority
属性可用于服务排序。
# api/config/services.yaml
services:
# ...
'App\PropertySchemaRestriction\CustomPropertySchemaRestriction':
~
# Uncomment only if autoconfiguration is disabled
#tags: [ 'api_platform.metadata.property_schema_restriction' ]
收集非规范化错误
提交数据时,可以使用 COLLECT_DENORMALIZATION_ERRORS 选项收集非规范化错误。
它可以直接在 #[ApiResource]
属性(或作中)中完成:
<?php
// api/src/Entity/Book.php
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
#[ApiResource(
collectDenormalizationErrors: true
)]
class Book
{
public ?bool $boolean;
public ?string $property1;
}
如果提交的数据存在非规范化错误,则 HTTP 状态代码将设置为 422 Unprocessable Content
,响应正文将包含错误列表:
{
"@context": "/api/contexts/ConstraintViolationList",
"@type": "ConstraintViolationList",
"title": "An error occurred",
"description": "boolean: This value should be of type bool.\nproperty1: This value should be of type string.",
"violations": [
{
"propertyPath": "boolean",
"message": "This value should be of type bool.",
"code": "0"
},
{
"propertyPath": "property1",
"message": "This value should be of type string.",
"code": "0"
}
]
}
您还可以在全局资源默认值中启用全局收集非规范化错误。