Коли однакова логіка формування відповіді повторюється в багатьох контролерах, є два інструменти, щоб винести її в одне місце.
1. Інтерфейс Responsable - об'єкт сам знає, як перетворитися на відповідь:
use Illuminate\Contracts\Support\Responsable;
final class CsvExport implements Responsable
{
public function __construct(
private iterable $rows,
private string $filename,
) {}
public function toResponse($request): StreamedResponse
{
return response()->streamDownload(function () {
$out = fopen('php://output', 'w');
foreach ($this->rows as $row) {
fputcsv($out, $row);
}
fclose($out);
}, $this->filename, ['Content-Type' => 'text/csv']);
}
}
public function export(): CsvExport
{
return new CsvExport(User::query()->cursor()->map->only('id', 'email'), 'users.csv');
}
Контролер повертає намір («ось експорт»), а не деталі HTTP. Так влаштовані API Resources, Uri, Inertia-відповіді.
Responsable з урахуванням формату запиту:
public function toResponse($request)
{
return $request->expectsJson()
? response()->json(['id' => $this->order->id], 201)
: to_route('orders.show', $this->order)->with('status', 'Замовлення створено');
}
Один контролер обслуговує і форму, і API.
2. Макроси відповідей - новий метод на фабриці response():
// AppServiceProvider::boot()
Response::macro('problem', function (string $title, int $status, array $extra = []) {
return Response::json(
['type' => 'about:blank', 'title' => $title, 'status' => $status, ...$extra],
$status,
['Content-Type' => 'application/problem+json'],
);
});
return response()->problem('Недостатньо коштів', 422, ['balance' => $balance]);
Що обрати:
Responsable |
Макрос | |
|---|---|---|
| де живе логіка | у власному класі | у сервіс-провайдері |
| тестування | клас тестується окремо | через HTTP-тест |
| автодоповнення в IDE | повне | потрібні PHPDoc чи ide-helper |
| для чого | складні відповіді з даними | короткі допоміжні методи формату |
Ризики:
- макроси глобальні й неявні: новий розробник не знайде, де визначено
response()->problem(), а статичний аналіз їх не бачить без додаткових анотацій; - конфлікт імен з майбутніми методами фреймворку чи пакетів;
- Responsable не повинен робити побічних дій (запис у базу, відправку листів) - він має лише формувати відповідь; інакше middleware чи тести, що створюють відповідь, викличуть побічні ефекти.
Альтернатива для помилок: власний виняток з методом render() часто кращий за макрос - його можна кинути з будь-якого рівня, а не лише повернути з контролера.