
🚀 Przesyłanie wielu plików za pomocą HTML i PHP
Użytkownik musi przesłać pięć zdjęć, a formularz przyjmuje tylko jedno. Albo klient prosi o przesyłanie dokumentów paczką z przypisaniem tytułu do każdego pliku. To nie fantastyka: standardowe zadanie, które PHP rozwiązuje w trzydziestu linijkach.
Problem w tym, że większość poradników pokazuje przesyłanie jednego pliku. Multiprzesyłanie działa inaczej: tablica plików zamiast jednego, powiązanie z dodatkowymi polami, ochrona przed nadpisaniem. Poniżej działająca implementacja od zera do gotowego snippetu.
💡 Szybki przegląd:
- Tworzą Państwo formularz HTML z
enctype="multipart/form-data"i polemfilez atrybutemmultiple. - Przetwarzają Państwo tablicę
$_FILESw PHP: pętla po indeksach imove_uploaded_file()dla każdego pliku. - Dodają Państwo walidację rozszerzenia i rozmiaru przed zapisem na dysk.
- Dla powiązania „plik + tytuł" używają Państwo równoległych tablic
title[]ifileUpload[].
Szybki przegląd wideo tego samego tematu, od formularza HTML do zapisu plików w bazie danych:
Krok 1: Formularz HTML do przesyłania wielu plików
Najpierw proszę zbudować formularz. Kluczowe punkty: atrybut enctype z wartością multipart/form-data i pole wejściowe z multiple. Bez enctype serwer po prostu nie otrzyma plików, to nie błąd PHP, a specyfikacja HTTP.
Nazwę pola proszę podawać jako tablicę: fileUpload[]. Nawiasy kwadratowe na końcu informują PHP, że na serwer przychodzi nie jeden plik, a lista.
1 <form action="" method="post" enctype="multipart/form-data"> 2 <label>Выберите файлы:</label> 3 <input type="file" name="fileUpload[]" multiple> 4 <input type="submit" name="Submit" value="Загрузить"> 5 </form>
Powyższy formularz pozwala zaznaczyć w oknie wyboru dowolną liczbę plików za jednym razem. Przeglądarka sama spakuje je do żądania multipart i wyśle na serwer. Żaden JavaScript na tym etapie nie jest potrzebny.
Krok 2: Przetwarzanie tablicy plików w PHP
Gdy formularz trafia na serwer, PHP wypełnia superglobalną tablicę $_FILES. Jednak struktura jest tam inna niż przy pojedynczym przesyłaniu: klucze name / tmp_name / size / error stają się tablicami, gdzie indeks odpowiada numerowi porządkowemu pliku.
Przechodzimy pętlą po indeksach i zapisujemy każdy plik przez move_uploaded_file():
1 <?php 2 3 $target_dir = 'uploads/'; 4 5 if (isset($_FILES['fileUpload']['name'])) { 6 7 $total_files = count($_FILES['fileUpload']['name']); 8 9 for ($key = 0; $key < $total_files; $key++) { 10 11 // Пропускаем пустые поля (файл не выбран) 12 if (isset($_FILES['fileUpload']['name'][$key]) 13 && $_FILES['fileUpload']['size'][$key] > 0) { 14 15 $original_filename = $_FILES['fileUpload']['name'][$key]; 16 $target = $target_dir . basename($original_filename); 17 $tmp = $_FILES['fileUpload']['tmp_name'][$key]; 18 19 move_uploaded_file($tmp, $target); 20 } 21 } 22 }
Proszę dodać ten kod do functions.php motywu potomnego lub do wtyczki Code Snippets, a formularz z kroku 1 zacznie przyjmować pliki.
Proszę zwrócić uwagę: basename() odcina ścieżkę z nazwy pliku. To ochrona przed atakiem path traversal: osoba atakująca może wysłać ../../wp-config.php jako nazwę. basename() zostawia tylko wp-config.php, ale kolejną warstwą ochrony i tak musi być walidacja rozszerzenia.
Krok 3: Sprawdzanie typu i rozmiaru pliku
Nie można przyjmować „jak leci" wszystkiego, co wysłała przeglądarka. Minimalna ochrona: biała lista rozszerzeń i ograniczenie rozmiaru.
Sprawdzanie rozszerzenia przez pathinfo() z flagą PATHINFO_EXTENSION. Proszę zamienić na małe litery i porównać z dozwolonymi:
1 $allowed_ext = array('jpg', 'jpeg', 'png', 'gif', 'bmp', 'pdf', 'doc', 'docx'); 2 $ext = strtolower(pathinfo($_FILES['fileUpload']['name'][$key], PATHINFO_EXTENSION)); 3 4 if (!in_array($ext, $allowed_ext)) { 5 $errors[$key] = 'Недопустимый тип файла: ' . $ext; 6 continue; 7 }
Rozmiar sprawdza się jeszcze prościej, wartość size w bajtach jest już w $_FILES:
1 $max_file_size = 5 * 1024 * 1024; // 5 МБ 2 3 if ($_FILES['fileUpload']['size'][$key] > $max_file_size) { 4 $errors[$key] = 'Файл превышает допустимый размер (5 МБ)'; 5 continue; 6 }
Obie walidacje proszę umieszczać przed move_uploaded_file() w tej samej pętli. Pliki, które nie przejdą walidacji, nie trafią na dysk. Proszę zbierać błędy do tablicy i po przetworzeniu wyświetlać użytkownikowi listę: który plik i dlaczego nie został przesłany. Bez tego zobaczy on tylko pusty wynik i odejdzie, uznając, że formularz jest zepsuty. Dla każdego odrzuconego pliku proszę podawać nazwę i przyczynę: „photo.png: niedozwolony typ" lub „archive.zip: przekroczono rozmiar".
Krok 4: Zmiana nazw plików podczas przesyłania
Jeśli dwóch użytkowników prześle photo.jpg, drugi plik nadpisze pierwszy. Rozwiązanie: generowanie unikalnej nazwy podczas zapisu. Najprostszy wariant: zamiana spacji na podkreślenia i dodanie time().
1 if (isset($_FILES['fileUpload']['name'][$key]) 2 && $_FILES['fileUpload']['size'][$key] > 0) { 3 4 $original_filename = $_FILES['fileUpload']['name'][$key]; 5 6 // Расширение отдельно 7 $ext = pathinfo($original_filename, PATHINFO_EXTENSION); 8 9 // Имя без расширения 10 $filename_without_ext = basename($original_filename, '.' . $ext); 11 12 // Новое имя: без пробелов + метка времени 13 $new_filename = str_replace(' ', '_', $filename_without_ext) 14 . '_' . time() . '.' . $ext; 15 16 move_uploaded_file( 17 $_FILES['fileUpload']['tmp_name'][$key], 18 $target_dir . $new_filename 19 ); 20 }
Znacznik time() daje unikalność z dokładnością do sekundy. W projekcie o dużym obciążeniu proszę go zastąpić przez uniqid() lub UUID: dwa jednoczesne żądania w jednej sekundzie wystarczą, by doszło do kolizji i utraty jednego z plików. Ponadto time() ujawnia datę przesłania w nazwie, co czasem jest niepożądane. UUID, na przykład ramsey/uuid, całkowicie eliminuje oba problemy.
Krok 5: Przesyłanie z dodatkowymi polami dla każdego pliku
Zdarza się, że do każdego pliku trzeba dołączyć tytuł, opis lub kategorię. Zamiast jednej pary „tytuł + plik" robią Państwo kilka, nazwy pól formatują Państwo jako tablice:
1 <form action="" method="post" enctype="multipart/form-data"> 2 3 <input type="text" name="title[]" placeholder="Заголовок"> 4 <input type="file" name="fileUpload[]"> 5 6 <input type="text" name="title[]" placeholder="Заголовок"> 7 <input type="file" name="fileUpload[]"> 8 9 <input type="text" name="title[]" placeholder="Заголовок"> 10 <input type="file" name="fileUpload[]"> 11 12 <input type="submit" name="Submit" value="Загрузить"> 13 14 </form>

Po stronie serwera $_POST['title'] i $_FILES['fileUpload']['name'] to równoległe tablice. Indeks 0 w jednej odpowiada indeksowi 0 w drugiej. Przetwarzanie: ta sama pętla, ale teraz z przypisaniem tytułu:
1 $total_files = count($_FILES['fileUpload']['name']); 2 3 for ($key = 0; $key < $total_files; $key++) { 4 5 if ($_FILES['fileUpload']['size'][$key] === 0) { 6 continue; // пустое поле — пропускаем 7 } 8 9 $title = isset($_POST['title'][$key]) ? $_POST['title'][$key] : ''; 10 $tmp = $_FILES['fileUpload']['tmp_name'][$key]; 11 $fname = basename($_FILES['fileUpload']['name'][$key]); 12 $target = $target_dir . time() . '_' . $fname; 13 14 if (move_uploaded_file($tmp, $target)) { 15 // Сохраните $title и $target в базу данных 16 // или в массив для дальнейшей обработки 17 } 18 }
Proszę sprawdzić, czy liczba pól title[] zgadza się z liczbą fileUpload[] w formularzu. Jeśli użytkownik dodał pola dynamicznie przez JavaScript, wszystkie indeksy muszą iść po kolei, bez luk. PHP nie zwija automatycznie tablic rzadkich i wtedy $_POST['title'][5] będzie istnieć, a $_FILES['fileUpload']['name'][5] już nie.
W praktyce to podejście pokrywa dobrą połowę zadań klienckich: galerie zdjęć z podpisami, przesyłanie dokumentów z tagami, składanie CV z portfolio. Ta sama zasada, równoległe tablice, działa dla description, category, order i dowolnych innych pól.
⁉️🤔 Często zadawane pytania
Dlaczego plik nie trafia do $_FILES, mimo że formularz został wysłany?
W dziewięciu przypadkach na dziesięć zapomniano o
enctype="multipart/form-data"w tagu<form>. Bez tego przeglądarka wysyła dane jako zwykły tekst i PHP nie wypełnia$_FILES. Proszę sprawdzić równieżpost_max_sizeiupload_max_filesizewphp.ini, przekroczenie któregokolwiek z nich daje pustą tablicę bez widocznych błędów.
Jak ograniczyć całkowity rozmiar przesyłanych danych dla jednego żądania?
Dyrektywa
post_max_sizewphp.iniogranicza całe ciało żądania POST, włącznie z plikami i polami tekstowymi. Wartość domyślna, zgodnie z dokumentacją PHP, to 8 MB. Proszę ustawić ją wyżej niżupload_max_filesizez zapasem na narzut kodowania multipart (około 20%). Dla masowego przesyłania proszę zwiększyć oba parametry:upload_max_filesize = 20Mipost_max_size = 25M.
Czy można przesyłać pliki asynchronicznie przez JavaScript?
Tak. Proszę użyć
FormDataifetchzmethod: 'POST'. PolafileUpload[]proszę dodawać przezformData.append('fileUpload[]', file)dla każdego wybranego pliku. NagłówkaContent-Typenie należy ustawiać ręcznie, przeglądarka sama ustawimultipart/form-dataz poprawną granicą. Do wizualnej informacji zwrotnej nada się pasek postępu przezXMLHttpRequest.upload.onprogress,fetchbezpośrednio nie udostępnia postępu.
Co z bezpieczeństwem: osoba atakująca przesyła skrypt PHP zamiast obrazu?
Sprawdzanie rozszerzenia z kroku 3 to pierwsza linia obrony. Druga: proszę zapisywać przesłane pliki POZA folderem głównym witryny lub w katalogu z
.htaccesszabraniającym wykonywania (php_flag engine off). Trzecia: proszę zmieniać nazwy plików podczas zapisu (krok 4), nawet jeślishell.phpprzejdzie filtr rozszerzeń, zamieni się wshell_1718300000.php, co samo w sobie nie jest groźne bez prawa do wykonania. Czwarta: proszę sprawdzać typ MIME nie z$_FILES['type'](wysyła go przeglądarka, łatwo podrobić), a przezfinfo(wbudowane w PHP Fileinfo), które odczytuje sygnaturę pliku.
Co daje opisane podejście: obraz podsumowujący
Zbudowali Państwo mechanizm przesyłania, który radzi sobie z trzema scenariuszami od ręki: paczka plików jednym przyciskiem, pojedyncze przesyłanie z walidacją oraz powiązanie „plik + metadane" przez równoległe tablice. Kodu około trzydziestu linijek na każdy krok, nic zbędnego.
Jeśli pracują Państwo w WordPressie, handler proszę umieścić w functions.php motywu potomnego lub we wtyczce Code Snippets. Na czystym PHP wystarczy umieścić skrypt obsługi w target dla action formularza.
Kod z artykułu pokrywa większość typowych zadań związanych z przesyłaniem. Pozostałe scenariusze, chunked-upload dla dużych plików, bezpośrednie przesyłanie do storage kompatybilnego z S3 oraz drag-and-drop z podglądem, omówimy w osobnych materiałach. Proszę napisać w komentarzach, który z nich jest istotny dla Państwa projektu: zajmiemy się nim w pierwszej kolejności.



