У Laravel-застосунку з Vue чи React одні й ті самі структури описуються двічі: PHP-класом на бекенді і TypeScript-типом на фронтенді. Ручна синхронізація рано чи пізно ламається - поле перейменували в PHP, а у фронтенді лишилося старе.
Генерація типів з PHP - пакет spatie/laravel-typescript-transformer:
use Spatie\TypeScriptTransformer\Attributes\TypeScript;
#[TypeScript]
final class VacancyData
{
public function __construct(
public int $id,
public string $title,
public ?int $salary,
public VacancyStatus $status,
) {}
}
#[TypeScript]
enum VacancyStatus: string
{
case Draft = 'draft';
case Published = 'published';
}
php artisan typescript:transform
Результат:
export type VacancyData = {
id: number;
title: string;
salary: number | null;
status: VacancyStatus;
};
export type VacancyStatus = 'draft' | 'published';
PHP-енуми стають об'єднаннями рядкових літералів, ?int - number | null, колекції з PHPDoc (array<int, TagData>) - масивами.
Зі spatie/laravel-data це особливо зручно: data-об'єкти одночасно є DTO, правилами валідації й ресурсами відповіді - і з них же генеруються типи.
Як вбудувати в процес:
- генерувати в CI і перевіряти, що згенерований файл не змінився (
git diff --exit-code) - так забута регенерація виявляється одразу; - або генерувати під час збирання фронтенду і не зберігати результат у Git;
- для маршрутів - Laravel Wayfinder генерує типізовані функції для контролерів і названих маршрутів.
Обмеження, про які варто пам'ятати:
- типи описують формат, але не перевіряють дані під час виконання. Генерація прибирає розбіжність у коді, але не захищає від старої версії бекенду в кеші чи помилки серіалізації;
- Eloquent-моделі перетворювати напряму погано: у відповідь потрапляє лише те, що віддає ресурс, а не всі атрибути. Генерувати варто з DTO чи ресурсів - того, що справді відправляється;
- формат JSON: дати,
decimal(рядок),snake_caseчиcamelCase- тип має відповідати серіалізованому вигляду.
Альтернатива на рівні всього API - OpenAPI-специфікація (наприклад, згенерована пакетом на кшталт Scramble) і генерація клієнта з неї.