Senior: питання на співбесіді з теми «Файли»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
2 питання
Файл, покладений у storage/app, недоступний ззовні - це правильно за замовчуванням. Питання в тому, як віддати його тому, кому можна.
Що робити не варто: класти приватні файли в public/ чи публічний бакет і покладатися на те, що адресу ніхто не вгадає. Посилання розходяться, індексуються й лишаються робочими назавжди.
Спосіб 1 - віддати через контролер із перевіркою прав:
public function download(Document $document)
{
$this->authorize('view', $document);
return Storage::disk('private')->download($document->path, $document->name);
}
Кожне звернення проходить авторизацію. Мінус - файл іде крізь PHP, тож на великих файлах воркер зайнятий увесь час передачі.
Спосіб 2 - тимчасове підписане посилання до S3-сумісного сховища:
$url = Storage::disk('s3')->temporaryUrl($document->path, now()->addMinutes(5));
Посилання підписане й саме перестає працювати після строку. Файл віддає сховище напряму, PHP у передачі не бере участі - це те, що потрібно для великих файлів.
Спосіб 3 - підписаний маршрут, коли сховище тимчасових URL не вміє:
URL::temporarySignedRoute('documents.download', now()->addMinutes(5), ['document' => $document->id]);
Middleware signed перевіряє підпис і строк.
Про що часто забувають:
- Спосіб 1 без
authorize()перетворюється на публічний доступ через власний контролер. - Строк життя посилання має бути коротким: доки воно живе, ним може скористатися будь-хто, кому воно потрапило.
- Імʼя файла від користувача не можна підставляти в шлях -
Storage::putFile()генерує безпечне імʼя сам.
Головне правило - не тримати файл цілком у пам'яті PHP. Storage::get() на файлі в 2 ГБ упреться в memory_limit.
Запис потоком:
Storage::disk('s3')->putFile('videos', $request->file('video')); // потоком, а не get/put
Storage::disk('s3')->writeStream('backup.sql.gz', fopen($path, 'r'));
putFile() і putFileAs() передають файл потоком самі.
Читання потоком:
$stream = Storage::disk('s3')->readStream('exports/big.csv');
while (($line = fgets($stream)) !== false) { /* ... */ }
Віддача користувачу:
// згенерований на льоту файл - без запису на диск
return response()->streamDownload(function () {
$out = fopen('php://output', 'w');
Order::query()->lazyById(1000)->each(fn ($o) => fputcsv($out, [$o->id, $o->total]));
fclose($out);
}, 'orders.csv');
// готовий файл у S3 - краще посилання, ніж проксі через PHP
return redirect(Storage::disk('s3')->temporaryUrl($path, now()->plus(minutes: 5)));
Що ще допомагає:
- важку обробку (конвертація відео, архівування) - у чергу, з окремим воркером і більшим таймаутом;
- завантаження великих файлів від клієнтів - напряму в сховище (
temporaryUploadUrl()); - для
localу Nginx -X-Accel-Redirect, щоб файл віддавав вебсервер, а PHP лише перевіряв права.