Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

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 лише перевіряв права.

Докладніше в документації: Потокові завантаження