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

Настройка параметров PHP

Для начала, нам потребуется настроить некоторые параметры php, для этого необходимо отредактировать конфигурационный файл php.ini. Если у вас нет доступа к этому файлу, обратитесь в техническую поддержку вашего хостинга. Я же использую локальный сервер OpenServer и для настройки php нужно перейти в раздел Дополнительно -> Конфигурация -> PHP (ваша версия PHP). Укажем следующие настройки:

;Время выполнения скрипта в секундах (максимальное), обычно стараются укладывать в 30-60 секунд, для примера задам 3 минуты
max_execution_time = 180
 
;Максимальное потребление памяти одним php скриптом
memory_limit = 128M


;Флаг загрузки файлов (On - можно Off - нельзя)
file_uploads = On

;Максимально разрешённое количество одновременно загружаемых файлов
max_file_uploads = 20

;Максимально допустимый размер данных, отправляемых методом POST
post_max_size = 100M
 
;Папка для временного хранения файлов во время загрузки, я использую OpenServer поэтому в пути присутсвует спец.переменная %sprogdir%
upload_tmp_dir = "%sprogdir%/userdata/temp/"
 
;Максимально допустимый размер загружаемого файла
upload_max_filesize = 5M


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

Загрузка одного файла из веб-формы

Описываем на примере загрузки аватара в профиль пользователя на форуме. Чтобы форма имела какой-нибудь «приличный» вид, я подключил css bootstrap используя cdn.Сама форма максимально простая, выглядит так:


    <div class="container">
        <div class="row">
            <div class="col-6">
                <h1>Загрузка файла на сервер</h1>
                <form action="profile.php" method="post" enctype="multipart/form-data">
                    <label for="user_avatar" class="form-label">Аватар пользователя</label>
                    <input class="form-control" type="file" id="user_avatar" name="user_avatar"><br/>
                    <button type="submit" class="btn btn-primary">Отправить</button>
                </form>
            </div>
            <div class="col-6"></div>
        </div>
    </div>



Файл profile.php который будет отвечать за обработку загружаемого изображения пока содержит следующий код: 

 echo '<pre>'; print_r($_FILES); echo '</pre>';

Все передаваемые на сервер файлы попадают в так называемый «суперглобальный массив» $_FILES, приведённый выше код покажет нам его содержимое. Отправим тестовый файл, у меня это картинка формата jpg размером 100x100 пикселей. Получим следующее содержимое $_FILES:
 Array(
    [user_avatar] => Array
        (
            [name] => avatar.jpg
            [type] => image/jpeg
            [tmp_name] => F:\OpenServer\userdata\php_upload\php6F8A.tmp
            [error] => 0
            [size] => 6933
        )

) 

Обратите внимание что все данные о загружаемом файле хранятся в виде ассоциативного массива, при этом ключ массива user_avatar равен значению атрибута name в форме загрузки файла. Сам файл при этом попадает во временную директорию на сервере tmp_name которую мы указывали в php.ini нашего сервере. 



В случае каких-то ошибок в поле error этого ассоциативного массива будут попадать коды ошибок загрузки файла. К этим кодам относятся:
  • UPLOAD_ERR_OK - Ошибок не возникло, файл был успешно загружен на сервер;
  • UPLOAD_ERR_INI_SIZE - Размер принятого файла превысил максимально допустимый размер;
  • UPLOAD_ERR_FORM_SIZE - Размер загружаемого файла превысил значение MAX_FILE_SIZE, указанное в HTML-форме;
  • UPLOAD_ERR_PARTIAL - Загружаемый файл был получен только частично;
  • UPLOAD_ERR_NO_FILE - Файл не был загружен;
  • UPLOAD_ERR_NO_TMP_DIR - Отсутствует временная папка;
  • UPLOAD_ERR_CANT_WRITE - Не удалось записать файл на диск;
  • UPLOAD_ERR_EXTENSION - PHP-расширение остановило загрузку файла.
Следует так же отметить что поля type и size формируются из передаваемых http заголовков в момент загрузки, а их можно подделать. Вообще при работе с данными пришедшими от пользователя не стоит доверять ничему. Все они могут нести потенциальную угрозу и их нужно тщательно фильтровать.

Давайте выполним ряд проверок загружаемого файла:


// Массив c описанием ошибок
$uploadErrorMessages = [
    'UPLOAD_ERR_INI_SIZE'   => 'Размер файла превысил значение upload_max_filesize в конфигурации PHP.',
    'UPLOAD_ERR_FORM_SIZE'  => 'Размер загружаемого файла превысил значение MAX_FILE_SIZE в HTML-форме.',
    'UPLOAD_ERR_PARTIAL'    => 'Загружаемый файл был получен только частично.',
    'UPLOAD_ERR_NO_FILE'    => 'Файл не был загружен.',
    'UPLOAD_ERR_NO_TMP_DIR' => 'Отсутствует временная папка.',
    'UPLOAD_ERR_CANT_WRITE' => 'Не удалось записать файл на диск.',
    'UPLOAD_ERR_EXTENSION'  => 'PHP-расширение остановило загрузку файла.',
];

