Laravel: Загрузка файлов с помощью FilePond

Источник: «Uploading Files in Laravel Using FilePond»
При создании веб-приложения часто требуется добавить функцию, позволяющую пользователям загружать файлы. Например, вы можете разрешить пользователям обновлять изображения своего профиля или обмениваться файлами друг с другом.

В этой статье мы рассмотрим, как использовать библиотеку JavaScript под названием FilePond для загрузки файлов в приложение Laravel. Мы также кратко обсудим альтернативные подходы к загрузке файлов в Laravel. Затем мы рассмотрим, как можно использовать FilePond для одновременной загрузки нескольких файлов и загрузки изображений (с предварительным просмотром изображения). Наконец, мы рассмотрим, как можно проверить загруженные файлы, как удалить временные файлы и как написать тесты для кода загрузки файлов.

Что такое FilePond

FilPond — это библиотека JavaScript, позволяющая загружать файлы в веб-приложения.

Он представляет простой, доступный и визуально привлекательный интерфейс для загрузки файлов. Это может дать отличную отправную точку для создания собственной функции загрузки файлов, не беспокоясь о стиле и доступности интерфейса.

FilePond позволяет загружать файлы синхронно или асинхронно. Это означает, что вы можете загружать файлы одним запросом во время отправки формы (синхронно) или загружать файлы отдельными запросами перед отправкой формы (асинхронно). Использование асинхронного подхода может обеспечить лучший пользовательский интерфейс, поскольку пользователи могут продолжать заполнять поля в форме во время выполнения загрузки. В этой статье мы сосредоточимся на асинхронном подходе к загрузке файлов.

Существует ряд плагинов, которые вы можете использовать с FilePond для расширения функционала. Например, вы можете использовать плагин FilePondPluginImagePreview для предварительного просмотра загружаемого изображения. Фактически мы рассмотрим этот плагин позже в этой статье.

FilePond также позволяет загружать файлы по частям. Это полезно, если вы хотите загрузить большие файлы, которые могут оказаться слишком большими для загрузки одним запросом. Однако в этом руководстве мы будем рассматривать загрузку файлов только в одном запросе. Если вам нужна дополнительная информация о том, как загружать файлы фрагментами, вы можете ознакомиться с документацией FilePond

Как в FilePond работает поток асинхронной загрузки файлов

Чтобы объяснить, как пользователь может асинхронно загрузить файл в форму с помощью FilePond, давайте рассмотрим пример. Представим, что пользователь обновляет профиль, используя форму, которая позволяет ему обновлять имя и изображение профиля. Предположим, что пользователь хочет загрузить файл avatar.png в качестве нового изображения профиля.

Поток может работать примерно так:

  1. Пользователь нажимает Browse в компоненте FilePond в форме.
  2. Появляется традиционное окно загрузки файла, где пользователь может выбрать файл avatar.png, который он хочет загрузить со своего устройства.
  3. Как только файл выбран, FilePond отправляет avatar.png на сервер в формате multipart/form-data используя запрос POST.
  4. Сервер (наше Laravel приложение) сохраняет файл во временном уникальном месте. Например, он может сохранить файл в tmp/12345abcdef/avatar.png.
  5. Сервер возвращает уникальное местоположение (в данном случае tmp/12345abcdef/avatar.png) в text/plain ответе обратно в FilePond.
  6. FilePond добавляет это уникальное место в поле hidden, в форме.
  7. Пока выполнялись шаги 3-6, пользователь мог продолжать заполнять остальную часть формы, во время загрузки файла. После завершения загрузки файла пользователь может отправить форму (которая теперь включает поле ввода hidden).
  8. Сервер (наше Laravel приложение) использует уникальное расположение для перемещения файла из места временного хранения в предполагаемое место хранения. Например, он может переместить файл из tmp/12345abcdef/avatar.png в avatars/user-1.png.

Теперь, когда у нас есть краткое представление о том, как работает асинхронная загрузка файлов, давайте рассмотрим её преимущества перед синхронной загрузкой файлов в формах.

Синхронная загрузка файлов блокирует пользовательский интерфейс

