Spatie Laravel Data вместо FormRequest и Resource
Структура
kernel/
└── Data/
└── IdResult.php
modules/
└── Auto/
├── Services/
│ ├── Data/
│ │ └── CreateAutoData.php
│ └── CreateAutoHandler.php
├── Console/
│ └── CreateAutoCommand.php
└── Http/
└── Controllers/
└── CreateAutoAction.php
DTO для создания автомобиля с валидацией php-аттрибутами для request'а
<?php
class CreateAutoData extends Data
{
public function __construct(
#[Min(8)]#[Max(9)] // пример HTTP-guard, бизнес-правила номера будут в ValueObject.
public readonly string $number,
public readonly bool $isSelf,
#[RequiredIf('isSelf', true)]
public readonly ?string $description,
) {}
}
Обработчик создания автомобиля, на вход DTO
<?php
class CreateAutoHandler
{
public function handle(CreateAutoData $data): IdResult
{
// Тут должна быть write через доменную модель
// c валидацией номера в ValueObject
$auto = new Auto();
$auto->number = $data->number; // типизированные свойства
$auto->is_self = $data->isSelf;
$auto->description = $data->description;
$auto->save();
return new IdResult($auto->id);
}
}
Invokable-контроллер, на вход DTO, автоматическая валидация request'а с выбросом 422 ошибки, IdResult автоматически переводится в JsonResponse
<?php
class CreateAutoAction
{
public function __construct(private CreateAutoHandler $createAutoHandler) {}
public function __invoke(CreateAutoData $data): IdResult
{
return $this->createAutoHandler->handle($data);
}
}
Если вернуть Data в POST запросе, код будет 201 автоматически
<?php
class IdResult extends Data
{
public function __construct(
public readonly string $id
) {}
}
Создание из консольной команды, тот же DTO
<?php
class CreateAutoCommand extends Command
{
protected $signature = 'auto:create {number} {--self} {--description=}';
protected $description = 'Create Auto';
public function handle(CreateAutoHandler $createAutoHandler): int
{
$data = new CreateAutoData(
number: (string) $this->argument('number'),
isSelf: (bool) $this->option('self'),
description: $this->option('description'),
);
$result = $createAutoHandler->handle($data);
$this->info("Created. ID: {$result->id}");
return self::SUCCESS;
}
}Например, если бы мы возвращали большую структуру данных. Если вернуть Data в GET/PUT/PATCH запросе, код будет 200 автоматически
<?php
class AutoData extends Data
{
public function __construct(
public readonly string $id,
public readonly string $number,
public readonly bool $isSelf,
public readonly ?string $description,
) {}
}
class GetAutoQuery
{
public function handle(string $id): AutoData
{
$auto = Auto::findOrFail($id);
// Понятное и безошибочное заполнение объекта
// именоваными параметрами конструктора
return new AutoData(
id: $auto->id,
number: $auto->number,
isSelf: $auto->is_self,
description: $auto->description,
);
}
}
class GetAutoAction
{
public function __construct(private GetAutoQuery $getAutoQuery) {}
public function __invoke(string $id): AutoData
{
return $this->getAutoQuery->handle($id);
}
}- Ни одного массива
- Типизация, везде объекты
- Нет текстовых ключей и значений
- Одно DTO в request и консоли
- Явная сборка ответа типизированными параметрами конструктора