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


Вам также могут понравиться

Загрузка файлов на сервер средствами PHP

Загрузка файлов на сервер средствами PHP

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

Современные способы заработка на сайтах

Современные способы заработка на сайтах

Не для кого не секрет, что в современном мире вы можете зарабатывать определённые деньги на своём сайте ничего не продавая. Говоря «ничего не продавая», я имею ввиду классическую схему, когда вы продаёте свои услуги, перепродаёте товар (предварительно закупая или используя схемы прямой поставки «дроп-шиппинга») или что-то производите. В статье рассмотрим методы, которые не требуют от вас подобных действий. И так, рассмотрим основные методы в порядке возрастания их сложности и времени которое требуется от владельца сайта.

Пользовательский тип свойств инфоблока в 1С Битрикс

Пользовательский тип свойств инфоблока в 1С Битрикс

В дополнение к статье о создании собственного типа пользовательских полей хочу рассказать как делать аналогичные свойства для информационных блоков, т.к. эти свойства относятся к другому модулю, а именно «информационные блоки» (iblock).


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

Самые читаемые

Тонкая настройка SEO для результатов фильтрации каталога битрикс

Тонкая настройка SEO для результатов фильтрации каталога битрикс

Одним из преимуществ интернет-магазинов на 1С Битрикс на мой взгляд является наличие не так давно до...

Основы SEO оптимизации сайта

Основы SEO оптимизации сайта

Эта статья не истина в последней инстанции, а лишь набор правил которые я применяю при создании/испр...

Собственный тип пользовательских полей в 1С Битрикс

Собственный тип пользовательских полей в 1С Битрикс

Для решения некоторых задач порой не хватает стандартного набора пользовательских полей поставляемых...