// Проверяем что $_FILES есть наш файл
if (isset($_FILES['user_avatar'])) {
    $image = $_FILES['user_avatar'];
    // Получаем нужные элементы массива "user_avatar"
    $tmpFileName = $_FILES['user_avatar']['tmp_name'];
    $uploadErrorCode = $_FILES['user_avatar']['error'];

    //Если есть ошибка или файл был загружен не через HTTP POST вернём ошибку
    if ($uploadErrorCode !== UPLOAD_ERR_OK || !is_uploaded_file($tmpFileName)) {
        //Выводим соответствующее $errorCode сообщение об ошибка или "ошибка неизвестна"
        $message = isset($uploadErrorMessages[$uploadErrorCode]) ? $uploadErrorMessages[$uploadErrorCode] : 'При загрузке файла произошла неизвестная ошибка.';
        //Завершаем работу скрипта, выводим сообщение об ошибке
        echo json_encode(['status'=>'error', 'msg'=>$message]);
    } else {
        //Если всё ок, выполняем манипуляции с файлом
        echo json_encode(['status'=>'ok', 'msg'=>'Ошибок нет']);
    }
}


Здесь я проверяю что если код ошибки не равен UPLOAD_ERR_OK или функция is_uploaded_file() вернула false (т.е. файл был загружен не через HTTP POST а как-то иначе), то подготавливаем переменную $message и возвращаем JSON строку с ошибкой. Если всё ок, то выводится сообщение что ошибок нет, опять таки в виде JSON. Это сделано для того, чтобы более удобно взаимодействовать с фронт-частью нашего приложения.

Давайте сохраним файл в папку /img/ на нашем учебном сервере. Добавим следующий код в условие, где не возникло ошибок при загрузке:


//Если всё ок, выполняем манипуляции с файлом
if (move_uploaded_file($_FILES['user_avatar']['tmp_name'], $uploadDir . $_FILES['user_avatar']['name'])) {
	echo json_encode(['status'=>'ok', 'msg'=>'Файл загружен в директорию ' . $uploadDir]);
} else {
	echo json_encode(['status'=>'error', 'msg'=>'Ошибка загрузки файла']);
}


А так же выведем список файлов в папке /img/:


<div class="container">
    <div class="row">
        <div class="col-6">
            <h1>Загрузка файла на сервер</h1>
            <form action="profile.php" method="post" enctype="multipart/form-data">
                <label for="user_avatar" class="form-label">Аватар пользователя</label>
                <input class="form-control" type="file" id="user_avatar" name="user_avatar"><br/>
                <button type="submit" class="btn btn-primary">Отправить</button>
            </form>
        </div>
        <div class="col-6">
            <h2>Список файлов в /img/</h2>
            <ul>
                <?
                $uploadDir = $_SERVER['DOCUMENT_ROOT'] . '/fileupload/img/';
                $arFiles = scandir($uploadDir, 1);
                if(is_array($arFiles)) {
                    foreach ($arFiles as $file)
                    {
                        ?>
                        <li><a href="img/<?=$file?>" target="_blank"><?=$file?></a></li>
                        <?
                    }
                }
                ?>
            </ul>
        </div>
    </div>
</div>

Получаем такую картину

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



Множественная загрузка файлов на сервер

Добавим ещё одну форму в наш код.


<div class="container">
    <div class="row">
        <div class="col-6">
            <h1>Загрузка файла на сервер</h1>
            <form action="profile.php" method="post" enctype="multipart/form-data">
                <label for="user_avatar" class="form-label">Аватар пользователя</label>
                <input class="form-control" type="file" id="user_avatar" name="user_avatar"><br/>
                <button type="submit" class="btn btn-primary">Отправить</button>
            </form>
            <br>
            <hr>
            <br>
            <h2>Множественная загрузка файлов</h2>
            <form action="gallery.php" method="post" enctype="multipart/form-data">
                <label for="gallery" class="form-label">Фотогалерея</label>
                <input class="form-control" type="file" id="gallery" name="gallery[]" multiple><br/>
                <button type="submit" class="btn btn-primary">Отправить</button>
            </form>
        </div>
        <div class="col-6">
            <h2>Список файлов в /img/</h2>
            <ul>
                <?
                $uploadDir = $_SERVER['DOCUMENT_ROOT'] . '/fileupload/img/';
                $arFiles = scandir($uploadDir, 1);
                if(is_array($arFiles)) {
                    foreach ($arFiles as $file)
                    {
                        ?>
                        <li><a href="img/<?=$file?>" target="_blank"><?=$file?></a></li>
                        <?
                    }
                }
                ?>
            </ul>
        </div>
    </div>
</div>


