• Czas czytania ~22 min
  • 10.03.2023

Podczas tworzenia aplikacji internetowej często trzeba dodać funkcję, która umożliwia użytkownikom przekazywanie plików. Można na przykład zezwolić użytkownikom na aktualizowanie obrazów profilowych lub udostępnianie sobie plików.

W tym artykule przyjrzymy się, jak używać biblioteki JavaScript o nazwie "FilePond" do przesyłania plików w aplikacjach Laravel. Omówimy również pokrótce alternatywne podejścia do przesyłania plików w Laravel. Następnie przyjrzymy się, jak możesz użyć FilePond do przesyłania wielu plików jednocześnie i przesyłania obrazów (z podglądem obrazu). Na koniec przyjrzymy się, jak zweryfikować przesłane pliki, jak usunąć pliki tymczasowe i jak napisać testy dla kodu przesyłania plików.

Czym jest FilePond?

FilePond to biblioteka JavaScript, która umożliwia przesyłanie plików w aplikacjach internetowych.

Zapewnia prosty, dostępny i atrakcyjny wizualnie interfejs do przesyłania plików. Może to stanowić świetny punkt wyjścia do tworzenia własnych funkcji przesyłania plików bez konieczności zbytniego martwienia się o styl i dostępność interfejsu.

FilePond umożliwia przesyłanie synchroniczne lub asynchroniczne. Oznacza to, że możesz przekazać pliki w jednym żądaniu w momencie przesyłania formularza (synchronicznie) lub możesz przesłać pliki w osobnych żądaniach przed przesłaniem formularza (asynchroniczny). Korzystanie z podejścia asynchronicznego może zazwyczaj zapewnić lepsze wrażenia użytkownika, ponieważ użytkownicy mogą nadal wypełniać inne pola w formularzu podczas przesyłania plików. Na potrzeby tego artykułu skupimy się na podejściu asynchronicznym.

Istnieje również wiele wtyczek, których można użyć z FilePond, aby dodać dodatkowe funkcje. Na przykład możesz użyć wtyczki FilePondPluginImagePreview, aby wyświetlić podgląd przesyłanego obrazu. W rzeczywistości przyjrzymy się tej wtyczce w dalszej części tego artykułu.

FilePond zapewnia również możliwość przesyłania plików w kawałkach. Jest to przydatne, jeśli chcesz przesłać większe pliki, które mogą być zbyt duże, aby przesłać je w jednym żądaniu. Jednak na potrzeby tego samouczka przyjrzymy się przesyłaniu plików tylko w jednym żądaniu. Jeśli chcesz uzyskać więcej informacji na temat przesyłania plików we fragmentach, możesz zapoznać się z dokumentacją < href = "https://pqina.nl/filepond/docs/api/server/#process-chunks" >FilePond.

Jak działa przepływ asynchronicznego przesyłania plików FilePond?

Aby wyjaśnić, w jaki sposób użytkownik może przesłać plik w formularzu asynchronicznie za pomocą FilePond, rzućmy okiem na przykład. Wyobraźmy sobie, że użytkownik aktualizuje swój profil za pomocą formularza, który pozwala mu zaktualizować swoje imię i nazwisko oraz zdjęcie profilowe. Zakładamy, że użytkownik chce przesłać plik .png awatara jako nowe zdjęcie profilowe.

Przepływ może działać mniej więcej tak:

  1. Użytkownik klika "Przeglądaj" w komponencie FilePond w formularzu.
  2. Zostanie wyświetlone tradycyjne okno dialogowe przesyłania pliku, w którym użytkownik może wybrać plik awatara.png, który chce przesłać ze swojego urządzenia.
  3. Po wybraniu pliku FilePond wysyła plik awatara .png do serwera jako multipart/form-data za pomocą żądania POST.
  4. Następnie serwer (nasza aplikacja Laravel) zapisuje plik w tymczasowej, unikalnej lokalizacji. Na przykład plik może zapisać w formacie tmp/12345abcdef/avatar.png.
  5. Następnie serwer zwraca unikalną lokalizację (w tym przypadku 12345abcdef/avatar.png) w odpowiedzi tekstowej / zwykłej z powrotem do FilePond.
  6. FilePond dodaje tę unikalną lokalizację w ukrytym polu wprowadzania danych w formularzu.
  7. Podczas wykonywania kroków 3-6 użytkownik mógł kontynuować wypełnianie reszty formularza podczas przesyłania pliku. Po zakończeniu przesyłania pliku użytkownik może przesłać formularz (który zawiera teraz ukryte pole wprowadzania).
  8. Serwer (nasza aplikacja Laravel) używa unikalnej lokalizacji, aby przenieść plik z tymczasowej lokalizacji przechowywania do zamierzonej lokalizacji. Na przykład plik może zostać przeniesiony z tmp/12345abcdef/avatar.png do avatars/user-1.png.