Как правило, в веб-приложении при использовании синхронного подхода для загрузки файлов пользователь может нажать поле загрузка файла в форме. Затем он может выбрать, какой файл хочет загрузить. После выбора файла, файл фактически не загружается на сервер, пока пользователь не отправит форму (в отличие от асинхронного подхода, который мы видели выше). Это означает, что файл загружается в одном запросе (с остальными полями формы) при отправке формы.

Использование этого синхронного подхода иногда может блокировать взаимодействие пользователя с пользовательским интерфейсом. Это особенно заметно, если файл большой и его загрузка занимает много времени, потому что в процессе загрузки у пользователя не будет обратной связи с тем, что происходит.

Это отличается от того, как работает асинхронная загрузка файлов. При асинхронном подходе файл уже был бы загружен на сервер (или находился бы в процессе загрузки) в отдельном запросе до отправки формы.

Проблемы с синхронной загрузкой файлов на бессерверных (serverless) платформах

Если вы запускаете своё приложение на бессерверной платформе, такой как AWS Lambda, синхронная загрузка файлов может быстро стать проблематичной. На момент написания этой статьи, согласно документации AWS Lambda, максимальный размер запроса составлял 6 МБ. Это означает, что вам необходимо убедиться, что размер данных в вашей форме (включая загружаемые файлы) не превышает этого ограничения.

Это означает, что вам необходимо использовать асинхронный подход для загрузки файлов, если вы собираетесь запускать свои приложения на бессерверной платформе. В зависимости от вашего приложения вы можете захотеть загрузить их прямо в ваш провайдер хранилища (например, AWS S3) из вашего браузера. В результате этого вы можете вообще избежать контакта файлов с вашим сервером. Это может быть не только более безопасным (поскольку вы избегаете обработки потенциально вредоносных файлов на вашем сервере), но также может быть более производительным (поскольку вам не нужно сначала загружать файлы на свой сервер), и позволяет избегать ограничения в 6 мегабайт.

Хотя общие принципы, описанные в этой статье, могут применены к загрузке файлов непосредственно в ваше хранилище, мы сосредоточимся сначала на загрузке файлов на ваш сервер, а затем на их перемещение в ваше хранилище. Однако если вы используете Laravel Vapor, вы можете ознакомиться с документацией, чтобы узнать больше о том, как загружать файлы непосредственно в корзину AWS S3.

Настройка FilePond на фронтэнде

Теперь, когда у нас есть представление о потоке асинхронной загрузки файлов, давайте посмотрим, как можно настроить FilePond во внешнем интерфейсе нашего Laravel приложения.

FilePond представляет несколько адаптеров, которые можно использовать с разными платформами, такими как Vue, React и Angular. Однако в этой статье мы будем использовать обычный адаптер JavaScript.

Предположим мы работаем над свежей версией Laravel, использующей Vite для компиляции ресурсов.

Возьмём базовый пример. Мы собираемся представить, что мы создаём функцию импорта CSV, позволяющую пользователю загружать CSV-файл, содержащий сведения о продукте, который будет создан в нашем веб-приложении.

Для начала давайте создадим очень просто представление Blade, содержащую форму с одним полем file:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">

<title>FilePond Tutorial</title>

<meta name="csrf-token" content="{{ csrf_token() }}">

@vite('resources/js/app.js')
</head>

<body>
<form action="{{ route('products.import') }}" method="POST">
@csrf

<input type="file" name="csv" class="filepond"/>

<button type="submit">Import Products</button>
</form>
</body>
</html>

Теперь давайте установим FilePond через NPM, выполнив следующую команду:

npm i filepond --save

Затем мы можем открыть файл resources/js/app.js и добавить функционал для подключения FilePond в поле ввода:

import * as FilePond from 'filepond';
import 'filepond/dist/filepond.min.css';

const inputElement = document.querySelector('input[type="file"].filepond');

const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');

FilePond.create(inputElement).setOptions({
server: {
process: './uploads/process',
headers: {
'X-CSRF-TOKEN': csrfToken,
}
}
});

Давайте быстро взглянем на то, что делается в приведённом выше коде. Во-первых, мы импортируем FilePond файлы JavaScript и CSS, которые обеспечивают необходимые нам функциональные возможности и стили.

