Php function на загрузку файла

Отдаем файлы эффективно с помощью PHP

Метод хорош тем, что работает с коробки. Надо только написать свою функцию отправки файла (немного измененный пример из официальной документации):

function file_force_download($file) < if (file_exists($file)) < // сбрасываем буфер вывода PHP, чтобы избежать переполнения памяти выделенной под скрипт // если этого не сделать файл будет читаться в память полностью! if (ob_get_level()) < ob_end_clean(); >// заставляем браузер показать окно сохранения файла header('Content-Description: File Transfer'); header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename=' . basename($file)); header('Content-Transfer-Encoding: binary'); header('Expires: 0'); header('Cache-Control: must-revalidate'); header('Pragma: public'); header('Content-Length: ' . filesize($file)); // читаем файл и отправляем его пользователю readfile($file); exit; > > 

Таким способом можно отправлять даже большие файлы, так как PHP будет читать файл и сразу отдавать его пользователю по частям. В документации четко сказано, что readfile() не должен создавать проблемы с памятью.

  • Скрипт ждет пока весь файл будет прочитан и отдан пользователю.
  • Файл читается в внутренний буфер функции readfile(), размер которого составляет 8кБ (спасибо 2fast4rabbit)

2. Читаем и отправляем файл вручную

Метод использует тот же Drupal при отправке файлов из приватной файловой системы (файлы недоступны напрямую по ссылкам):

function file_force_download($file) < if (file_exists($file)) < // сбрасываем буфер вывода PHP, чтобы избежать переполнения памяти выделенной под скрипт // если этого не сделать файл будет читаться в память полностью! if (ob_get_level()) < ob_end_clean(); >// заставляем браузер показать окно сохранения файла header('Content-Description: File Transfer'); header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename=' . basename($file)); header('Content-Transfer-Encoding: binary'); header('Expires: 0'); header('Cache-Control: must-revalidate'); header('Pragma: public'); header('Content-Length: ' . filesize($file)); // читаем файл и отправляем его пользователю if ($fd = fopen($file, 'rb')) < while (!feof($fd)) < print fread($fd, 1024); >fclose($fd); > exit; > > 
  • Скрипт ждет пока весь файл будет прочитан и отдан пользователю.
  • Позволяет сэкономить память сервера
Читайте также:  Javascript function global object

3. Используем модуль веб сервера

3a. Apache

Модуль XSendFile позволяет с помощью специального заголовка передать отправку файла самому Apache. Существуют версии по Unix и Windows, под версии 2.0.*, 2.2.* и 2.4.*

В настройках хоста нужно включить перехват заголовка с помощью директивы:

Также можно указать белый список директорий, файлы в которых могут быть обработаны. Важно: если у Вас сервер на базе Windows путь должен включать букву диска в верхнем регистре.

Описание возможных опций на сайте разработчика: https://tn123.org/mod_xsendfile/

function file_force_download($file) < if (file_exists($file)) < header('X-SendFile: ' . realpath($file)); header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename=' . basename($file)); exit; >> 
3b. Nginx

Nginx умеет отправлять файлы из коробки через специальный заголовок.

Для корректной работы нужно запретить доступ к папку напрямую через конфигурационный файл:

Пример отправки файла (файл должен находиться в директории /some/path/protected):

function file_force_download($file) < if (file_exists($file)) < header('X-Accel-Redirect: ' . $file); header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename=' . basename($file)); exit; >> 

Больше информации на странице официальной документации

  • Скрипт завершается сразу после выполнения всех инструкций
  • Физически файл отправляется модулем самого веб сервера, а не PHP
  • Минимальное потребление памяти и ресурсов сервера
  • Максимальное быстродействие

Update: Хабраюзер ilyaplot дает дельный совет, что лучше слать не application/octet-stream , а реальный mime type файла. Например, это позволит браузеру подставить нужные программы в диалог сохранение файла.

Источник

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

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

В статье приведен пример формы и php-скрипта для безопасной загрузки файлов на сервер, возможные ошибки и рекомендации при работе с данной темой. HTML-форма отправит файл только методом POST и с атрибутом enctype=»multipart/form-data» .

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

Файл upload.php

  • Поддерживает как одиночную загрузку файла так и множественную (multiple) без изменения кода.
  • Проверка на все возможные ошибки которые могут возникнуть при загрузке файлов.
  • Имена файлов переводятся в транслит и удаляются символы которые будут в дальнейшем мешать вывести их на сайте.
  • Есть возможность указать разрешенные и запрещенные для загрузки расширения файлов.
// Название $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 '

' . $success . '

'; > else < echo '

' . $error . '