Teraz, gdy mamy już krótkie pojęcie o tym, jak działa asynchroniczne przekazywanie plików, przyjrzyjmy się ich zaletom w porównaniu z synchronicznym przekazywaniem plików w formularzach.

Synchroniczne przekazywanie plików blokuje interfejs użytkownika

Zazwyczaj w aplikacji internetowej podczas korzystania z synchronicznego podejścia do przesyłania plików użytkownik może kliknąć pole "przesyłanie plików" w formularzu. Następnie mogą wybrać plik, który chcą przesłać. Po wybraniu pliku plik nie jest faktycznie przesyłany na serwer, dopóki użytkownik nie prześle formularza (w przeciwieństwie do podejścia asynchronicznego, które widzieliśmy powyżej). Oznacza to, że plik jest przesyłany w jednym żądaniu (wraz z pozostałymi polami formularza) podczas przesyłania formularza.

Korzystanie z tego synchronicznego podejścia może czasami blokować użytkownikowi interakcję z interfejsem użytkownika. Jest to szczególnie ważne, jeśli plik jest duży i przesyłanie zajmuje dużo czasu, ponieważ użytkownik nie będzie miał zbyt wielu informacji zwrotnych na temat tego, co się dzieje.

Różni się to od sposobu działania asynchronicznego przekazywania plików. W podejściu asynchronicznym plik zostałby już przesłany na serwer (lub byłby w trakcie przesyłania) w oddzielnym żądaniu przed przesłaniem formularza.

Synchroniczne problemy z przesyłaniem plików na platformach bezserwerowych

Jeśli uruchamiasz aplikację na platformie bezserwerowej, takiej jak AWS Lambda, synchroniczne przekazywanie plików może szybko stać się problematyczne. W chwili pisania tego artykułu, zgodnie z dokumentacją AWS Lambda, maksymalny rozmiar żądania wynosi 6 MB. Oznacza to, że musisz upewnić się, że rozmiar danych w formularzu (w tym przesłanych plików) nie przekracza tego limitu.

Oznacza to, że należy przyjąć asynchroniczne podejście do przekazywania plików, jeśli aplikacje mają być uruchamiane na platformie bezserwerowej. W zależności od aplikacji możesz przesłać je bezpośrednio do dostawcy pamięci masowej (takiego jak AWS S3) z przeglądarki. W rezultacie oznacza to, że możesz w ogóle uniknąć dotykania serwera przez pliki. Może to być nie tylko bezpieczniejsze (ponieważ pozwala uniknąć przetwarzania potencjalnie złośliwych plików na serwerze), ale może być również bardziej wydajne (ponieważ nie trzeba najpierw przesyłać plików na serwer) i pozwala uniknąć limitu 6 MB.

Chociaż ogólne zasady omówione w tym artykule można zastosować do przesyłania plików bezpośrednio do dostawcy pamięci masowej, skupimy się najpierw na przesyłaniu plików na serwer, a następnie na przesyłaniu ich do dostawcy pamięci masowej. Jeśli jednak używasz Laravel Vapor, możesz sprawdzić dokumentację < href="https://docs.vapor.build/1.0/resources/storage.html#file-uploads>", aby dowiedzieć się więcej o tym, jak przesyłać pliki bezpośrednio do zasobnika AWS S3. Konfigurowanie FilePond na froncie

 

