На данный момент загрузка файлов является одной из необходимых функций любого сайта, будь то загрузка изображения в профиль пользователя, отправка документов при регистрации, размещение прайс листов, создание галереи и т.д. В статье пойдёт речь о реализации загрузки различных файлов на сервер средствами 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 и JS файлы к шаблону 1С Битрикс

Как подключить CSS и JS файлы к шаблону 1С Битрикс

Как правильно подключать стили и скрипты к шаблону 1С Битрикс.

Генерация оглавления статьи

Генерация оглавления статьи

В статье рассмотрен пример функции для генерации оглавления статьи блога или новости


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