savks / negotiator
Requires
- php: ^8.1
- laravel/framework: ^9.0 | ^10.0 | ^11.0
- savks/php-contexts: ^1.0
- dev-master
- 2.16.0
- 2.15.0
- 2.14.0
- 2.13.1
- 2.13.0
- 2.12.1
- 2.12.0
- 2.11.2
- 2.11.1
- 2.11.0
- 2.10.1
- 2.10.0
- 2.9.1
- 2.9.0
- 2.8.0
- 2.7.0
- 2.6.3
- 2.6.2
- 2.6.1
- 2.6.0
- 2.5.0
- 2.4.0
- 2.3.0
- 2.2.1
- 2.2.0
- 2.1.0
- 2.0.2
- 2.0.1
- v2.0.0
- 1.x-dev
- 1.16.0
- 1.15.1
- 1.15.0
- 1.14.0
- 1.13.2
- 1.13.1
- 1.13.0
- 1.12.0
- 1.10.2
- 1.10.1
- 1.10.0
- 1.9.0
- 1.8.0
- 1.7.1
- 1.7.0
- 1.6.1
- 1.6.0
- 1.5.1
- 1.5.0
- 1.4.1
- 1.4.0
- 1.3.0
- 1.2.0
- 1.1.3
- 1.1.2
- 1.1.1
- 1.1.0
- 1.0.0
- dev-next-optional
- dev-next-2.1
- dev-next
- dev-feature/jit
- dev-feature/json-schema
- dev-feature/remove-object-ignore-keys
This package is auto-updated.
Last update: 2024-09-16 15:16:50 UTC
README
该包用作 Laravel JSON 资源的替代品。该包的优点在于严格的映射类型化以及内置的 TypeScript 类型生成工具。
安装
composer require savks/negotiator
映射器描述
要编写自定义映射器,需要创建一个继承自 \Savks\Negotiator\Support\Mapping\Mapper
的类。映射器示例
<?php namespace App\Http\Mapping; use App\Models\User; use Savks\Negotiator\Support\Mapping\{ Casts\Cast, Mapper, Schema }; final class UserMapper extends Mapper { public function __construct(public readonly User $user) { } public static function schema(): Cast { return Schema::object([ 'id' => Schema::string('id'), 'firstName' => Schema::string('first_name'), 'lastName' => Schema::string('last_name')->nullable(), ], 'user'); } }
映射器描述不应包含命令式代码,因为无法生成类型。这是因为类型生成过程中会模拟创建映射器以获取类型信息,如果描述中存在命令式代码,则将阻止它们的工作。
NULL 和非必需字段
如果字段可能具有 null
的值,则需要将其标记为 ->nullable()
,因为严格类型化会导致抛出错误。如果字段不是必需的,则可以将其标记为 ->optional()
(建议这样做以减少数据输出量),在这种情况下,如果值为 null
,则在 object
和 keyedArray
的类型转换中忽略该字段。
此外,为了减少数据输出量,不仅可以使
null
成为非必需的。为此,在boolean
中有辅助方法->optionalIfFalse()
,或在string
和array
中有->optionalIfEmpty()
。为了更灵活的配置,需要使用->optional()
方法的参数。
在极端情况下,可能需要仅将
optional
指定给类型,同时保留映射时的类型检查。在这种情况下,需要使用->maybeOptional()
或->maybeNullable()
方法。
内置类型
简单类型
string
,boolean
,number
— 原始类型。constString
,constBoolean
,constNumber
— 静态类型。区别在于值是明确设置的。此外,它们也可以作为(静态值的)字面量。anyObject
— 允许描述对象,而不描述其字段(在 TypeScript 中是Record<string, any>
)。enum
— 枚举值。null
— 将值定义为 NULL。any
— 任何值(类似于 TypeScript 中的值)。
复杂类型
array
— 常规数组类型 — 列表。基于任何可迭代值。例如
<?php use Savks\Negotiator\Support\Mapping\{ Casts\Cast, Schema }; Schema::object([ 'items' => Schema::array( Schema::anyObject(), 'items' ), ]);
object
— 带有静态字段的对象。例如
<?php use Savks\Negotiator\Support\Mapping\{ Casts\Cast, Schema }; Schema::object([ 'field' => Schema::string('field'), ]);
keyedArray
— 关联数组/映射,与对象不同之处在于它基于可迭代值。例如
<?php use Savks\Negotiator\Support\Mapping\{ Casts\Cast, Schema }; Schema::object([ 'items' => Schema::keyedArray( Schema::anyObject(), 'items' ), ]);
实用类型
mapper
— 允许指定其他映射器作为值。例如
<?php use App\Models\User; use Savks\Negotiator\Support\Mapping\{ Casts\Cast, Schema }; Schema::object([ 'user' => Schema::mapper( fn (User $user): UserMapper => new UserMapper($user), 'user' ), ]); Schema::object([ 'user' => Schema::mapper(UserMapper::class, 'user'), ]);
为了正确生成 TypeScript 中的类型,在映射器的解析函数中,重要的是指定映射器本身作为返回类型,否则值将具有
any
的值。
union
— 允许指定多个可能的类型。指定为选项集(条件不影响类型生成)。例如
<?php use App\Models\User; use Savks\Negotiator\Support\Mapping\{ Casts\Cast, Schema }; Schema::object([ 'field' => Schema::union() ->variant( fn (User $user) => $user->role === 'admin', Schema::object([ 'field' => Schema::string('field'), ]) ) ->variant( ['role', 'guest'], Schema::object([ 'field' => Schema::string('field'), ]) ) ->default( Schema::object([ 'field' => Schema::string('field'), ]) ), ]);
spread
— 允许将一个对象展开到另一个对象中。例如
<?php use Savks\Negotiator\Support\Mapping\Schema; use Savks\Negotiator\Support\Mapping\Casts\{ ObjectUtils\Spread, Cast }; Schema::object([ 'field' => Schema::string('field'), new Spread([ 'otherField' => Schema::string('other_field') ], 'accessor'), ]);
typedField
— 允许指定具有类型化键的字段。例如
<?php use Savks\Negotiator\Support\Mapping\Schema; use Savks\Negotiator\Support\Mapping\Casts\{ ObjectUtils\TypedField, Cast }; Schema::object([ 'field' => Schema::string('field'), new TypedField(SomeEnum::CASE, [ 'otherField' => Schema::string('other_field') ]), ]);
intersection
— 用于指定交叉类型,通常用于需要扩展其他映射的情况。例如
<?php use Savks\Negotiator\Support\Mapping\{ Casts\Cast, Schema }; Schema::object([ 'field' => Schema::intersection( Schema::mapper(UserMapper::class, 'user'), Schema::object([ 'otherField' => Schema::string('other_field') ], 'user'), ), ]);
oneOfConst
— 允许指定值可以具有一个或多个类型常量。例如
<?php use Savks\Negotiator\Support\Mapping\{ Casts\Cast, Schema }; Schema::object([ 'field' => Schema::oneOfConst([ Schema::constNumber(1), Schema::constNumber(2), Schema::constNumber(3), ]), ]);
scope
— 允许计算将被传递到映射中的值。例如
<?php use Savks\Negotiator\Support\Mapping\{ Casts\Cast, Schema }; Schema::object([ 'field' => Schema::scope( Schema::oneOfConst([ Schema::constNumber(1), Schema::constNumber(2), Schema::constNumber(3), ]), fn (array $data) => $data['value'] ), ]);
类型生成
为了生成包类型,包含了一个生成器类 Savks\Negotiator\Support\TypeGeneration\Generator
。要使用它,只需要指定要为哪些映射器和哪些命名空间生成代码。示例用法
<?php use App\Http\Mapping\UserMapper; use Savks\Negotiator\Enums\RefTypes; use Illuminate\Support\Str; use Savks\Negotiator\Support\TypeGeneration\TypeScript\{ Generator, Target }; $generator = new Generator( /* Функція для визначення референсів. */ fn (RefTypes $type, string $target) => match ($type) { RefTypes::ENUM => sprintf( 'import(\'@enums\').%s', class_basename($target::class) ), RefTypes::MAPPER => sprintf( 'import(\'@dto\').%s', class_basename($target::class) ), } ); $generator->addTarget( new Target(UserMapper::class, '@dto') ); $generator->saveTo('./path_to_file.ts');
有时,生成器无法为获取类型创建映射器,因为映射器在构造函数中接收特定的输入数据。在这种情况下,需要实现接口
Savks\Negotiator\Support\Mapping\WithCustomMock
,并使用该方法创建带有自定义数据的映射器。
极端情况
-
无法声明性描述映射器的数据。
解决这个问题的一种方法是使用类型转换来处理最终值。类型转换具有访问器,这是一种指定从哪里获取数据的方法,它可以是返回最终值的匿名函数,在这种情况下,类型转换中只剩下类型描述。