Затем мы переходим к поиску поля ввода, которое хотим преобразовать в поле FilePond. Обратите внимание, как мы добавили класс filepond в селектор запросов. Это сделано для того, чтобы мы могли различать поля ввода, которые мы хотим преобразовать в поля FilePond. И те, которые нам не нужны.

Затем мы получили токен CSRF из мета тега, добавленного в представление Blade. Это сделано для того, чтобы мы могли передать его в FilePond и его можно было отправить на наш сервер при попытке загрузить файл. Без добавления CSRF вы будете получать ответ об ошибке HTTP 419 всякий раз, когда будете пытаться загрузить файл.

Далее мы создали наш экземпляр FilePond и указали, что когда мы хотим загрузить новый файл, он должен быть отправлен на URL-адрес ./uploads/process на нашем сервере. FilePond также предоставляет возможность указать URL-адрес для удаления временно загруженных файлов, но мы не будем использовать эту функцию.

Фронтенд готов к использованию. Если пользователь выберет CSV-файл, он будет отправлен на URL-адрес /uploads/process и временно сохранён. Затем скрытое поле csv в форме, будет заполнено путём к файлу, в который мы временно сохранили файл.

Настройка FilePond на бэкэнде

Теперь мы можем настроить серверную часть нашего Laravel приложения для обработки загрузок файлов, поступающих из FilePond. Для этого нужно создать маршрут и контроллер, отвечающие за хранение загруженных файлов.

Как я упоминал ранее, FilePond предоставляет возможность загружать файлы фрагментами. Но для этого руководства мы не будем усложнять задачу и рассмотрим загрузку файлов только в одном запросе.

Начнём с создания нового FileUploadController, выполнив следующую команду:

php artisan make:controller FileUploadController

Добавим в контроллер метод process(), для обработки загрузки и сохранения файла в каталог tmp в хранилище:

declare(strict_types=1);

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Str;

final class FileUploadController extends Controller
{
public function process(Request $request): string
{
// Мы не знаем имя переданного файла, поэтому нам нужно захватить
// все файлы из запроса и взять первый файл.
/** @var UploadedFile[] $files */
$files = $request->allFiles();

if (empty($files)) {
abort(422, 'No files were uploaded.');
}

if (count($files) > 1) {
abort(422, 'Only 1 file can be uploaded at a time.');
}
// Теперь, когда мы знаем, что есть только один ключ, мы можем
// использовать его для получения файла из запроса.
$requestKey = array_key_first($files);

// Если мы разрешаем загрузку нескольких файлов, поле запроса будет
// представлять собой массив с одним файлов, а не один файл (например,
// `csv[]`, а не `csv`). итак, нам нужно получить первый файл из массива.
// В противном случае мы можем предположить, что загруженный файл
// предназначен для ввода одного файла, и мы можем его непосредственно
// из запроса.
$file = is_array($request->input($requestKey))
? $request->file($requestKey)[0]
: $request->file($requestKey);

// Сохраняем файл во временной локации и возвращаем её для использования
// FilePond
return $file->store(
path: 'tmp/'.now()->timestamp.'-'.Str::random(20)
);
}
}

Возможно, вы, заметили, что мы также добавили поддержку полей формы, которые допускают загрузку нескольких файлов. Позже мы расскажем, как настроить FilePond во фронтенде для поддержки загрузки нескольких файлов.

Если пользователь загружает файл в этот контроллер, будет возвращена строка, подобная следующей:

tmp/1678198256-88eXsQV7XB2RU5zXdw0S/9A4eK5mRLAtayW78jhRo3Lc3WdSSrsihpVHhMvzr.png

Зарегистрируем маршрут uploads/process в файле web.php следующим образом:

use App\Http\Controllers\FileUploadController;
use Illuminate\Support\Facades\Route;

Route::post('uploads/process', [FileUploadController::class, 'process'])->name('uploads.process');

Ваше приложение должно успешно загружать файлы и сохранять их во временном каталоге.

Доступ к загруженному файлу в контроллере

Когда мы настроили FilePond во фронтэнде и добавили функциональность временного хранения файлов в бэкенде. Мы можем посмотреть, как получить доступ к загруженным файлам в контроллерах при отправке формы.

