savks / negotiator
Installs: 1 022
Dependents: 0
Suggesters: 0
Security: 0
Stars: 5
Watchers: 1
Forks: 1
Open Issues: 0
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
Пакет використовується як альтернатива JSON-ресурсів Laravel. Перевагою даного пакету є строга типізація мапингу та вбудований інструмент для генерації 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
. Для цого є допоміжний метод->optionalIfFalse()
вboolean
, або->optionalIfEmpty()
в типахstring
таarray
. Для більш гнучкого налаштування потрібно використовувати аргументи методу->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
з методом створення маперу з довільними даними.
Крайні випадки
-
Неможливо декларативно описати дані для мапера.
Розв'язання цієї проблеми буде прокидка в каст кінцевих значень. Касти мають аксесор, це спосіб вказати звідки брати дані для роботи, він може бути анонімною функцією яка поверне кінцеве значення, в такому випадку в самому касті залишиться лише описати типи.