Laravel: файли, сховища й зображення
20 питань · ~20 хв · Версія v3.0
Увійдіть, щоб продовжити
Диски й видимість, публічні й тимчасові URL, завантаження файлів і їхні імена, потокові відповіді, обробка зображень у Laravel 13, підміна сховища в тестах - питання всіх рівнів, від junior до lead.
- За спробу
- 20
- У пулі
- 41
- Проходжень
- 0
- Середній бал
- -
- Пройшли на 70%+
- -
Питання для підготовки
8 питаньКлючове правило - не тримати весь файл у пам'яті.
- Стрімінг замість читання цілком:
return Storage::disk('s3')->response($path); // стрім на скачування Storage::writeStream($path, fopen($source, 'r')); // стрім на запис - Direct uploads на S3 - клієнт вантажить напряму в сховище за pre-signed URL, минаючи PHP-процес (не блокує воркер, обходить ліміти
upload_max_filesize). - Chunked upload - великі файли частинами (resumable).
- Фонова обробка - конвертацію відео/зображень виносити в черги.
- Враховувати
max_execution_time, таймаути nginx і ліміти пам'яті воркера.
Файл приходить через $request->file(); зберігають через фасад Storage:
$request->validate([
'avatar' => ['required', 'image', 'max:2048'], // до 2 МБ
]);
$path = $request->file('avatar')->store('avatars', 'public');
// або з випадковим унікальним іменем - store() уже так робить
$user->update(['avatar_path' => $path]);
store()повертає шлях для збереження в БД.- Публічний URL -
Storage::url($path)(потребуєphp artisan storage:link). - Валідатори
image,mimes:pdf,docx,max:(в КБ) убезпечують від небажаних файлів.
Laravel абстрагує файлові операції через фасад Storage поверх Flysystem. «Диски» (local, public, s3) налаштовуються в config/filesystems.php.
Storage::disk('s3')->put('reports/q1.pdf', $contents);
$url = Storage::disk('s3')->url('reports/q1.pdf');
$temporary = Storage::disk('s3')->temporaryUrl('reports/q1.pdf', now()->addMinutes(5));
php artisan storage:linkстворює символічне посиланняpublic/storage → storage/app/publicдля публічного доступу.- Зміна сховища (локально ↔ S3) не вимагає переписування коду - лише конфіг.
Файл, покладений у 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()генерує безпечне імʼя сам.
Завантаження файлів - одна з найчастіших дір: файл може виявитися скриптом, перезаписати чужий файл чи покласти сервер розміром.
Валідація:
$request->validate([
'avatar' => ['required', File::image()->max('2mb')->dimensions(Rule::dimensions()->maxWidth(4000))],
'contract' => ['required', 'file', 'mimes:pdf', 'max:10240'],
]);
mimesвизначає тип за вмістом, а не за назвою;extensionsперевіряє розширення, яке дав користувач - корисно разом зmimes;max- у кілобайтах; ліміт розміру тіла запиту ще й на вебсервері.
Збереження:
- ім'я -
hashName()(випадкове) і розширення -extension()(за вмістом).getClientOriginalName()можна підробити, зокрема з../у шляху; - приватні документи - на приватний диск, віддавати через контролер з перевіркою прав чи
temporaryUrl(); - публічний диск ніколи не має виконувати PHP: файли віддає вебсервер як статику.
Обробка:
- зображення перекодувати (Laravel 13:
$request->image('avatar')->toWebp()) - нове кодування зазвичай відкидає вбудований у файл сторонній вміст і метадані на кшталт геолокації (результат варто перевірити для свого драйвера); - великі файли обробляти в черзі.
Оригінальне ім'я файлу, якщо воно потрібне людям, зберігають окремо в базі й екранують при виводі.
Спершу почитати
Прочитати - ще не значить знати
20 питань, по одному на екран, ~20 хв. Після завершення - розбір кожної помилки з посиланням на питання.