Teraz, gdy rozumiemy asynchroniczny przepływ przesyłania plików, rzućmy okiem na to, jak możemy skonfigurować FilePond na froncie naszej aplikacji Laravel.

FilePond zapewnia kilka adapterów, których można używać z różnymi frameworkami, takimi jak Vue, React i Angular. Jednak w tym artykule użyjemy tylko waniliowego adaptera JavaScript.

Zakładamy, że pracujemy nad świeżą instalacją Laravel, która używa Vite do kompilacji zasobów.

Weźmy prosty przykład. Wyobraźmy sobie, że tworzymy funkcję importu CSV, która pozwala użytkownikom przesyłać plik CSV zawierający szczegóły produktu, który zostanie utworzony w naszej aplikacji

internetowej.Na początek zróbmy bardzo prosty widok Blade, który zawiera formularz z pojedynczym polem wejściowym "plik":

<!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>

Teraz zainstalujmy FilePond przez NPM, uruchamiając następujące polecenie:

npm i filepond --save

Następnie możemy otworzyć nasz plik resources / js / app.js i dodać funkcjonalność, aby włączyć FilePond w naszym polu wejściowym:

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,
        }
    }
});

Rzućmy okiem na to, co jest robione w powyższym kodzie. Najpierw importujemy pliki JavaScript i CSS FilePond, które zapewniają funkcjonalność i style, których potrzebujemy.

Następnie przeszliśmy do znalezienia pola wejściowego, które chcemy przekonwertować na pole FilePond. Zwróć uwagę, jak dodaliśmy klasę filepond do selektora zapytań. Dzieje się tak, abyśmy mogli odróżnić pola wejściowe, które chcemy przekonwertować na pola FilePond, od tych, których możemy nie chcieć.

Następnie pobraliśmy token CSRF z metatagu, który dodaliśmy do widoku Blade. Dzieje się tak, abyśmy mogli przekazać go do FilePond, aby mógł zostać wysłany na nasz serwer podczas próby przesłania pliku. Bez dodawania tego otrzymasz odpowiedź o błędzie HTTP 419 przy każdej próbie przekazania pliku.

Następnie utworzyliśmy naszą instancję FilePond i określiliśmy, że gdy chcemy przesłać nowy plik, powinien on zostać wysłany na adres URL /uploads/process na naszym serwerze. FilePond zapewnia nam również funkcję określania adresu URL do usuwania tymczasowo przesłanych plików, ale nie będziemy używać tej funkcji w tym samouczku.

Przód powinien być teraz gotowy do użycia. Jeśli użytkownik wybierze plik CSV, zostanie on wysłany na adres URL /uploads/process i tymczasowo zapisany. Ukryte pole csv w formularzu zostanie wypełnione ścieżką pliku, w którym tymczasowo zapisaliśmy plik.

Konfigurowanie FilePond na zapleczu

Możemy teraz skonfigurować backend naszej aplikacji Laravel, aby obsługiwać przesyłanie plików pochodzących z FilePond. Aby to zrobić, musimy utworzyć trasę i kontroler, które są odpowiedzialne za tymczasowe przechowywanie przesłanych plików.

Jak wspomniałem wcześniej, FilePond zapewnia możliwość przesyłania plików w kawałkach. Ale na potrzeby tego samouczka zamierzamy zachować prostotę i spojrzeć tylko na przesyłanie plików w jednym żądaniu.

Najpierw zaczniemy od utworzenia nowego FileUploadController, uruchamiając następujące polecenie:

php artisan make:controller FileUploadControlle

