Сегодня загрузка файлов является практически неотъемлемым атрибутом современного web приложения. В данной статье речь пойдёт о том, как же загрузить файл(ы) на сервер с помощью PHP.
Настройка php.ini
[Resource Limits]
; Максимальное время выполнения скрипта в секундах
max_execution_time = 60
; Максимальное потребление памяти одним скриптом
memory_limit = 64M
[Data Handling]
; Максимально допустимый размер данных отправляемых методом POST
post_max_size = 5M
[File Uploads]
; Разрешение на загрузку файлов
file_uploads = On
; Папка для хранения файлов во время загрузки
upload_tmp_dir = home/user/temp
; Максимальный размер загружаемого файла
upload_max_filesize = 5M
; Максимально разрешённое количество одновременно загружаемых файлов
max_file_uploads = 10
Конфигурационный файл
php.ini
необходимо настраивать согласно бизнес-логики проекта! Например, мы планируем загружать не более десяти файлов до 2 Мбайт, а это значит нам понадобиться ~20 Мбайт памяти.
Загрузка одного файла на сервер из формы
Для начала разберём механизм загрузки одной картинки на сервер. Для загрузки картинки с компьютера пользователя необходимо с помощью HTML-формы отправить нужный (выбранный) файл PHP-скрипту upload.php
методом POST
и указать способ кодирования данных enctype="multipart/form-data"
(в данном случае данные не кодируются и это значение применяется только для отправки бинарных файлов).
<form action="upload.php" method="post" enctype="multipart/form-data">
<input type="file" name="image">
<button type="submit">Загрузить</button>
</form>
После отправки файла PHP-скрипту upload.php
его можно перехватить с помощью суперглобальной переменной $_FILES
с таким же именем, которая в массиве содержит информацию о файле (в нашем случае image
):
var_dump($_FILES);
Получим массив:
Array
(
[name] => image.jpg // оригинальное имя файла
[type] => image/jpeg // MIME-тип файла
[tmp_name] => home\user\temp\phpD07E.tmp // бинарный файл
[error] => 0 // код ошибки
[size] => 17170 // размер файла в байтах
)
Не всем данным из $_FILES
можно доверять: MIME-тип и размер файла можно подделать, т. к. они формируются из HTTP-ответа, а расширению в имени файла не стоит доверять в силу того, что за ним может скрываться совершенно другой файл. Тем не менее, дальше нам нужно проверить корректно ли загрузился наш файл и загрузился ли он вообще. Для этого необходимо проверить ошибки в $_FILES['image']['error']
и удостовериться, что файл загружен методом POST с помощью функции is_uploaded_file()
. Если что-то идёт не по плану, значит выводим ошибку на экран:
// Если в $_FILES существует "image" и она не NULL
if (isset($_FILES['image'])) {
$image = $_FILES['image'];
// Получаем нужные элементы массива "image"
$fileTmpName = $_FILES['image']['tmp_name'];
$errorCode = $_FILES['image']['error'];
// Проверим на ошибки
if ($errorCode !== UPLOAD_ERR_OK || !is_uploaded_file($fileTmpName)) {
// Массив с названиями ошибок
$errorMessages = [
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-расширение остановило загрузку файла.',
];
// Зададим неизвестную ошибку
$unknownMessage = 'При загрузке файла произошла неизвестная ошибка.';
// Если в массиве нет кода ошибки, скажем, что ошибка неизвестна
$outputMessage = isset($errorMessages[$errorCode]) ? $errorMessages[$errorCode] : $unknownMessage;
// Выведем название ошибки
die($outputMessage);
} else {
echo 'Ошибок нет.';
}
};
Для того, чтобы "редиска" не загрузил вредоносный код, встроенный в изображение, нельзя доверять функции getimagesize()
, которая также возвращает MIME-тип. Функция ожидает, что первый аргумент является ссылкой на корректный файл изображения. Определить настоящий MIME-тип картинки можно через расширение FileInfo
. Код ниже проверит наличие ключевого слова image
в типе нашего загружаемого файла и если его не окажется, выдаст ошибку:
// Создадим ресурс FileInfo
$fi = finfo_open(FILEINFO_MIME_TYPE);
// Получим MIME-тип
$mime = (string) finfo_file($fi, $fileTmpName);
// Проверим ключевое слово image (image/jpeg, image/png и т. д.)
if (strpos($mime, 'image') === false) die('Можно загружать только изображения.');
Таким образом, при необходимости, делаем проверку и на другие MIME-типы. Например, для zip архивов проверка будет такая:
// Проверим ключевое слово zip (application/zip)
if (strpos($mime, 'zip') === false) die('Можно загружать только архивы ZIP.');
На данном этапе мы уже можем загружать абсолютно любые картинки на наш сервер, прошедшие проверку на MIME-тип, но для загрузки изображений по определённым характеристикам нам необходимо валидировать их с помощью функции getimagesize()
, которой отдадим сам бинарный файл $_FILES['image']['tmp_name']
. В результате мы получим массив из элементов:
// Результат функции запишем в переменную
$image = getimagesize($fileTmpName);
var_dump($image);die;
// Результат
Array
(
[0] => 1280 // ширина
[1] => 768 // высота
[2] => 2 // тип
[3] => width="1280" height="768" // атрибуты для HTML
[bits] => 8 // глубина цвета
[channels] => 3 // цветовая модель
[mime] => image/jpeg // MIME-тип
)
Для дальнейшей валидации изображения и работы над ним нам необходимо знать только 3 значения: ширину, высоту и размер файла (для вычисления размера применим функцию filesize()
для бинарного файла из временной папки).
// Результат функции запишем в переменную
$image = getimagesize($fileTmpName);
// Зададим ограничения для картинок
$limitBytes = 1024 * 1024 * 5;
$limitWidth = 1280;
$limitHeight = 768;
// Проверим нужные параметры
if (filesize($fileTmpName) > $limitBytes) die('Размер изображения не должен превышать 5 Мбайт.');
if ($image[1] > $limitHeight) die('Высота изображения не должна превышать 768 точек.');
if ($image[0] > $limitWidth) die('Ширина изображения не должна превышать 1280 точек.');
После всех проверок мы можем с уверенностью переместить наш загружаемый файл в какую-нибудь директорию с картинками. Делать лучше это через функцию move_uploaded_file(), которая работает в безопасном режиме. Перед перемещением файла нельзя забыть сгенерировать случайное имя и расширение из типа изображения для нашего файла. Вот так это выглядит:
// Сгенерируем новое имя файла на основе MD5-хеша
$name = md5_file($fileTmpName);
// Сгенерируем расширение файла на основе типа картинки
$extension = image_type_to_extension($image[2]);
// Сократим .jpeg до .jpg
$format = str_replace('jpeg', 'jpg', $extension);
// Переместим картинку с новым именем и расширением в папку /upload
if (!move_uploaded_file($fileTmpName, __DIR__ . '/upload/' . $name . $format)) {
die('При записи изображения на диск произошла ошибка.');
}
echo 'Картинка успешно загружена!';
Вместо простого способа генерации имени файла на основе MD5-хеша можно пойти более продвинутым путём, а именно написать отдельную функцию, которая будет проверять уникальность названия картинки для того, чтобы случайно не перезаписать уже загруженный файл. Если такого названия ещё нет, функция сгенерирует его. Такая проблема появляется в больших проектах и с большим количеством картинок. Но всё же)
function getRandomFileName($path)
{
$path = $path ? $path . '/' : '';
do {
$name = md5(microtime() . rand(0, 9999));
$file = $path . $name;
} while (file_exists($file));
return $name;
}
Генерация имени для картинки теперь будет такой:
// Сгенерируем новое имя файла через функцию getRandomFileName()
$name = getRandomFileName($fileTmpName);
Мы реализовали простой, но в тоже время практичный (с точки зрения безопасности) механизм загрузки файла на сервер. Весь код целиком лежит здесь .
Загрузка нескольких файлов на сервер из формы
Разберём механизм загрузки нескольких изображений за один раз с локальной машины пользователя. Продолжим дальше работать с $_FILES
. Наша новая HTML-форма будет немного отличаться от старой.
<form action="upload.php" method="post" enctype="multipart/form-data">
<input type="file" name="images[]" multiple>
<button type="submit">Загрузить</button>
</form>
Как видно в конец имени поля выбора файла name="images[]"
добавились фигурные скобки и атрибут multiple
, который разрешает браузеру выбрать несколько файлов. Все файлы снова загрузятся во временную папку, если не будет никаких ошибок в php.ini
. Перехватить их можно в $_FILES
, но на этот раз суперглобальная переменная будет иметь неудобную структуру для обработки данных в массиве. Решается эта задача небольшими манипуляциями с массивом:
// Изменим структуру $_FILES
foreach($_FILES['images'] as $key => $value) {
foreach($value as $k => $v) {
$_FILES['images'][$k][$key] = $v;
}
// Удалим старые ключи
unset($_FILES['images'][$key]);
}
// Загружаем все картинки по порядку
foreach ($_FILES['images'] as $k => $v) {
// Загружаем по одному файлу
$fileTmpName = $_FILES['images'][$k]['tmp_name'];
$errorCode = $_FILES['images'][$k]['error'];
// Проверим на ошибки
// ...
}
Мы реализовали механизм загрузки нескольких файлов на сервер. Весь код целиком лежит здесь .
Комментарии (0)
Пока еще не было комментариев ✍️