PHP

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

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

Пример формы:

Форма отправит файл только методом POST и с атрибутом enctype="multipart/form-data".

<form action="/upload.php" method="post" enctype="multipart/form-data">
    <input type="file" name="file">
    <input type="submit" value="Отправить">
</form>

Форма для загрузки сразу нескольких файлов:

<form action="/upload.php" method="post" enctype="multipart/form-data">
    <input type="file" name="file[]" multiple>
    <input type="submit" value="Отправить">
</form>

Файл upload.php:

  • Поддерживает как одиночную загрузку файла так и множественную (multiple) без изменения кода.
  • Проверка на все возможные ошибки которые могут возникнуть при загрузке файлов.
  • Имена файлов переводятся в транслит и удаляются символы которые будут в дальнейшем мешать вывести их на сайте.
  • Есть возможность указать разрешенные и запрещенные для загрузки расширения файлов.
// Название <input type="file">
$input_name = 'file';

// Разрешенные расширения файлов.
$allow = array();

// Запрещенные расширения файлов.
$deny = array(
    'phtml', 'php', 'php3', 'php4', 'php5', 'php6', 'php7', 'phps', 'cgi', 'pl', 'asp', 
    'aspx', 'shtml', 'shtm', 'htaccess', 'htpasswd', 'ini', 'log', 'sh', 'js', 'html', 
    'htm', 'css', 'sql', 'spl', 'scgi', 'fcgi'
);

// Директория куда будут загружаться файлы.
$path = __DIR__ . '/uploads/';

if (isset($_FILES[$input_name])) {
    // Проверим директорию для загрузки.
    if (!is_dir($path)) {
        mkdir($path, 0777, true);
    }

    // Преобразуем массив $_FILES в удобный вид для перебора в foreach.
    $files = array();
    $diff = count($_FILES[$input_name]) - count($_FILES[$input_name], COUNT_RECURSIVE);
    if ($diff == 0) {
        $files = array($_FILES[$input_name]);
    } else {
        foreach($_FILES[$input_name] as $k => $l) {
            foreach($l as $i => $v) {
                $files[$i][$k] = $v;
            }
        }        
    }    
    
    foreach ($files as $file) {
        $error = $success = '';

        // Проверим на ошибки загрузки.
        if (!empty($file['error']) || empty($file['tmp_name'])) {
            switch (@$file['error']) {
                case 1:
                case 2:  $error = 'Превышен размер загружаемого файла.'; break;
                case 3:  $error = 'Файл был получен только частично.'; break;
                case 4:  $error = 'Файл не был загружен.'; break;
                case 6:  $error = 'Файл не был загружен - отсутствует временная директория.'; break;
                case 7:  $error = 'Не удалось записать файл на диск.'; break;
                case 8:  $error = 'PHP-расширение остановило загрузку файла.'; break;
                case 9:  $error = 'Файл не был загружен - директория не существует.'; break;
                case 10: $error = 'Превышен максимально допустимый размер файла.'; break;
                case 11: $error = 'Данный тип файла запрещен.'; break;
                case 12: $error = 'Ошибка при копировании файла.'; break;
                default: $error = 'Файл не был загружен - неизвестная ошибка.'; break;
            }
        } elseif ($file['tmp_name'] == 'none' || !is_uploaded_file($file['tmp_name'])) {
            $error = 'Не удалось загрузить файл.';
        } else {
            // Оставляем в имени файла только буквы, цифры и некоторые символы.
            $pattern = "[^a-zа-яё0-9,~!@#%^-_\$\?\(\)\{\}\[\]\.]";
            $name = mb_eregi_replace($pattern, '-', $file['name']);
            $name = mb_ereg_replace('[-]+', '-', $name);
            
            // Т.к. есть проблема с кириллицей в названиях файлов (файлы становятся недоступны).
            // Сделаем их транслит:
            $converter = array(
                'а' => 'a',   'б' => 'b',   'в' => 'v',    'г' => 'g',   'д' => 'd',   'е' => 'e',
                'ё' => 'e',   'ж' => 'zh',  'з' => 'z',    'и' => 'i',   'й' => 'y',   'к' => 'k',
                'л' => 'l',   'м' => 'm',   'н' => 'n',    'о' => 'o',   'п' => 'p',   'р' => 'r',
                'с' => 's',   'т' => 't',   'у' => 'u',    'ф' => 'f',   'х' => 'h',   'ц' => 'c',
                'ч' => 'ch',  'ш' => 'sh',  'щ' => 'sch',  'ь' => '',    'ы' => 'y',   'ъ' => '',
                'э' => 'e',   'ю' => 'yu',  'я' => 'ya', 
            
                'А' => 'A',   'Б' => 'B',   'В' => 'V',    'Г' => 'G',   'Д' => 'D',   'Е' => 'E',
                'Ё' => 'E',   'Ж' => 'Zh',  'З' => 'Z',    'И' => 'I',   'Й' => 'Y',   'К' => 'K',
                'Л' => 'L',   'М' => 'M',   'Н' => 'N',    'О' => 'O',   'П' => 'P',   'Р' => 'R',
                'С' => 'S',   'Т' => 'T',   'У' => 'U',    'Ф' => 'F',   'Х' => 'H',   'Ц' => 'C',
                'Ч' => 'Ch',  'Ш' => 'Sh',  'Щ' => 'Sch',  'Ь' => '',    'Ы' => 'Y',   'Ъ' => '',
                'Э' => 'E',   'Ю' => 'Yu',  'Я' => 'Ya',
            );

            $name = strtr($name, $converter);
            $parts = pathinfo($name);

            if (empty($name) || empty($parts['extension'])) {
                $error = 'Недопустимое тип файла';
            } elseif (!empty($allow) && !in_array(strtolower($parts['extension']), $allow)) {
                $error = 'Недопустимый тип файла';
            } elseif (!empty($deny) && in_array(strtolower($parts['extension']), $deny)) {
                $error = 'Недопустимый тип файла';
            } else {
                // Чтобы не затереть файл с таким же названием, при необходимости добавим префикс.
                $i = 0;
                $prefix = '';
                while (is_file($path . $parts['filename'] . $prefix . '.' . $parts['extension'])) {
                      $prefix = '(' . ++$i . ')';
                }
                $name = $parts['filename'] . $prefix . '.' . $parts['extension'];

                // Перемещаем файл в директорию.
                if (move_uploaded_file($file['tmp_name'], $path . $name)) {
                    // Далее можно сохранить название файла в БД и т.п.
                    $success = 'Файл «' . $name . '» успешно загружен.';
                } else {
                    $error = 'Не удалось загрузить файл.';
                }
            }
        }
        
        // Выводим сообщение о результате загрузки.
        if (!empty($success)) {
            echo '<p>' . $success . '</p>';        
        } else {
            echo '<p>' . $error . '</p>';
        }
    }
}