Начнём с создания нового контроллера, отвечающего за импорт продукции из CSV файла. Мы можем сделать это выполнив следующую команду:

php artisan make:controller ImportProductController -i

Обновим ImportProductController для обработки импорта файлов:

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Services\ProductImportService;
use Illuminate\Http\File;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

final class ImportProductController extends Controller
{
public function __invoke(
Request $request,
ProductImportService $productImportService
): RedirectResponse {
$validated = $request->validate([
'csv' => 'required|string',
]);

// Копируем файл из временной локации в постоянную.
$fileLocation = Storage::putFile(
path: 'imports',
file: new File(Storage::path($validated['csv']))
);

$productImportService->import(
csvLocation: $fileLocation
);

return redirect()
->route('products.index')
->with('success', 'Products imported successfully');
}
}

Давайте рассмотрим, что делается в приведённом выше примере контроллера.

Во-первых, мы добавили подсказку типа для класса ProductImportService, чтобы он был разрешён из сервис контейнера для использования в методе нашего контроллера. Мы не будем рассматривать этот класс в этой статье, но мы можем предположить, что он отвечает за импорт продукции из CSV-файла.

Также мы проверяем, содержит ли запрос строковое поле csv. Позже мы рассмотрим, как можно улучшить эту проверку.

После этого мы копируем файл из его временной локации в постоянную, чтобы можно было передать его объекту ProductImportService.

Когда всё это было сделано, мы возвращаем ответ о перенаправлении на страницу индекса продуктов с сообщением об успешном завершении.

Зарегистрируем маршрут для ImportProductController в web.php:

use App\Http\Controllers\ImportProductController;

Route::post('products/import', ImportProductController::class)->name('products.import');

Загрузка изображений

FilePond предоставляет очень удобный плагин FilePondPluginImagePreview, позволяющий показывать предварительный просмотр изображения, которое пользователь выбрал для загрузки. Он также предоставляет пользователю некую обратную связь о файле, который он выбрал для загрузки, чтобы он мог подтвердить правильность выбора.

Чтобы использовать плагин FilePondPluginImagePreview, его нужно установить через npm, выполнив следующую команду:

npm i filepond-plugin-image-preview --save

После установки добавить следующие строки импорта в app.js:

import FilePondPluginImagePreview from 'filepond-plugin-image-preview';
import 'filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css';

Затем нужно зарегистрировать плагин в FilePond с помощью метода registerPlugin:

FilePond.registerPlugin(FilePondPluginImagePreview);

После всех этих операций, ваш код будет выглядеть примерно так:

import * as FilePond from 'filepond';
import 'filepond/dist/filepond.min.css';
import FilePondPluginImagePreview from 'filepond-plugin-image-preview';
import 'filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css';

const inputElement = document.querySelector('input[type="file"].filepond');

const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');

FilePond.registerPlugin(FilePondPluginImagePreview);

FilePond.create(inputElement).setOptions({
server: {
process: './uploads/process',
headers: {
'X-CSRF-TOKEN': csrfToken,
}
},
allowMultiple: true,
});

Вот и всё! Теперь у вас должен быть работающий компонент FilePond, позволяющий быстро загружать и просматривать изображения.

Загрузка нескольких файлов

Могут быть случаи, когда вы хотите загрузить несколько файлов одновременно в одной форме. Например, вы можете загрузить несколько изображений для одного продукта.

Для этого можно добавить атрибут multiple к элементу input:

<input type="file" name="csv[]" class="filepond" multiple/>

Мы можем передать allowMultiple: true методу setOptions:

FilePond.create(inputElement).setOptions({
server: {
process: './uploads/process',
fetch: null,
revert: null,
headers: {
'X-CSRF-TOKEN': csrfToken,
}
},
allowMultiple: true,
});

Вот и всё! Мы уже убедились, что FileUploadController может обрабатывать несколько файлов, поэтому не нужно вносить какие-либо изменения.

Если пользователь попытается загрузить два файла, на сервер будут отправлены два отдельных запроса на сохранение файлов. Затем в форму будут добавлены два скрытых поля csv[] с именами загруженных файлов.