Następnie możemy dodać metodę procesu do kontrolera, który obsługuje przesyłanie pliku i przechowuje plik w katalogu tmp w magazynie:

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
    {
        // We don't know the name of the file input, so we need to grab
        // all the files from the request and grab the first file.
        /** @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.');
        }
        // Now that we know there's only one key, we can grab it to get
        // the file from the request.
        $requestKey = array_key_first($files);

        // If we are allowing multiple files to be uploaded, the field in the
        // request will be an array with a single file rather than just a
        // single file (e.g. - `csv[]` rather than `csv`). So we need to
        // grab the first file from the array. Otherwise, we can assume
        // the uploaded file is for a single file input and we can
        // grab it directly from the request.
        $file = is_array($request->input($requestKey))
            ? $request->file($requestKey)[0]
            : $request->file($requestKey);

        // Store the file in a temporary location and return the location
        // for FilePond to use.
        return $file->store(
            path: 'tmp/'.now()->timestamp.'-'.Str::random(20)
        );
    }
}

Być może zauważyliście, że dodaliśmy również obsługę pól formularzy, które akceptują wiele przesłanych plików. Omówimy, jak skonfigurować FilePond na froncie, aby również obsługiwać przesyłanie wielu plików w dalszej części tego artykułu.

Jeśli użytkownik prześle plik do tego kontrolera, zostanie zwrócony ciąg podobny do następującego:

tmp/1678198256-88eXsQV7XB2RU5zXdw0S/9A4eK5mRLAtayW78jhRo3Lc3WdSSrsihpVHhMvzr.png

Następnie możemy zarejestrować trasę /uploads/process w naszym pliku internetowym.php w następujący sposób:

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

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

Twoja aplikacja powinna teraz pomyślnie przekazywać pliki i przechowywać je w katalogu tymczasowym.

Uzyskiwanie dostępu do przesłanego pliku w kontrolerze

Teraz, gdy skonfigurowaliśmy FilePond na froncie i dodaliśmy funkcję tymczasowego przechowywania plików na zapleczu, możemy teraz sprawdzić, jak uzyskać dostęp do przesłanych plików w naszych kontrolerach po przesłaniu formularza.

Zaczniemy od utworzenia nowego kontrolera, który jest odpowiedzialny za import produktów z pliku CSV. Możemy to zrobić, uruchamiając następujące polecenie:

php artisan make:controller ImportProductController -i

Następnie możemy zaktualizować nasz nowo utworzony ImportProductController, aby obsłużyć import plików:

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',
        ]);
        // Copy the file from a temporary location to a permanent location.
        $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');
    }
}

Rzućmy okiem na to, co jest robione w powyższej metodzie kontrolera.

Najpierw dodaliśmy wskazówkę typu dla klasy ProductImportService, dzięki czemu zostanie ona rozwiązana z kontenera usługi, którego możemy użyć w naszej metodzie kontrolera. Nie jest to klasa, którą będziemy omawiać w tym artykule, ale możemy założyć, że jest odpowiedzialna za importowanie produktów z pliku CSV.

Sprawdzamy również, czy żądanie zawiera pole ciągu csv. Przyjrzymy się, jak możemy poprawić tę walidację w dalszej części artykułu.

Następnie kopiujemy plik z jego tymczasowej lokalizacji do stałej lokalizacji, abyśmy mogli przekazać go do naszego obiektu ProductImportService.

Po wykonaniu tej czynności zwracamy odpowiedź przekierowania na stronę indeksu produktów z komunikatem o powodzeniu.

Możemy teraz zarejestrować trasę dla naszego ImportProductController w naszej sieci.php plik taki jak so:Przesyłanie obrazów FilePond

use App\Http\Controllers\ImportProductController;

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

 

zapewnia bardzo przydatną wtyczkę FilePondPluginImagePreview, która pozwala nam wyświetlić podgląd obrazu, który użytkownik wybrał do przesłania. Myślę, że to naprawdę miły akcent i wygląda świetnie. Zapewnia również informacje zwrotne dla użytkownika na temat pliku, który zdecydował się przesłać, aby mógł potwierdzić, że jest to poprawny.

Aby użyć wtyczki FilePondPluginImagePreview, możemy zainstalować ją za pośrednictwem NPM, uruchamiając następujące polecenie:Po zainstalowaniu możemy

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

zaimportować następujące linie do naszej aplikacji.js file:

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

Następnie możemy użyć metody registerPlugin, aby zarejestrować wtyczkę w FilePond:

FilePond.registerPlugin(FilePondPluginImagePreview);

Po dodaniu tych linii Twój kod może wyglądać mniej więcej tak:

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,
});

To wszystko! Powinieneś teraz mieć działający komponent FilePond, który umożliwia przesyłanie obrazów i ich podgląd.

Przesyłanie wielu plików

Może się zdarzyć, że zechcesz przesłać wiele plików jednocześnie w jednym przesyłaniu formularza. Na przykład możesz przesłać kilka obrazów dla jednego produktu.

Aby to zrobić, możemy dodać atrybut multiple do naszego elementu wejściowego:

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

Następnie możemy przekazać allowMultiple: true do metody setOptions:

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

To wszystko! Upewniliśmy się już, że nasz FileUploadController może obsługiwać wiele plików, więc nie musimy wprowadzać w nim żadnych zmian.

Jeśli użytkownik spróbuje przesłać dwa pliki, do serwera zostaną wysłane dwa oddzielne żądania w celu przechowywania plików. Następnie do formularza zostaną dodane dwa ukryte pola csv[] z nazwami plików przesłanych plików.

Zauważ, że musimy używać csv[] zamiast csv. Wynika to z faktu, że gdybyśmy użyli csv, bylibyśmy w stanie wysłać tylko jedną ścieżkę pliku za każdym razem, gdy formularz zostanie przesłany. Za pomocą csv[] możemy wysyłać wiele ścieżek plików, do których można następnie uzyskać dostęp w naszym kontrolerze jako tablicę ciągów.

Idąc dalej Teraz,

gdy przyjrzeliśmy się, w jaki sposób możemy przesyłać pliki do naszych aplikacji Laravel za pomocą FilePond, rzućmy okiem na kilka innych rzeczy, które prawdopodobnie będziesz chciał

zrobić.Walidacja

Filepond zapewnia kilka pomocników, których można użyć do dodania walidacji do komponentu przesyłania plików, takich jak data-max-file-size. Możesz dodać tych pomocników walidacji do elementu wejściowego w następujący sposób:Należy

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

jednak pamiętać, że walidacja po stronie klienta jest przeznaczona głównie do celów interfejsu użytkownika / UX, a nie bezpieczeństwa. Zawsze należy również sprawdzać poprawność danych po stronie serwera, aby upewnić się, że dane są prawidłowe.

Z tego powodu bardzo ważne jest, aby zweryfikować plik po przesłaniu formularza przed próbą jego przetworzenia.

Wyobraźmy sobie na przykład, że udostępniamy użytkownikowi funkcjonalność aktualizacji zdjęcia profilowego. Nie chcesz, aby to pole akceptowało plik CSV. Zamiast tego chcielibyśmy się upewnić, że plik jest obrazem.

Rzućmy więc okiem na to, jak możemy napisać regułę sprawdzania poprawności, aby upewnić się, że przesłany plik jest prawidłowy. Zaczniemy od utworzenia nowej reguły sprawdzania poprawności, uruchamiając następujące polecenie:

php artisan make:rule ValidFileUpload

Możemy zaktualizować naszą regułę ValidFileUpload, aby wyglądała tak: W klasie

declare(strict_types=1);

namespace App\Rules;

use Closure;
use Illuminate\Contracts\zrobić.Walidacja\zrobić.WalidacjaRule;
use Illuminate\Support\Facades\Storage;

final class ValidFileUpload implements zrobić.WalidacjaRule
{
    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 zdefiniowaliśmy konstruktor, który akceptuje tablicę prawidłowych typów MIME.

W metodzie sprawdzania poprawności dodaliśmy dwa testy:Sprawdź,

  1. czy plik istnieje w magazynie.
  2. Sprawdź, czy typ MIME pliku znajduje się w tablicy prawidłowych typów MIME.

Następnie możemy użyć tej reguły do walidacji w następujący sposób:

use App\Rules\ValidFileUpload;

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

Możesz nawet pójść o krok dalej i dodać dodatkowe asercje, takie jak sprawdzenie, czy rozmiar pliku nie przekracza określonego rozmiaru.

Czyszczenie plików tymczasowych

Z czasem w folderze tmp może gromadzić się duża liczba plików tymczasowych. Możesz więc napisać polecenie Artisan, które możesz zaplanować regularne uruchamianie, aby usunąć foldery z folderu tmp, które są starsze niż określony czas.

Rzućmy okiem na to, jak możemy to zrobić. Zaczniemy od utworzenia nowego polecenia DeleteTempUploadedFiles, uruchamiając następujące polecenie:

php artisan make:command DeleteTempUploadedFiles

Następnie możemy zaktualizować nasze polecenie DeleteTempUploadedFiles, aby wyglądało mniej więcej tak:

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);
            }
        }
    }
}

W powyższym poleceniu przeglądamy pętlę przez wszystkie katalogi w folderze tmp magazynu i sprawdzamy, czy katalog jest starszy niż 24 godziny. Jeśli tak, usuwamy katalog.

Następnie możemy zaplanować uruchamianie tego polecenia co godzinę, dodając je do metody harmonogramu w klasie app/Console/Kernel.php class:

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();
    }
    // ...
}

Zakładając, że masz uruchomiony harmonogram aplikacji, oznacza to, że co godzinę aplikacja będzie usuwać wszystkie katalogi tymczasowe starsze niż 24 godziny. Oznacza to, że folder tmp powinien zawierać tylko pliki, które mogły być ostatnio używane lub mogą być obecnie używane.

W zależności od aplikacji można zmienić czas istnienia katalogu lub częstotliwość jego usuwania.

Testowanie kodu

Jeśli czytałeś wcześniej którykolwiek z moich artykułów, będziesz wiedział, że jestem wielkim fanem testowania. Ważne jest, aby Twój kod miał napisane dla niego testy, zwłaszcza jeśli zamierzasz go używać w produkcji. Daje to pewność, że kod działa poprawnie i ułatwia wprowadzanie zmian w przyszłości.

Rzućmy okiem na to, jak możemy napisać kilka podstawowych testów dla naszej funkcji przesyłania plików w naszym FileUploadController. Na wysokim poziomie chcemy to sprawdzić:

  • Plik może być przechowywany w folderze tmp, jeśli pole formularza obsługuje pojedynczy plik.
  • Plik może być przechowywany w folderze tmp, jeśli pole formularza obsługuje wiele plików.
  • Błąd jest zwracany, jeśli żaden plik nie zostanie przekazany w żądaniu.
  • Błąd jest zwracany, jeśli w żądaniu przekazano więcej niż jeden plik.

Moglibyśmy napisać kilka podstawowych testów, aby objąć te scenariusze w następujący sposób:

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();

        // Use a fake storage driver so we don't store files on the real disk.
        Storage::fake();

        // Freeze time and define how `Str::random` should work. This allows us
        // to explicitly check that the file is stored in the correct location
        // and is being named correctly.
        $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);
    }
}

Chociaż te testy są dość podstawowe, powinny dać ci dobry punkt wyjścia do napisania własnych testów dla funkcji przesyłania plików. Możesz rozwinąć te testy, aby sprawdzić, czy zwracany jest prawidłowy komunikat o błędzie. Możesz też sprawdzić, czy tylko niektórzy użytkownicy mogą przekazywać pliki, jeśli dodasz uwierzytelnianie i autoryzację do przepływu przekazywania plików.

Możesz również dodać testy dla reguły sprawdzania poprawności. Może to pomóc w uzyskaniu większej pewności co do dodawania większej liczby potwierdzeń w przyszłości, jeśli zdecydujesz się na zaostrzenie walidacji.

Wtym artykule przyjrzeliśmy się, jak używać FilePond do asynchronicznego przesyłania plików w aplikacjach Laravel. Przyjrzeliśmy się również, jak usunąć pliki tymczasowe, zweryfikować przesłane pliki i napisać testy, aby upewnić się, że przesyłane pliki działają.

Mamy nadzieję, że teraz powinieneś czuć się wystarczająco pewnie, aby wdrożyć to samo podejście we własnych projektach Laravel, aby dodać funkcję przesyłania plików do swoich aplikacji.

Comments

CrazyBoy49z
CrazyBoy49z 11.03.2023 00:19

test comment

Yurij Finiv

Yurij Finiv

Full stack

O

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

O autorze CrazyBoy49z
WORK EXPERIENCE
Kontakt
Ukraine, Lutsk
+380979856297