Загрузка файлов через AJAX

Файлы можно отправить без перезагрузки страницы с помощью jQuery Form Plugin.

Как это работает:

  1. На поле выбора файла с id="js-file" повешено jquery событие change.
  2. При выборе файла срабатывает событие и выполняется метод плагина ajaxSubmit, он все поля из формы с id="js-form" включая выбранный файл отправляет на uploads.php.
  3. Далее то что отдаст uploads.php выведется в <div id="result">...</div>.
  4. После завершения, форма очищается методом reset() т.к. input file останется с выбранным файлом.
<form id="js-form" method="post" enctype="multipart/form-data">
    <input id="js-file" type="file" name="file[]" multiple>
</form>

<div id="result">
    <!-- Сюда выводится результат из uploads.php -->
</div>

<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//malsup.github.io/min/jquery.form.min.js"></script>

<script>
$('#js-file').change(function() {
    $('#js-form').ajaxSubmit({
        type: 'POST',
        url: '/upload.php',
        target: '#result',
        success: function() {
            // После загрузки файла очистим форму.
            $('#js-form')[0].reset();
        }
    });
});
</script>

Возможные проблемы

  • На unix хостингах php функция move_uploaded_file() не будут перемещать файлы в директорию если у нее права меньше 777.
  • Загрузка файлов может быть отключена в настройках PHP директивой file_uploads.
  • Не загружаются файлы большого размера, причина в ограничениях хостинга.
    Посмотрите в phpinfo() значения директив:
    • upload_max_filesize – максимальный размер закачиваемого файла.
    • max_file_uploads – максимальное количество одновременно закачиваемых файлов.
    • post_max_size – максимально допустимый размер данных, отправляемых методом POST, его значение должно быть больше upload_max_filesize.
    • memory_limit – значение должно быть больше чем post_max_size.

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

25 ноября 2017
В статье приведены основные примеры работы с расширением PHP PDO. Такие как подключение к БД, получение, изменение и...
В статье описана регистрация клиента, получения access token, примеры использования методов API Instagram на PHP.
Пример как сформировать товарную накладную в с помощью класса PHPExcel
Инструкция как получить бессрочный access token Facebook, запросы к Graph API, отправка сообщения на стену FB.
Можно найти множество применений Яндекс Диска на своем сайте, например, хранение бекапов и отчетов, обновление прайсов,...
Для начала вы должны быть авторизированы в VK и являться администратором группы. Далее нужно создать приложение...