Під час роботи над проєктом для Ticketmatic ми постійно стикалися з одним і тим самим сценарієм. Для інтеграції з Ticketmatic API ми використовуємо бібліотеку Saloon. Наш застосунок критично залежить від неї: фактично це SPA, де майже кожна взаємодія проходить через API. Якщо ви знайомі із Saloon, то знаєте, що це означає: окремий клас Request для кожного endpoint, а також DTO для парсингу даних відповіді та тіла запиту. Помножте це на десятки етапів — і ви отримаєте величезну кількість одноманітної роботи.
Існують консольні команди Artisan, як-от php artisan saloon:response, які покликані полегшити цей процес. Проте навіть вони не позбавляють від необхідності власноруч створювати DTO, додавати параметри, імпортувати класи та пов'язувати все це докупи. Значна частина роботи все одно залишається ручною та монотонною.
Як це було раніше: та сама робота, тільки швидше
Кожен новий endpoint вимагав чотирьох кроків. Ось як виглядає додавання простого виклику "get order":
1. Створення класу Params
Це невеликий DTO, який містить параметри запиту та мапить їх на ключі, які очікує API:
class GetOrderParams
{
public function __construct(
public int $orderId,
// лише одна властивість для прикладу
) {}
public function toTicketmaticParams(): array
{
return ['orderId' => $this->orderId];
}
}
2. Створення Resource DTO
Об'єкт spatie/laravel-data із фабричним методом, який конвертує lowercase-ключі з API у типізовані властивості в camelCase:
class Order extends Data
{
public function __construct(
public int $orderId,
public string $status,
// ...
) {}
public static function fromTicketmatic(array $data): self
{
return new self(
orderId: $data['orderid'],
status: $data['status'],
);
}
}
3. Створення класу Request
Request у Saloon, який поєднує все: endpoint, HTTP-метод, роботу із сесіями та трансформацію DTO:
class GetOrderRequest extends Request
{
use WithSession;
protected Method $method = Method::GET;
public function __construct(
protected GetOrderParams $params,
) {}
public function resolveEndpoint(): string
{
return "user/orders/{$this->params->orderId}";
}
public function createDtoFromResponse(Response $response): Order
{
return Order::fromTicketmatic($response->json());
}
}
Ми дотримуємося певних конвенцій: GET-запити розширюють Request безпосередньо, POST/PUT — TicketShopRequest (який додає HasJsonBody), а більшість запитів використовують trait WithSession для передачі заголовків сесії.
4. Додавання методу в Resource
public function get(GetOrderParams $params): ?Order
{
return $this->connector->send(new GetOrderRequest($params))
->dtoOrFail();
}
Це дає нам чистий ланцюжок викликів:
app(TicketShop::class)->orders()->get(new GetOrderParams(123));
У цьому немає нічого складного. Це просто нескінченне повторення одного й того самого шаблону.
Чому ми робили це вручну?
Спочатку я власноруч створював ці файли у відповідних папках, копіюючи існуючі Request і виправляючи namespaces та методи. Наприкінці 2025 року Claude став значно кращим і почав чітко виконувати інструкції. Я почав просити Claude генерувати файли запитів та ресурсів за мене. Це значно пришвидшило процес. Згодом з'явилися так звані "Claude Skills" — багаторазові набори інструкцій для AI-асистентів, які активуються автоматично залежно від контексту.
Як видно на відео, Claude ставить серію запитань про запит, який ми хочемо створити. Отримавши всю інформацію, він генерує файли у правильних директоріях, виправляє namespaces, додає властивості, обробляє обов’язкові та опціональні поля і навіть створює DTO. Ми також прописали у skill автоматичний запуск Laravel Pint для форматування коду.
Уявіть, що вам потрібно вручну додати 15 властивостей у DTO для тіла запиту, а потім ще 15 — для відповіді. Це марудна робота. Тепер ми просто копіюємо специфікації API у Claude, і він робить усе сам.
У 9 з 10 випадків усе ідеально: файли, властивості та namespaces. Проте перевірка згенерованого коду все одно необхідна. Завжди залишається той 1% помилок, зазвичай через нестачу контексту.
Інші скіли
Skill для Saloon — не єдиний у нашому проєкті. Іншим рутинним завданням було створення нових сторінок. Livewire має власні команди Artisan для створення компонентів, але в нашому коді ми робимо багато додаткових кроків. Наприклад, у нас є клас конфігурації Shop, де компоненти Livewire реєструються в масиві:
class Shop
{
// Alias => Component Class
protected array $components = [
// Global
ShopComponent::GlobalFlash->value => Flash::class,
ShopComponent::GlobalOptIns->value => OptIns::class,
//...
];
Кожен компонент мапиться на значення enum, яке слугує його аліасом:
enum ShopComponent: string
{
case GlobalFlash = 'tm.global.flash';
//...
}
Ви можете зауважити, що Livewire реєструє компоненти автоматично. Це правда, але ми створили клас Shop, оскільки цей проєкт розробляється як пакет, на базі якого будуть будуватися інші застосунки Laravel. Деяким із них може знадобитися вимкнути певні функції або замінити реалізацію на власну, і такий реєстр дає нам необхідну гнучкість.
Тож щоразу при додаванні компонента Livewire нам потрібно не лише створити файл, а й додати варіант у enum, зареєструвати його в класі Shop та підключити стандартні traits. Це ідеальна задача для Claude Skills. Ось приклад того, як ми створюємо сторінку логіну:
Ми отримуємо готовий компонент Livewire, шаблон Blade (у правильних папках!), необхідні методи та traits, а також реєстрацію в класі Shop.
Окрім генерації коду, ці скіли допомагають підтримувати єдність стилю. Наприклад, я створив review-pr skill, який перевіряє мої pull requests перед тим, як їх побачить мій колега Себ. Він виявляє дрібні помилки на кшталт неперекладених рядків або порушення конвенцій іменування.
Ці маленькі автоматизації усувають зайве тертя в щоденній роботі. Менше повторень, менше помилок і більше часу на справді важливі завдання.