Обратите внимание, что нам нужно использовать csv[] вместо csv. Это связано с тем, что если бы мы использовали csv, то могли бы отправлять только один путь к файлу каждый раз при отправке формы. Используя csv[], мы можем отправить несколько путей к файлам, к которым можно будет получить доступ в контроллере в виде массива строк.

Двигаемся дальше

Теперь, когда мы рассмотрели, как мы можем загружать файлы в наши Laravel приложения с помощью FilePond, давайте взглянем на некоторые другие вещи, которые вы, вероятно захотите сделать.

Валидация

FilePod предоставляет несколько хелперов, которые вы можете использовать добавить к компоненту загрузки файлов для валидации, например data-max-file-size. Вы можете добавить эти хелперы в элемент input следующим образом:

<input type="file" name="csv" class="filepond" data-max-file-size="3MB"/>

Важно помнить, что проверка на стороне клиента в основном предназначена для UI/UX целей, а не для обеспечения безопасности. Всегда проверяйте данные на стороне сервера, чтобы убедиться, что они валидные.

По этой причине очень важно проверять файл после отправки формы, до его обработки.

Например, давайте представим, что мы предоставляем пользователю возможность обновить изображение своего профиля. Вы бы не хотели, чтобы это поле принимало CSV-файл. Вместо этого мы хотели бы убедиться, что файл является изображением.

Итак, давайте посмотрим, как написать правило проверки, чтобы убедиться, что загруженный файл валиден. Начнём с создания нового правила валидации, выполнив следующую команду:

php artisan make:rule ValidFileUpload

Обновим правило ValidFileUpload, чтобы оно выглядело так:

declare(strict_types=1);

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Storage;

final class ValidFileUpload implements ValidationRule
{
public function __construct(
private readonly array $validMimeTypes
) {
//
}

public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (!Storage::exists($value)) {
$fail('The file does not exist.');
}

if (!in_array(Storage::mimeType($value), $this->validMimeTypes, true)) {
$fail('The file is not a valid mime type.');
}
}
}

В классе ValidFileUpload определили конструктор, принимающий массив допустимых mime типов.

В методе validate() добавили две проверки:

  1. Проверка, что файл существует.
  2. Проверка, что mime тип файла содержится в массиве валидных mime типов.

Можно использовать это правило валидации следующим образом:

use App\Rules\ValidFileUpload;

$validated = $request->validate([
'csv' => ['required', 'string', new ValidFileUpload(['text/csv'])],
]);

Можно расширить валидацию и добавить дополнительные проверки, такие как проверка того, что размер файла не превышает определённого размера.

Очистка временных файлов

Со временем в каталоге tmp может накопиться большое количество временных файлов. Можно написать команду Artisan, которую можно добавить в регулярно выполняемые задачи, для удаления из каталога tmp файлов старше определённого периода времени.

Давайте посмотрим, как это делается. Начнём с создания новой команды DeleteTempUploadedFiles, выполнив следующую команду:

php artisan make:command DeleteTempUploadedFiles

Обновим команду DeleteTempUploadedFiles, чтобы она выглядела примерно так:

declare(strict_types=1);

namespace App\Console\Commands;

use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;

final class DeleteTempUploadedFiles extends Command
{
protected $signature = 'app:delete-temp-uploaded-files';

protected $description = 'Delete temporary uploaded files older than 24 hours.';

public function handle(): void
{
foreach (Storage::directories('tmp') as $directory) {
$directoryLastModified = Carbon::createFromTimestamp(Storage::lastModified($directory));

if (now()->diffInHours($directoryLastModified) > 24) {
Storage::deleteDirectory($directory);
}
}
}
}

В приведённой выше команде мы перебираем все каталоги в папке tmp вашего хранилища и проверяем, старше ли каталог 24 часов. Если это так, мы его удаляем.

Теперь мы можем запланировать запуск этой команды каждый час, добавив её в метод schedule в файле app/Console/Kernel.php:

namespace App\Console;

use App\Console\Commands\DeleteTempUploadedFiles;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
/**
* Define the application's command schedule.
*/

protected function schedule(Schedule $schedule): void
{
$schedule->command(DeleteTempUploadedFiles::class)->hourly();
}

// ...
}

