vanilla / garden-schema
基于 JSON Schema 的简单数据验证和清洗库。
Requires
- php: >=7.4
Requires (Dev)
- phpunit/phpunit: ^9.0
- dev-master
- v3.2.0
- v3.1.0
- v3.0.1
- v3.0
- v2.3
- v2.2
- v2.1
- v2.0
- v1.14
- v1.13
- v1.12.0
- v1.11.0
- v1.10.2
- v1.10.1
- v1.10
- v1.9
- v1.8
- v1.7.7
- v1.7.6
- v1.7.5
- v1.7.4
- v1.7.3
- v1.7.2
- v1.7.1
- v1.7
- v1.6.1
- v1.6
- v1.5.0
- v1.4.2
- v1.4.1
- v1.4.0
- v1.3.0
- v1.2.0
- v1.1.0
- v1.0.0
- v0.1.0
- dev-feature/undeprecate-multi-type-validation
- dev-release/1.x
- dev-feature/protected-fields
- dev-feature/php81-support
- dev-feature/php8.1-support
- dev-feature/github-actions
- dev-feature/php-8.0-vnla-2729
- dev-fix/validation-array-access
- dev-backport/1.x/maxByteLength-property
- dev-feature/maxByteLength-property
- dev-backport/1.x/73
- dev-feature/schema-attributes
- dev-feature/tweak-null-message
- dev-release/1.9
- dev-fix-validate-url-flags
- dev-feature/version-bump-go-over
- dev-backport/numeric-validators
- dev-feature/discriminator
- dev-feature/filters
- dev-feature/readme-updates
- dev-feature/rename-number-code
- dev-feature/validation-values
- dev-feature/validation-factory
- dev-fix/colon-properties
- dev-feature/slash-paths
- dev-feature/readme-additions
- dev-feature/open-api-compat
- dev-feature/deprecate-types
- dev-feature/fix-json-message
- dev-feature/concat-errors-empty
- dev-feature/concat-errors
This package is auto-updated.
Last update: 2024-09-16 21:54:10 UTC
README
花园模式(Garden Schema)是一个基于 OpenAPI 3.0 模式 的简单数据验证和清洗库。
特性
-
定义任意深度的 PHP 数组的数据结构,并进行验证。
-
验证后的数据将被清洗并转换为适当的类型。
-
模式定义了允许数据的白名单,并移除所有多余的数据。
-
Schema 类理解 OpenAPI 模式格式中的一部分数据。随着时间的推移,我们将添加更多内置 JSON 模式验证的支持。
-
开发者可以使用更短的模式格式快速定义模式。我们构建这个类以便尽可能易于使用。避免开发者在使用锁定数据时发出叹息。
-
添加自定义验证回调以支持几乎所有验证场景。
-
覆盖验证类以自定义应用程序中错误显示的方式。
用法
花园模式旨在成为数据验证的通用包装器。当您想要使代码针对用户提交的数据进行加固时,它将非常有价值。以下是一些示例用法:
-
检查提交到您的 API 端点的数据。在您的端点开头定义模式,并在执行任何其他操作之前验证数据。这样,您可以确保使用的是干净的数据,并在代码的后期避免大量乱麻式的检查。这正是我们开发花园模式的原因。
-
清洗用户输入。Schema 对象将数据转换为适当的类型,并优雅地处理常见的用例(例如,将字符串 "true" 转换为布尔值)。这允许您在代码中使用更多的 "===" 检查,这有助于在长期内避免错误。
-
在传递到数据库之前验证数据,以呈现易于理解的错误,而不是神秘的数据库生成的错误。
-
在返回之前清洗输出。许多数据库驱动程序将数据作为字符串返回,即使它被定义为不同的类型。Schema 将适当清洗数据,这对于非 PHP 世界的消费尤其重要。
基本用法
要验证数据,您首先创建 Schema 类的一个实例,然后调用其 validate() 方法。
namespace Garden\Schema; $schema = Schema::parse([...]); try { $valid = $schema->validate($data); } catch (ValidationException $ex) { ... }
在上面的示例中,使用传递给其构造函数的模式定义创建了一个 Schema 对象(更多关于这一点稍后介绍)。然后可以将要验证的数据传递给 validate() 方法。如果数据没有问题,则返回一个干净的版本,否则抛出 ValidationException。
定义模式
Schema 类通过一个定义模式的数组进行实例化。该数组可以是 OpenAPI 3.0 模式格式,也可以是自定义短格式。建议您使用 OpenAPI 格式定义您的模式,但对于想要快速编写原型的人来说,短格式是很好的。本节将描述短格式。
默认情况下,模式是一个数组,其中数组的每个元素定义一个对象属性。当我们说 "对象" 时,我们指的是 JavaScript 对象或具有字符串键的 PHP 数组。属性可以以几种方式定义
[ '<property>', // basic property, can be any type '<property>?', // optional property '<property>:<type>?', // optional property with specific type '<property>:<type>?' => 'Description', // optional, typed property with description '<property>?' => ['type' => '<type'>, 'description' => '...'], // longer format '<property>:o' => [ // object property with nested schema '<property>:<type>' => '...', ... ], '<property>:a' => '<type>', // array property with element type '<property>:a' => [ // array property with object element type '<property>:<type>' => '...', ... ] ]
您可以通过提供所需的信息来快速定义对象模式。您可以根据需要创建深度嵌套的方案,以验证非常复杂的数据。这个简短的方案在内部转换为与JSON模式兼容的数组,您可以通过 jsonSerialize() 方法查看此数组。
我们提供一流的支持描述,因为我们相信从一开始就编写可读的代码。如果您不喜欢这种方式,可以简单地省略描述,它们将在方案中留空。
类型和简短类型
Schema 类支持以下类型。每种类型都有一个或多个别名。在定义代码中的方案时,您可以使用别名来简化,它将在内部转换为正确的类型,包括在错误中使用。
数组和对象
数组和对象类型有些特殊,因为它们包含多个元素而不是单个值。因此,您可以定义那些属性应该包含的数据类型。以下是一些示例
$schema = Schema::parse([ 'items:a', // array of any type 'tags:a' => 's', // array of strings 'attributes:o', // object of any type 'user:o' => [ // an object with specific properties 'name:s', 'email:s?' ] ]);
非对象模式
默认情况下,模式定义一个对象,因为这是模式最常用的用途。如果您想使模式表示一个数组或甚至是一个基本类型,您只需定义一个没有名称的单个字段。以下示例定义了一个对象数组(即数据库查询的输出)。
$schema = Schema::parse([ ':a' => [ 'id:i', 'name:s', 'birthday:dt' ] ]);
此方案适用于以下类似的数据
[ ['id' => 1, 'name' => 'George', 'birthday' => '1732-02-22'], ['id' => 16, 'name' => 'Abraham', 'birthday' => '1809-02-12'], ['id' => 32, 'name' => 'Franklin', 'birthday' => '1882-01-30'] ]
可选属性和可空属性
在定义对象方案时,您可以使用一个 "?" 来表示属性是可选的。这意味着在验证过程中可以完全省略该属性。这与为属性提供一个 null 值不同,这在可选属性中被认为是无效的。
如果您想使属性允许 null 值,可以在属性上指定 nullable
属性。有两种方法可以这样做
[ // You can specify nullable as a property attribute. 'opt1:s?' => ['nullable' => true], // You can specify null as an optional type in the declaration. 'opt2:s|n?' => 'Another nullable, optional property.' ]
默认值
您可以使用 default
属性指定一个默认值。如果在验证过程中省略了值,则将使用默认值。请注意,在稀疏验证期间不应用默认值。
验证数据
一旦您有了方案,您就可以使用 validate() 或 isValid() 方法来验证数据。
Schema::validate() 方法
您将想要验证的数据传递给 Schema::validate(),它将返回您数据的清理副本或抛出一个 ValidationException。
$schema = Schema::parse(['id:i', 'name:s']); try { // $u1 will be ['id' => 123, 'name' => 'John'] $u1 = $schema->validate(['id' => '123', 'name' => 'John']); // This will thow an exception. $u2 = $schema->validate(['id' => 'foo']); } catch (ValidationException $ex) { // $ex->getMessage() will be: 'id is not a valid integer. name is required.' }
在用户提交的数据上调用 validate() 允许您尽早检查数据并在数据不正确时退出。如果您只想检查数据而不抛出异常,则 isValid() 方法是一个方便的方法,它根据数据是否有效返回 true 或 false。
$schema = Schema::parse(['page:i', 'count:i?']); if ($schema->isValid(['page' => 5]) { // This will be hit. } if ($schema->isValid(['page' => 2, 'count' => 'many']) { // This will not be hit because the data isn't valid. }
ValidationException 和 Validation 类
当您调用 validate() 并且验证失败时,将抛出一个 ValidationException。此异常包含一个属性,该属性是一个包含有关已失败字段更多信息的 Validation 对象。
如果您正在编写 API,则可以 json_encode() ValidationException,它应提供一个丰富的数据集,以帮助任何消费者确切地了解他们犯了什么错误。您还可以使用 Validation 属性的各个属性来帮助适当地呈现错误输出。
Validation JSON 格式
Validation
对象和 ValidationException
都编码为特定的格式。以下是一个示例
ValidationError = { "message": "string", // Main error message. "code": "integer", // HTTP-style status code. "errors": { // Specific field errors. "<fieldRef>": [ // Each key is a JSON reference field name. { "message": "string", // Field error message. "error": "string", // Specific error code, usually a schema attribute. "code": "integer" // Optional field error code. } ] } }
此格式优化用于帮助向用户界面呈现错误。您可以通过循环特定的 errors
集合,在用户界面上将错误与其输入对齐。对于深度嵌套的对象,字段名称是 JSON 引用。
模式引用
OpenAPI 允许使用 $ref
属性通过引用访问模式。使用引用允许您在一个地方定义常用模式,然后从多个位置引用它们。
要使用引用,您必须
- 在某个地方定义要引用的模式。
- 使用
$ref
属性引用模式。 - 使用
Schema::setRefLookup()
将模式查找函数添加到主模式中
定义可重用模式
OpenAPI 规范将所有可重用模式放置在 /components/schemas
下。如果您正在定义一个大的数组,这是一个放置它们的好地方。
$components = [ 'components' => [ 'schemas' => [ 'User' => [ 'type' => 'object', 'properties' => [ 'id' => [ 'type' => 'integer' ], 'username' => [ 'type' => 'string' ] ] ] ] ] ]
使用 $ref
引用模式
使用 /
字符分隔键来引用模式路径。
$userArray = [ 'type' => 'array', 'items' => [ '$ref' => '#/components/schemas/User' ] ]
使用 Schema::setRefLookup()
解析引用
Schema
类有一个 setRefLookup()
方法,允许您添加一个用于解析引用的可调用函数。该函数应具有以下签名
function(string $ref): array|Schema|null { ... }
函数接受来自 $ref
属性的字符串,并返回一个模式数组、Schema
对象或 null(如果找不到模式)。Garden Schema 在 ArrayRefLookup
类中具有默认的 ref 查找实现,可以从静态数组中解析引用。这对于大多数用途来说应该足够好了,但您始终可以定义自己的。
您可以将所有内容组合如下
$sch = new Schema($userArray); $sch->setRefLookup(new ArrayRefLookup($components)); $valid = $sch->validate(...);
引用在验证期间解析,所以如果您的引用有任何错误,那么在设置您的模式或引用查找函数时,将抛出 RefNotFoundException
,而不是在验证期间。
模式多态性
通过允许您根据其值验证不同的模式,模式支持实现模式多态性。
属性 discriminator
模式的 discriminator
允许您指定一个对象属性,该属性指定了对象的类型。然后使用该属性引用对象的特定模式。该判别器的格式如下
{ "discriminator": { "propertyName": "<string>", // Name of the property used to reference a schema. "mapping": { "<propertyValue1>": "<ref>", // Reference to a schema. "<propertyValue>": "<alias>" // Map a value to another value. } } }
如上所示,propertyName
指定了哪个属性用作判别器。还有一个可选的 mapping
属性,允许您控制如何将模式映射到值。判别器按以下方式解析
- 使用映射属性将属性值进行映射。
- 如果值是有效的 JSON 引用,则进行查找。只有映射中的值可以指定这种方式下的 JSON 引用。
- 如果值不是有效的 JSON 引用,则在其前面添加
#/components/schemas/
以形成一个 JSON 引用。
以下是一个示例
{ "discriminator": { "propertyName": "petType", "mapping": { "dog": "#/components/schemas/Dog", // A direct reference. "fido": "Dog" // An alias that will be turned into a reference. } } }
属性 oneOf
oneOf
属性与 discriminator
一起使用,以限制对象允许验证的模式。如果您没有指定 oneOf
,则 #/components/schemas
下的任何模式都是合法的。
要使用 oneOf
属性,您必须指定如下所示的 $ref
节点
{ "oneOf": [ { "$ref": "#/components/schemas/Dog" }, { "$ref": "#/components/schemas/Cat" }, { "$ref": "#/components/schemas/Mouse" }, ], "discriminator": { "propertyType": "species" } }
在上面的示例中,“species”属性将被用来构建一个对模式的引用。该引用必须匹配 oneOf
属性中的其中一个引用。
如果您熟悉 OpenAPI 规范,请注意 Garden Schema 目前不支持为 oneOf
使用内联模式。
验证选项
validate() 和 isValid() 都可以接受一个额外的 $options 参数,该参数根据选项修改验证的行为。
选项 request
您可以通过传递选项 ['request' => true]
来指定您正在验证请求数据。在验证请求数据时,标记为 readOnly: true
的属性将视为不存在,即使它们被标记为必需。
response
选项
您可以通过传递选项 ['response' => true]
来指定您正在验证响应数据。在验证响应数据时,标记为 writeOnly: true
的属性将视为不存在,即使它们被标记为必需。
sparse
选项
您可以通过传递选项 ['sparse' => true]
来指定稀疏验证。当您执行稀疏验证时,缺失的属性不会引发错误,并将稀疏数据返回。稀疏验证允许您使用相同的模式进行记录的插入和更新。这在具有 POST 与 PATCH 请求的数据库或 API 中很常见。
标志
标志可以应用于模式以更改其继承的验证。
use Garden\Schema\Schema; $schema = Schema::parse([]); // Enable a flag. $schema->setFlag(Schema::VALIDATE_STRING_LENGTH_AS_UNICODE, true); // Disable a flag. $schema->setFlag(Schema::VALIDATE_STRING_LENGTH_AS_UNICODE, false); // Set all flags together. $schema->setFlags(Schema::VALIDATE_STRING_LENGTH_AS_UNICODE & Schema::VALIDATE_EXTRA_PROPERTY_NOTICE); // Check if a flag is set. $schema->hasFlag(Schema::VALIDATE_STRING_LENGTH_AS_UNICODE); // true
VALIDATE_STRING_LENGTH_AS_UNICODE
默认情况下,模式以字节为单位验证字符串长度。这对于数据库等存储单位来说是很有用的。
一些 Unicode 字符需要超过 1 个字节。例如,一个表情符号 😱 需要 4 个字节。
启用此标志以验证 Unicode 字符长度而不是字节长度。
VALIDATE_EXTRA_PROPERTY_NOTICE
设置此标志以在验证对象具有不在模式中定义的属性时触发通知。
VALIDATE_EXTRA_PROPERTY_EXCEPTION
设置此标志以在验证对象具有不在模式中定义的属性时抛出异常。
使用 addValidator() 自定义验证
您可以使用 Schema::addValidator()
来自定义验证。此方法允许您将回调附加到模式路径。回调具有以下形式
function (mixed $value, ValidationField $field): bool { }
如果值有效,则回调应返回 true
,否则返回 false
。您可以使用提供的 ValidationField
添加自定义错误消息。
过滤数据
您可以使用 Schema::addFilter()
在验证之前过滤数据。此方法允许您在模式路径上过滤数据。回调具有以下形式
function (mixed $value, ValidationField $field): mixed { }
回调应返回过滤后的值。过滤器在验证之前被调用,因此您可以使用它们来清理可能需要额外处理的数据。
Schema::addFilter()
还接受 $validate
参数,允许您的过滤器验证数据并绕过默认验证。如果您以这种方式验证日期,则可以向 ValidationField
参数添加自定义错误,并在验证失败时返回 Invalid::value()
。
格式过滤器
您还可以使用 Schema::addFormatFilter()
过滤具有特定格式的所有字段。此方法与 Schema::addFilter()
类似,但它应用于匹配给定 format
的所有字段。您甚至可以使用格式过滤器覆盖默认格式处理。
$schema = new Schema([...]); // By default schema returns instances of DateTimeImmutable, instead return a string. $schema->addFormatFilter('date-time', function ($v) { $dt = new \DateTime($v); return $dt->format(\DateTime::RFC3339); }, true);
重写验证类和本地化
由于模式生成错误消息,本地化可能是一个问题。尽管 Garden Schema 本身不提供任何本地化功能,但它被设计为可以扩展以添加本地化。您可以通过子类化 Validation 类并重写其 translate() 方法来实现这一点。以下是一个基本示例
class LocalizedValidation extends Validation { public function translate($str) { if (substr($str, 0, 1) === '@') { // This is a literal string that bypasses translation. return substr($str, 1); } else { return gettext($str); } } } // Install your class like so: $schema = Schema::parse([...]); $schema->setValidationClass(LocalizedValidation::class);
在上述示例中有几点需要注意
-
在重写 translate() 时,请确保处理以 '@' 字符开头的字符串的情况。此类字符串不应翻译且应移除字符。
-
您可以使用
setValidationClass()
方法告诉Schema
对象使用您特定的Validation
子类。此方法接受一个类名或对象实例。如果您传递一个对象,则每次需要验证对象时都会对其进行克隆。当您想要使用依赖注入并且您的类需要更复杂的实例化时,这很有用。
JSON Schema 支持
Schema
对象是OpenAPI Schema数组的包装器。这意味着您可以将有效的JSON schema传递给Schema的构造函数。下表列出了支持的JSON Schema属性。
OpenAPI Schema 支持
OpenAPI定义了一些在验证期间应用的扩展属性。