skiexx / laravel-data-scramble
Free Scramble extension for automatic OpenAPI schema generation from spatie/laravel-data classes
Requires
- php: ^8.4
- dedoc/scramble: ^0.13
- illuminate/contracts: ^12.0
- spatie/laravel-data: ^4.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- laravel/pint: ^1.0
- orchestra/testbench: ^10.0
- pestphp/pest: ^3.0
- phpstan/phpstan: ^2.0
README
Бесплатное расширение для dedoc/scramble, которое автоматически генерирует OpenAPI-схемы из классов spatie/laravel-data.
Пакет анализирует ваши Data, Resource и Dto классы, используемые как входные параметры контроллеров (вместо FormRequest) и как возвращаемые типы (вместо JsonResource), и строит полноценную OpenAPI-документацию для каждого маршрута.
Возможности
- Автоматический парсинг
Data,Resource,Dtoклассов - Поддержка всех скалярных типов PHP:
string,int,float,bool,array - Поддержка
Carbon,DateTime,DateTimeImmutable,DateInterval - Поддержка backed enum (
stringиint) - Вложенные Data-объекты с автоматическим созданием
$ref-ссылок в#/components/schemas/ - Коллекции Data-объектов (
Data[],#[DataCollectionOf]) - Маппинг 20+ атрибутов валидации spatie/laravel-data в OpenAPI-ограничения
- Поддержка переименования свойств через
#[MapOutputName],#[MapInputName],#[MapName] - Поддержка
SnakeCaseMapper,CamelCaseMapperи пользовательских маперов - Обработка
nullable,optional,default,computed,hidden,lazyсвойств - Интерфейс
OpenApiSchemaдля полностью ручного определения схемы любого класса - Трейт
HasOpenApiSchemaдля автогенерации схемы из Data-класса - Набор трейтов-форматов (
StringFormat,UuidFormat,EmailFormatи др.) - Атрибут
#[ResponseData]для описания ответов анонимныхJsonResource - Поддержка пагинации (LengthAwarePaginator, CursorPaginator) с meta/links
Требования
- PHP ^8.4
- Laravel ^12.0
- spatie/laravel-data ^4.0
- dedoc/scramble ^0.13
Установка
composer require skiexx/laravel-data-scramble
Сервис-провайдер регистрируется автоматически через Laravel auto-discovery. Расширение Scramble также регистрируется автоматически при загрузке пакета.
Публикация конфигурации (опционально)
php artisan vendor:publish --tag="skiexx-data-scramble-config"
Файл конфигурации будет создан в config/skiexx-data-scramble.php.
Быстрый старт
После установки пакет начинает работать сразу. Никаких дополнительных настроек не требуется.
Пример контроллера
use App\Data\UserData; use App\Data\CreateUserData; class UserController { public function index(): UserData { return UserData::from(User::first()); } public function store(CreateUserData $data): UserData { $user = User::create($data->toArray()); return UserData::from($user); } }
Пример Data-класса
use Spatie\LaravelData\Data; use Spatie\LaravelData\Attributes\Validation\Email; use Spatie\LaravelData\Attributes\Validation\Min; use Spatie\LaravelData\Attributes\Validation\Max; class CreateUserData extends Data { public function __construct( #[Min(2), Max(255)] public string $name, #[Email] public string $email, #[Min(8)] public string $password, public ?string $phone, ) { } }
Этот класс автоматически сгенерирует OpenAPI-схему:
CreateUserData: type: object required: - name - email - password properties: name: type: string minLength: 2 maxLength: 255 email: type: string format: email password: type: string minLength: 8 phone: type: - string - "null"
Конфигурация
// config/skiexx-data-scramble.php return [ // Автоматическая регистрация расширения в Scramble. // Установите false, если хотите зарегистрировать вручную. 'auto_register' => true, // Пропускать свойства с атрибутом #[Hidden]. 'skip_hidden' => true, // Lazy-свойства считаются необязательными (не попадают в required). 'lazy_as_optional' => true, // Computed-свойства получают readOnly: true. 'computed_as_readonly' => true, ];
Поддерживаемые типы
Скалярные типы
| PHP-тип | OpenAPI-тип |
|---|---|
string |
type: string |
int |
type: integer |
float |
type: number |
bool |
type: boolean |
array |
type: array |
mixed |
без ограничений |
Даты
| PHP-тип | OpenAPI |
|---|---|
Carbon\Carbon |
type: string, format: date-time |
Carbon\CarbonImmutable |
type: string, format: date-time |
DateTime |
type: string, format: date-time |
DateTimeImmutable |
type: string, format: date-time |
DateTimeInterface |
type: string, format: date-time |
DateInterval |
type: string, format: duration |
Перечисления (Enum)
Backed enum автоматически преобразуется в соответствующий тип с перечислением допустимых значений:
enum Status: string { case Active = 'active'; case Inactive = 'inactive'; } class UserData extends Data { public function __construct( public string $name, public Status $status, ) { } }
Результат:
status: type: string enum: - active - inactive
Для int-backed enum будет использован type: integer.
Вложенные Data-объекты
Вложенные объекты автоматически создают $ref-ссылки в #/components/schemas/:
class AddressData extends Data { public function __construct( public string $city, public string $street, ) { } } class UserData extends Data { public function __construct( public string $name, public AddressData $address, ) { } }
Результат:
UserData: type: object required: - name - address properties: name: type: string address: $ref: '#/components/schemas/AddressData'
Коллекции Data-объектов
class TeamData extends Data { /** * @param UserData[] $members */ public function __construct( public string $name, #[DataCollectionOf(UserData::class)] public array $members, ) { } }
Результат:
members: type: array items: $ref: '#/components/schemas/UserData'
Маппинг атрибутов валидации
Пакет автоматически преобразует атрибуты валидации spatie/laravel-data в соответствующие ограничения OpenAPI:
Ограничения размера
| Атрибут | string | integer/number | array |
|---|---|---|---|
#[Min(n)] |
minLength: n |
minimum: n |
minItems: n |
#[Max(n)] |
maxLength: n |
maximum: n |
maxItems: n |
#[Between(a,b)] |
minLength + maxLength |
minimum + maximum |
minItems + maxItems |
#[Size(n)] |
minLength = maxLength = n |
minimum = maximum = n |
minItems = maxItems = n |
Форматы
| Атрибут | OpenAPI |
|---|---|
#[Email] |
format: email |
#[Url] |
format: uri |
#[Uuid] |
format: uuid |
#[Ulid] |
format: ulid |
#[IP] |
format: ip |
#[IPv4] |
format: ipv4 |
#[IPv6] |
format: ipv6 |
#[Date] |
format: date |
#[Json] |
contentMediaType: application/json |
#[Image] |
format: binary |
#[File] |
format: binary |
Паттерны
| Атрибут | OpenAPI pattern |
|---|---|
#[Regex('/pat/')] |
значение регулярного выражения |
#[Alpha] |
^[a-zA-Z]+$ |
#[AlphaDash] |
^[a-zA-Z0-9_-]+$ |
#[AlphaNumeric] |
^[a-zA-Z0-9]+$ |
#[Lowercase] |
^[a-z]+$ |
#[Uppercase] |
^[A-Z]+$ |
#[Digits(n)] |
^\d{n}$ |
#[DigitsBetween(a,b)] |
^\d{a,b}$ |
#[StartsWith('a','b')] |
^(a|b) |
#[EndsWith('a','b')] |
(a|b)$ |
Прочие
| Атрибут | Эффект |
|---|---|
#[Nullable] |
nullable: true |
#[MultipleOf(n)] |
расширение x-multipleOf: n (для number) |
Атрибуты без прямого аналога в OpenAPI (Exists, Unique, Same, Different, Confirmed и др.) молча пропускаются.
Переименование свойств
Пакет полностью поддерживает механизм маппинга имен spatie/laravel-data.
На уровне класса
use Spatie\LaravelData\Attributes\MapOutputName; use Spatie\LaravelData\Mappers\SnakeCaseMapper; #[MapOutputName(SnakeCaseMapper::class)] class UserProfileData extends Data { public function __construct( public string $firstName, // -> first_name public string $lastName, // -> last_name public int $userAge, // -> user_age ) { } }
На уровне свойства
use Spatie\LaravelData\Attributes\MapOutputName; use Spatie\LaravelData\Attributes\MapInputName; class OrderData extends Data { public function __construct( #[MapInputName('order_id')] public string $orderId, #[MapOutputName('total_amount')] public float $totalAmount, ) { } }
Поддерживаемые маперы
SnakeCaseMapper--camelCase->snake_caseCamelCaseMapper--snake_case->camelCase#[MapName('custom_name')]-- комбинированный маппинг input + output#[MapInputName('name')]-- маппинг только для входных данных#[MapOutputName('name')]-- маппинг только для выходных данных- Любой пользовательский мапер, реализующий контракт laravel-data
Обработка специальных свойств
Nullable
class Data extends \Spatie\LaravelData\Data { public function __construct( public ?string $name, // nullable: true, не в required ) { } }
Default-значения
class Data extends \Spatie\LaravelData\Data { public function __construct( public string $status = 'active', // не в required ) { } }
Hidden
use Spatie\LaravelData\Attributes\Hidden; class Data extends \Spatie\LaravelData\Data { public function __construct( public string $name, #[Hidden] public string $internalToken, // полностью исключено из схемы ) { } }
Поведение настраивается через skip_hidden в конфигурации.
Computed
use Spatie\LaravelData\Attributes\Computed; class UserData extends \Spatie\LaravelData\Data { #[Computed] public string $fullName; public function __construct( public string $firstName, public string $lastName, ) { $this->fullName = "$firstName $lastName"; } }
Свойство fullName получит readOnly: true в OpenAPI-схеме. Поведение настраивается через computed_as_readonly.
Lazy
use Spatie\LaravelData\Lazy; class UserData extends \Spatie\LaravelData\Data { public function __construct( public string $name, public Lazy|AddressData $address, // не в required ) { } }
Lazy-свойства по умолчанию считаются необязательными. Поведение настраивается через lazy_as_optional.
Интерфейс OpenApiSchema
Для случаев, когда автоматическая генерация схемы не подходит, вы можете определить схему вручную через интерфейс OpenApiSchema.
Ручное определение схемы
use Skiexx\LaravelDataScramble\Contracts\OpenApiSchema; class ExternalPaymentResponse implements OpenApiSchema { public function __construct( public string $transactionId, public float $amount, public string $currency, ) { } public static function openApiSchema(): array { return [ 'type' => 'object', 'properties' => [ 'transaction_id' => [ 'type' => 'string', 'format' => 'uuid', 'description' => 'Уникальный идентификатор транзакции', ], 'amount' => [ 'type' => 'number', 'format' => 'double', 'description' => 'Сумма платежа', ], 'currency' => [ 'type' => 'string', 'enum' => ['USD', 'EUR', 'RUB'], ], ], 'required' => ['transaction_id', 'amount', 'currency'], ]; } }
Этот интерфейс можно повесить на любой класс -- он не обязан наследовать Data. Расширение Scramble распознает его автоматически.
Автогенерация через HasOpenApiSchema
Если класс наследует Data и вы хотите реализовать OpenApiSchema без ручного описания всех свойств, используйте трейт HasOpenApiSchema:
use Spatie\LaravelData\Data; use Skiexx\LaravelDataScramble\Contracts\OpenApiSchema; use Skiexx\LaravelDataScramble\Traits\HasOpenApiSchema; class UserData extends Data implements OpenApiSchema { use HasOpenApiSchema; public function __construct( public string $name, public string $email, ) { } }
Трейт автоматически генерирует массив схемы на основе свойств класса. Это полезно, когда вы хотите, чтобы другие части вашего приложения могли вызвать UserData::openApiSchema() для получения схемы программно.
Трейты форматов
Пакет предоставляет набор трейтов, которые можно использовать на классах, реализующих OpenApiSchema, для стандартных типов:
| Трейт | OpenAPI type |
OpenAPI format |
|---|---|---|
StringFormat |
string |
-- |
IntegerFormat |
integer |
-- |
NumberFormat |
number |
-- |
BooleanFormat |
boolean |
-- |
ArrayFormat |
array |
-- |
DateFormat |
string |
date-time |
UuidFormat |
string |
uuid |
EmailFormat |
string |
email |
Пример использования
use Skiexx\LaravelDataScramble\Contracts\OpenApiSchema; use Skiexx\LaravelDataScramble\Traits\Formats\UuidFormat; class UserId implements OpenApiSchema { use UuidFormat; public function __construct( public readonly string $value, ) { } public static function openApiSchema(): array { return static::openApiType(); } }
Результат:
UserId: type: string format: uuid
Data-классы как входные параметры контроллера
Пакет автоматически распознает Data-классы, используемые как параметры методов контроллера, и правильно генерирует OpenAPI-параметры:
- GET/DELETE/HEAD — свойства Data-класса отображаются как query parameters
- POST/PUT/PATCH — свойства Data-класса отображаются как request body (
application/json) #[FromRouteParameter]— свойства отображаются как path parameters
Пример
use Spatie\LaravelData\Data; use Spatie\LaravelData\Attributes\FromRouteParameter; use Spatie\LaravelData\Attributes\Validation\Min; use Spatie\LaravelData\Attributes\Validation\Max; class GetUsersData extends Data { public function __construct( #[Min(1), Max(100)] public int $page, public ?string $search, public int $perPage = 15, ) { } } class UserController { // GET /api/users?page=1&search=john&perPage=15 public function index(GetUsersData $data): JsonResource { return JsonResource::collection(User::paginate($data->perPage)); } }
Результат — query parameters:
parameters: - name: page in: query required: true schema: type: integer minimum: 1 maximum: 100 - name: search in: query required: false schema: type: - string - "null" - name: perPage in: query required: false schema: type: integer
Для POST-запросов
class CreateUserData extends Data { public function __construct( #[Min(2), Max(255)] public string $name, #[Email] public string $email, ) { } } class UserController { // POST /api/users — body: { "name": "...", "email": "..." } public function store(CreateUserData $data): JsonResource { return new JsonResource(User::create($data->toArray())); } }
Результат — request body (application/json).
Поддержка #[FromRouteParameter]
Используйте встроенный атрибут laravel-data для свойств, которые берутся из маршрута:
use Spatie\LaravelData\Attributes\FromRouteParameter; class UpdateUserData extends Data { public function __construct( #[FromRouteParameter('user')] public int $userId, #[Min(2), Max(255)] public string $name, #[Email] public string $email, ) { } } class UserController { // PUT /api/users/{user} public function update(UpdateUserData $data): JsonResource { // $data->userId автоматически берется из route param {user} return new JsonResource(User::findOrFail($data->userId)->update($data->toArray())); } }
Результат:
parameters: - name: user in: path required: true schema: type: integer requestBody: content: application/json: schema: type: object required: [name, email] properties: name: type: string minLength: 2 maxLength: 255 email: type: string format: email
Свойство userId не попадает в body — оно отображается как path parameter с именем user (из атрибута #[FromRouteParameter('user')]).
Поддержка #[FromRouteParameterProperty]
Для получения свойства модели из route parameter:
use Spatie\LaravelData\Attributes\FromRouteParameterProperty; class OrderData extends Data { public function __construct( #[FromRouteParameterProperty('user', property: 'id')] public int $userId, public string $product, ) { } }
Свойства с #[FromRouteParameterProperty] также исключаются из query/body и не попадают в документацию как параметры запроса.
Маппинг имен для входных параметров
При использовании #[MapInputName] или #[MapName], имена параметров в OpenAPI будут соответствовать маппингу:
use Spatie\LaravelData\Attributes\MapInputName; use Spatie\LaravelData\Mappers\SnakeCaseMapper; #[MapInputName(SnakeCaseMapper::class)] class FilterData extends Data { public function __construct( public string $firstName, // query param: first_name public string $lastName, // query param: last_name ) { } }
Атрибут ResponseData
Когда контроллер возвращает анонимный JsonResource для обертки ответа в { "data": ... }, Scramble не может определить реальный тип данных. Атрибут #[ResponseData] решает эту проблему.
Проблема
class UserController { // Scramble видит JsonResource, но не знает что внутри UserData public function show(string $id): JsonResource { return new JsonResource($action->execute($id)); } public function index(): JsonResource { return JsonResource::collection($action->execute()); } }
Решение
use Skiexx\LaravelDataScramble\Attributes\ResponseData; class UserController { #[ResponseData(UserData::class)] public function show(string $id): JsonResource { return new JsonResource($action->execute($id)); } #[ResponseData(UserData::class, collection: true)] public function index(): JsonResource { return JsonResource::collection($action->execute()); } }
Параметры атрибута
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
dataClass |
string | (обязательный) | FQCN Data-класса |
collection |
bool | false |
Массив объектов { "data": [{...}] } |
paginated |
bool | false |
LengthAwarePaginator с meta + links |
cursorPaginated |
bool | false |
CursorPaginator с meta |
status |
int | 200 |
HTTP status code (например 201 для POST Create) |
wrapped |
bool | true |
Обертка { "data": ... }, для paginated всегда true |
Примеры использования
Одиночный объект
#[ResponseData(UserData::class)] public function show(string $id): JsonResource { return new JsonResource($action->execute($id)); }
Результат:
responses: 200: content: application/json: schema: type: object required: [data] properties: data: $ref: '#/components/schemas/UserData'
Коллекция
#[ResponseData(UserData::class, collection: true)] public function index(): JsonResource { return JsonResource::collection($action->execute()); }
Результат:
responses: 200: content: application/json: schema: type: object required: [data] properties: data: type: array items: $ref: '#/components/schemas/UserData'
Пагинация (LengthAwarePaginator)
#[ResponseData(UserData::class, paginated: true)] public function index(): JsonResource { return JsonResource::collection($action->execute()); }
Результат включает полную структуру пагинации Laravel:
responses: 200: content: application/json: schema: type: object required: [data, links, meta] properties: data: type: array items: $ref: '#/components/schemas/UserData' links: type: object properties: first: { type: [string, "null"] } last: { type: [string, "null"] } prev: { type: [string, "null"] } next: { type: [string, "null"] } meta: type: object properties: current_page: { type: integer } from: { type: [integer, "null"] } last_page: { type: integer } links: type: array items: type: object properties: url: { type: [string, "null"] } label: { type: string } active: { type: boolean } path: { type: string } per_page: { type: integer } to: { type: [integer, "null"] } total: { type: integer }
Cursor-пагинация
#[ResponseData(UserData::class, cursorPaginated: true)] public function index(): JsonResource { return JsonResource::collection($action->execute()); }
Результат:
responses: 200: content: application/json: schema: type: object required: [data, meta] properties: data: type: array items: $ref: '#/components/schemas/UserData' meta: type: object properties: path: { type: string } per_page: { type: integer } next_cursor: { type: [string, "null"] } prev_cursor: { type: [string, "null"] } next_page_url: { type: [string, "null"] } prev_page_url: { type: [string, "null"] }
Кастомный status code
#[ResponseData(UserData::class, status: 201)] public function store(CreateUserData $data): JsonResource { return new JsonResource($action->execute($data)); }
Без обертки
Если ответ не обернут в { "data": ... }:
#[ResponseData(UserData::class, wrapped: false)] public function show(string $id): JsonResource { return new JsonResource($action->execute($id)); }
Результат -- прямая ссылка на схему без обертки:
responses: 200: content: application/json: schema: $ref: '#/components/schemas/UserData'
Для коллекции без обертки:
#[ResponseData(UserData::class, collection: true, wrapped: false)]
Результат:
responses: 200: content: application/json: schema: type: array items: $ref: '#/components/schemas/UserData'
Примечание: для
paginatedиcursorPaginatedпараметрwrappedигнорируется -- обертка сdata,metaиlinksприменяется всегда, так как это стандартный формат ответа Laravel-пагинатора.
Продвинутая настройка
Ручная регистрация расширения
Если вам нужно контролировать момент регистрации или регистрировать расширение только для определенного API:
// config/skiexx-data-scramble.php return [ 'auto_register' => false, // Отключаем авторегистрацию ];
// AppServiceProvider.php или другой провайдер use Dedoc\Scramble\Scramble; use Skiexx\LaravelDataScramble\Extensions\LaravelDataTypeToSchemaExtension; public function boot(): void { // Регистрация для конкретного API Scramble::registerExtension(LaravelDataTypeToSchemaExtension::class); }
Подмена резолверов
Если вам нужно изменить логику генерации схемы, вы можете создать собственное расширение, наследующее LaravelDataTypeToSchemaExtension:
use Skiexx\LaravelDataScramble\Extensions\LaravelDataTypeToSchemaExtension; use Dedoc\Scramble\Support\Generator\Types\Type as OpenApiType; use Dedoc\Scramble\Support\Type\Type; use Dedoc\Scramble\Support\Type\ObjectType; class CustomDataExtension extends LaravelDataTypeToSchemaExtension { public function shouldHandle(Type $type): bool { // Обрабатывать только классы из определенного namespace if ($type instanceof ObjectType && str_starts_with($type->name, 'App\\Data\\')) { return parent::shouldHandle($type); } return false; } public function toSchema(Type $type): OpenApiType { /** @var ObjectType $type */ $schema = parent::toSchema($type); // Добавить описание ко всем схемам $schema->setDescription("Автогенерированная схема для {$type->name}"); return $schema; } }
Зарегистрируйте своё расширение:
// config/skiexx-data-scramble.php return [ 'auto_register' => false, ];
// AppServiceProvider.php Scramble::registerExtension(CustomDataExtension::class);
Подмена ValidationAttributeMap
Для добавления поддержки собственных атрибутов валидации или изменения маппинга существующих:
use Skiexx\LaravelDataScramble\Support\ValidationAttributeMap; use Dedoc\Scramble\Support\Generator\Types\Type as OpenApiType; use Spatie\LaravelData\Attributes\Validation\ValidationAttribute; // В вашем расширении или сервис-провайдере class ExtendedValidationMap extends ValidationAttributeMap { public static function apply(ValidationAttribute $attribute, OpenApiType $type): void { // Сначала применяем стандартные маппинги parent::apply($attribute, $type); // Добавляем кастомную обработку if ($attribute instanceof YourCustomAttribute) { $type->format('your-custom-format'); } } }
Собственный DataClassSchemaResolver
Для полного контроля над процессом генерации схемы:
use Skiexx\LaravelDataScramble\Resolvers\DataClassSchemaResolver; use Dedoc\Scramble\Support\Generator\Types\ObjectType as OpenApiObjectType; class CustomSchemaResolver extends DataClassSchemaResolver { // Переопределите нужные методы }
И используйте его в своем расширении:
class CustomDataExtension extends TypeToSchemaExtension { public function toSchema(Type $type): OpenApiType { $resolver = new CustomSchemaResolver($this->components); return $resolver->resolve($type->name); } }
Использование с несколькими API Scramble
Если вы используете несколько API-документаций в Scramble:
use Dedoc\Scramble\Scramble; use Skiexx\LaravelDataScramble\Extensions\LaravelDataTypeToSchemaExtension; // Расширение регистрируется глобально для всех API Scramble::registerExtension(LaravelDataTypeToSchemaExtension::class); // Настройка конкретного API Scramble::configure('v2') ->routes(fn (Route $route) => str_starts_with($route->uri, 'api/v2'));
Разработка
Docker-окружение
Проект включает Docker-конфигурацию для разработки без локальной установки PHP:
# Сборка контейнера podman compose build # Установка зависимостей podman compose run --rm app composer install # Запуск тестов podman compose run --rm app vendor/bin/pest # Проверка стиля кода podman compose run --rm app vendor/bin/pint # Интерактивная оболочка podman compose run --rm app bash
Стандарты кода
- PSR-12 через Laravel Pint
declare(strict_types=1)во всех PHP-файлах- Тесты на Pest
Архитектура
src/
├── LaravelDataScrambleServiceProvider.php -- Сервис-провайдер, регистрирует расширения
├── Attributes/
│ └── ResponseData.php -- Атрибут для описания ответов JsonResource
├── Extensions/
│ ├── LaravelDataTypeToSchemaExtension.php -- TypeToSchemaExtension для Data-классов
│ └── ResponseDataOperationExtension.php -- OperationExtension для #[ResponseData]
├── Extractors/
│ └── DataParametersExtractor.php -- ParameterExtractor для Data-классов
├── Contracts/
│ └── OpenApiSchema.php -- Интерфейс для ручного определения схемы
├── Resolvers/
│ ├── DataClassSchemaResolver.php -- Оркестратор: собирает ObjectType из свойств
│ ├── PropertyTypeResolver.php -- PHP-тип -> OpenAPI-тип
│ ├── ValidationConstraintResolver.php -- Атрибуты валидации -> OpenAPI-ограничения
│ └── NameMappingResolver.php -- Маппинг имен (input/output)
├── Traits/
│ ├── HasOpenApiSchema.php -- Реализация OpenApiSchema по умолчанию
│ └── Formats/ -- Трейты форматов (String, Uuid, Email...)
└── Support/
└── ValidationAttributeMap.php -- Карта маппинга атрибутов валидации
Как это работает
-
Scramble при генерации документации вызывает
shouldHandle()на каждом расширении для каждого типа, найденного в контроллерах. -
LaravelDataTypeToSchemaExtensionпроверяет, является ли тип наследникомBaseData(Data, Resource, Dto) или реализуетOpenApiSchema. -
DataClassSchemaResolverполучает метаданные класса черезDataConfig::getDataClass()и для каждого свойства:- Проверяет, не скрыто ли оно (
#[Hidden]) - Определяет имя через
NameMappingResolver(с учетом#[MapOutputName]) - Определяет тип через
PropertyTypeResolver(скаляры, даты, enum, вложенные Data) - Применяет ограничения через
ValidationConstraintResolver(Min, Max, Email и др.) - Обрабатывает nullable, lazy, computed, default
- Проверяет, не скрыто ли оно (
-
reference()создает$ref-ссылку черезClassBasedReference, чтобы вложенные объекты не дублировались inline, а были вынесены в#/components/schemas/. -
ResponseDataOperationExtensionработает на уровне операций (послеResponseExtension): читает#[ResponseData]атрибут с метода контроллера и заменяет response schema, которую Scramble сгенерировал для анонимногоJsonResource, на правильную схему с указанным Data-классом, оберткой и пагинацией.
Лицензия
MIT