Предполагая, что у вас запущен планировщик вашего приложения. Это означает, что каждый час ваше приложение будет удалять временные все каталоги старше 24 часов. Это означает, что ваш каталог tmp должен содержать только файлы, которые были недавно использованы или используются в данный момент.

В зависимости от вашего приложения вы можете изменить продолжительность существования каталога или частоту их удаления.

Тестирование кода

Если вы читали какие-либо из моих статей, то знаете, что я большой поклонник тестирования. Важно, чтобы для вашего кода были написаны тесты, особенно если вы собираетесь использовать его продакшене. Это даёт уверенность, что код работает правильно, и упрощает внесение изменений в будущем.

Давайте посмотрим, как можно написать базовые тесты для функционала загрузки файлов в контроллере FileUploadController. На высоком уровне мы хотим проверить, что:

  1. Файл может быть сохранён в каталоге tmp, если поле формы поддерживает загрузку одного файла.
  2. Файл может быть сохранён в каталоге tmp, если поле формы поддерживает загрузку нескольких файлов.
  3. Если файл не передался в запросе, возвращается ошибка.
  4. Ошибка возвращается, если в запросе передаётся более одного файла.

Мы могли бы написать несколько базовых тестов для покрытия этих сценариев, например:

declare(strict_types=1);

namespace Tests\Feature\Controllers;

use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Tests\TestCase;

final class FileUploadControllerTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();

// Используем фейковое хранилище, чтобы не хранить файлы на реальном диске.
Storage::fake();

// Заморозьте время и определите, как должен работать `Str::random`. Это
// позволит явно проверить, что файл храниться в правильном месте и правильно
// назван
$this->freezeTime();
Str::createRandomStringsUsing(static fn (): string => 'random-string');
}

/** @test */
public function file_can_be_temporarily_uploaded_for_a_single_file_field(): void
{
$file = UploadedFile::fake()->image('avatar.png');

$expectedFilePath = 'tmp/'.now()->timestamp.'-random-string';

$this->post(route('uploads.process'), [
'avatar' => $file,
])
->assertOk()
->assertSee($expectedFilePath);

Storage::assertExists($expectedFilePath);
}

/** @test */
public function file_can_be_temporarily_uploaded_for_a_multiple_file_field(): void
{
$file = UploadedFile::fake()->image('avatar.png');

$expectedFilePath = 'tmp/'.now()->timestamp.'-random-string';

$this->post(route('uploads.process'), [
'avatar' => [
$file
],
])
->assertOk()
->assertSee($expectedFilePath);

Storage::assertExists($expectedFilePath);
}

/** @test */
public function error_is_returned_if_no_file_is_passed_in_the_request(): void
{
$this->post(route('uploads.process'))
->assertStatus(422);
}

/** @test */
public function error_is_returned_if_more_than_one_file_is_passed_in_the_request(): void
{
$file = UploadedFile::fake()->image('avatar.png');

$this->post(route('uploads.process'), [
'avatar' => $file,
'invalid' => $file,
])
->assertStatus(422);
}
}

Хотя эти тесты довольно простые, они должны стать хорошей отправной точкой для написания собственных тестов для функционала загрузки файлов. Вы можете расширить эти тесты, для проверки правильное ли значение об ошибке возвращается. Или вы можете проверять, что только определённым пользователям разрешено загружать файлы, если добавите аутентификацию и авторизацию в свой поток загрузки файлов.

Вы также можете добавить тесты для своих правил валидации. Это может помочь с большей уверенностью добавлять больше правил валидации, если вы решите сделать её более строгой.

Заключение

В этой статье мы рассмотрели, как использовать FilePond для асинхронной загрузки файлов в Laravel приложении. Мы также рассмотрели, как можно удалить временные файлы, проверить загруженные файлы и написать тесты, чтобы убедиться, что загрузка работает.

Надеюсь, теперь вы чувствуете себя более уверенно, чтобы реализовать тот же подход в собственных Laravel проектах, добавить функциональность загрузки в свои приложения.

Дополнительные материалы

Предыдущая Статья

JavaScript: Руководство по async/await, с примерами

Следующая Статья

JavaScript: Функциона­льное Выражение vs. Объявление Функции