Обратите внимание на то, что в name поля загрузки файла появились квадратные скобки gallery[] а так же атрибут multiple. С такими параметрами вы сможете выбрать несколько файлов зажав кнопку Ctrl в окне просмотра файлов. Форма приобрела следующий вид.

Множественная загрузка файлов

Я так же добавил отдельный скрипт для обработки множественной загрузки gallery.php. В целом он имеет ту же структуру, однако файлы теперь обрабатываются в цикле из заранее подготовленного массива $arFiles.




header('Content-Type: application/json');

// Массив c описанием ошибок
$uploadErrorMessages = [
    'UPLOAD_ERR_INI_SIZE'   => 'Размер файла превысил значение upload_max_filesize в конфигурации PHP.',
    'UPLOAD_ERR_FORM_SIZE'  => 'Размер загружаемого файла превысил значение MAX_FILE_SIZE в HTML-форме.',
    'UPLOAD_ERR_PARTIAL'    => 'Загружаемый файл был получен только частично.',
    'UPLOAD_ERR_NO_FILE'    => 'Файл не был загружен.',
    'UPLOAD_ERR_NO_TMP_DIR' => 'Отсутствует временная папка.',
    'UPLOAD_ERR_CANT_WRITE' => 'Не удалось записать файл на диск.',
    'UPLOAD_ERR_EXTENSION'  => 'PHP-расширение остановило загрузку файла.',
];

// Проверяем что $_FILES есть наш файл
if (isset($_FILES['gallery'])) {
    $uploadDir = $_SERVER['DOCUMENT_ROOT'] . '/fileupload/img/';
    $arFiles = [];
    //Изменим структуру массива $_FILES для удобства работы с файлами
    foreach($_FILES['gallery'] as $field => $arValues) {
        foreach($arValues as $key => $value) {
            $arFiles[$key][$field] = $value;
        }
    }

    foreach ($arFiles as $key => $file){

        // Получаем нужные элементы массива "user_avatar"
        $tmpFileName = $file['tmp_name'];
        $uploadErrorCode = $file['error'];

        //Если есть ошибка или файл был загружен не через HTTP POST вернём ошибку
        if ($uploadErrorCode !== UPLOAD_ERR_OK || !is_uploaded_file($tmpFileName)) {
            //Выводим соответствующее $errorCode сообщение об ошибка или "ошибка неизвестна"
            $message = isset($uploadErrorMessages[$uploadErrorCode]) ? $uploadErrorMessages[$uploadErrorCode] : 'При загрузке файла произошла неизвестная ошибка.';
            //Завершаем работу скрипта, выводим сообщение об ошибке
            echo json_encode(['status'=>'error', 'msg'=>$message], JSON_UNESCAPED_UNICODE);
        } else {
            //Добавим проверку на тип файла
            $allowedFormats = ['image/jpeg', 'image/gif', 'image/png'];
            $finfo = finfo_open(FILEINFO_MIME_TYPE);
            $fileInfo = finfo_file($finfo, $tmpFileName);

            if(in_array($fileInfo, $allowedFormats)){
                //Если всё ок, выполняем манипуляции с файлом
                if (move_uploaded_file($file['tmp_name'], $uploadDir . $file['name'])) {
                    echo json_encode(['status'=>'ok', 'msg'=>'Файл загружен в директорию ' . $uploadDir], JSON_UNESCAPED_UNICODE);
                } else {
                    echo json_encode(['status'=>'error', 'msg'=>'Ошибка загрузки файла'], JSON_UNESCAPED_UNICODE);
                }
            } else {
                echo json_encode(['status'=>'error', 'msg'=>'Можно загружать только изображения jpg, png или gif формата'], JSON_UNESCAPED_UNICODE);
            }
        }
    }
}

А текущем варианте скрипта отсутствует ajax и всякий js способный улучшить пользовательский опыт. Полную версию скрипта я опишу в разделе bitrix этого сайта где буду делать простой компонент множественной ajax загрузки файлов на сервер.


Не пропусти свежие посты, подпишись:
Полезная статья?
(Голосов: 5, Рейтинг: 3.22)
Курсы от партнёров
Хотите освоить востребованную профессию? Воспользуйтесь предложениями от наших партнёров. Пройдите учебный курс по одному из популярных IT направлений.

Все курсы партёнров
Вам также могут понравиться
Кем можно работать в сфере веб-разработки

Кем можно работать в сфере веб-разработки

Хотите начать работать в сфере веб-разработки, но не знаете с чего можно начать? Читайте описание самых популярных веб-профессий, с их описанием, обязанностями и ориентировочными зарплатами.

CSS курсоры

CSS курсоры

В статье рассмотрены возможности изменения курсоров пользователя при помощи CSS

Работа с регистром строк в php

Работа с регистром строк в php

В статье рассмотрены примеры работы с регистром строк в языке PHP, проверка регистра, изменение, инверсия


Комментарии
Защита от автоматических сообщений
CAPTCHA
Введите слово на картинке