'; > > >

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

  • На 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.

    Источник

    Php function на загрузку файла

    I think the way an array of attachments works is kind of cumbersome. Usually the PHP guys are right on the money, but this is just counter-intuitive. It should have been more like:

    Array
    (
    [0] => Array
    (
    [name] => facepalm.jpg
    [type] => image/jpeg
    [tmp_name] => /tmp/phpn3FmFr
    [error] => 0
    [size] => 15476
    )

    Anyways, here is a fuller example than the sparce one in the documentation above:

    foreach ( $_FILES [ «attachment» ][ «error» ] as $key => $error )
    $tmp_name = $_FILES [ «attachment» ][ «tmp_name» ][ $key ];
    if (! $tmp_name ) continue;

    $name = basename ( $_FILES [ «attachment» ][ «name» ][ $key ]);

    if ( $error == UPLOAD_ERR_OK )
    if ( move_uploaded_file ( $tmp_name , «/tmp/» . $name ) )
    $uploaded_array [] .= «Uploaded file ‘» . $name . «‘.
    \n» ;
    else
    $errormsg .= «Could not move uploaded file ‘» . $tmp_name . «‘ to ‘» . $name . «‘
    \n» ;
    >
    else $errormsg .= «Upload error. [» . $error . «] on file ‘» . $name . «‘
    \n» ;
    >
    ?>

    Do not use Coreywelch or Daevid’s way, because their methods can handle only within two-dimensional structure. $_FILES can consist of any hierarchy, such as 3d or 4d structure.

    The following example form breaks their codes:

    As the solution, you should use PSR-7 based zendframework/zend-diactoros.

    use Psr \ Http \ Message \ UploadedFileInterface ;
    use Zend \ Diactoros \ ServerRequestFactory ;

    $request = ServerRequestFactory :: fromGlobals ();

    if ( $request -> getMethod () !== ‘POST’ ) http_response_code ( 405 );
    exit( ‘Use POST method.’ );
    >

    $uploaded_files = $request -> getUploadedFiles ();

    if (
    !isset( $uploaded_files [ ‘files’ ][ ‘x’ ][ ‘y’ ][ ‘z’ ]) ||
    ! $uploaded_files [ ‘files’ ][ ‘x’ ][ ‘y’ ][ ‘z’ ] instanceof UploadedFileInterface
    ) http_response_code ( 400 );
    exit( ‘Invalid request body.’ );
    >

    $file = $uploaded_files [ ‘files’ ][ ‘x’ ][ ‘y’ ][ ‘z’ ];

    if ( $file -> getError () !== UPLOAD_ERR_OK ) http_response_code ( 400 );
    exit( ‘File uploading failed.’ );
    >

    $file -> moveTo ( ‘/path/to/new/file’ );

    The documentation doesn’t have any details about how the HTML array feature formats the $_FILES array.

    Array
    (
    [document] => Array
    (
    [name] => sample-file.doc
    [type] => application/msword
    [tmp_name] => /tmp/path/phpVGCDAJ
    [error] => 0
    [size] => 0
    )
    )

    Multi-files with HTML array feature —

    Array
    (
    [documents] => Array
    (
    [name] => Array
    (
    [0] => sample-file.doc
    [1] => sample-file.doc
    )

    [type] => Array
    (
    [0] => application/msword
    [1] => application/msword
    ) [tmp_name] => Array
    (
    [0] => /tmp/path/phpVGCDAJ
    [1] => /tmp/path/phpVGCDAJ
    )

    The problem occurs when you have a form that uses both single file and HTML array feature. The array isn’t normalized and tends to make coding for it really sloppy. I have included a nice method to normalize the $_FILES array.

    function normalize_files_array ( $files = [])

    foreach( $files as $index => $file )

    if (! is_array ( $file [ ‘name’ ])) $normalized_array [ $index ][] = $file ;
    continue;
    >

    foreach( $file [ ‘name’ ] as $idx => $name ) $normalized_array [ $index ][ $idx ] = [
    ‘name’ => $name ,
    ‘type’ => $file [ ‘type’ ][ $idx ],
    ‘tmp_name’ => $file [ ‘tmp_name’ ][ $idx ],
    ‘error’ => $file [ ‘error’ ][ $idx ],
    ‘size’ => $file [ ‘size’ ][ $idx ]
    ];
    >

    ?>

    The following is the output from the above method.

    Array
    (
    [document] => Array
    (
    [0] => Array
    (
    [name] => sample-file.doc
    [type] => application/msword
    [tmp_name] => /tmp/path/phpVGCDAJ
    [error] => 0
    [size] => 0
    )

    [documents] => Array
    (
    [0] => Array
    (
    [name] => sample-file.doc
    [type] => application/msword
    [tmp_name] => /tmp/path/phpVGCDAJ
    [error] => 0
    [size] => 0
    ) [1] => Array
    (
    [name] => sample-file.doc
    [type] => application/msword
    [tmp_name] => /tmp/path/phpVGCDAJ
    [error] => 0
    [size] => 0
    )

    Источник

Оцените статью