Профессиональная разработка Web-приложений.  
Боишься нашего дизайна?
Новости
PDF журнал
Участники проектa
Сотрудничество
Ссылки
Карта сайта
Комментарии
Комментарии к статье
Добавить комментарий
Обсудить на форуме
Информация об авторе
Оценка статьи

Upload файлов, и все с этим связанное

Самую свежую документацию, массу примеров и первоисточники можно найти на сайте php

Краткий эксурс в upload

Что такое Upload files, или почему не работает
copy ("c:\images\sample.jpg", "c:\uploads\ sample.jpg ")

Даже если у Вас в распоряжении всего один компьютер, на котором совмещен и сервер и рабочая станция, не стоит забывать о том, что php использует технологию клиент/сервер. Файл, который мы хотим загрузить, как правило, находится на машине клиента, т.е. пользователя, обыкновенного посетителя сайта. Место назначения - сервер. Для того чтобы совершить процесс передачи файла, нам понадобиться следующая форма:

<form enctype="multipart/form-data" action="/upload.php" method="post">
<input type="hidden" name="MAX_FILE_SIZE" value="30000">
Send this file: <input name="userfile" type="file">
<input type="submit" value="Send File">
</form>

При этом в поле action должен быть указан URL Вашего php-скрипта, который в дальнейшем будет заниматься обработкой загружаемых файлов. Скрытое поле MAX_FILE_SIZE должно предшествовать полю выбора файла, и содержать максимально допустимый размер файла в байтах. Его назначение - проверка размера файла еще до момента отправки файла на сервер. Это должно избавить пользователя от длительной и безрезультатной загрузки файла на сервер и образования лишнего трафика, но не стоит особо полагаться на это ограничение, так как его легко обойти.

Что происходит, когда пользователь выбрал файл на своем диске, и нажал на кнопку "Send file"? Браузер отсылает файл на сервер, где php-интерпретатор помещает его в свою временную директорию, присваивая ему случайное имя и выполняет скрипт, указанный в поле action.

Как должен выглядеть upload.php?

<?php
$uploaddir = '/var/www/uploads/';
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploaddir . 
	$_FILES['userfile']['name'])) {
    print "File is valid, and was successfully uploaded.";
} else {
    print "There some errors!";
}
?>

При написании скрипта, возникает естественный вопрос: как получить информацию о загруженном файле и достучаться до самого файла. Если Вы используете PHP версии 4.1.0 и старше, лучше всего будет обратиться к глобальному массиву $_FILES. Для каждого загруженного файла он содержит хеш-массив, со следующими данными:

  • $_FILES['userfile']['name'] - оригинальное имя файла, такое, каким его видел пользователь, выбирая файл;
  • $_FILES['userfile']['type'] - mime/type файла, к примеру, может быть image/gif; это поле полезно сохранить, если Вы хотите предоставлять интерфейс для скачивания загруженных файлов;
  • $_FILES['userfile']['size'] - размер загруженного файла;
  • $_FILES['userfile']['tmp_name'] - полный путь к временному файлу на диске;
  • $_FILES['userfile']['error'] - Начиная с версии 4.2.0, содержит код ошибки, который равен 0, если операция прошла успешно.

Для PHP версии ниже 4.1.0 (Рекомендуется немедленно обновить http://www.php.net/downloads.php) этот массив называется $HTTP_POST_FILES. Не стоит забывать, что в отличие от $_FILES этот массив не является суперглобальным и при обращении к нему, к примеру, из функции, необходимо явно указывать global $HTTP_POST_FILES;

Если в настройках Вашего сервера register_globals=on, будут созданы дополнительные переменные вида $userfile_name, $userfile_type, $userfile_size… Учитывая, что, начиная с версии 4.2.0, в настройках по умолчанию register_globals=off использования этих переменных не рекомендовано, даже если они определены. Лучший способ получения информации о загружаемых файлах - использовать массив $_FILES.

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

Настройка сервера

Я все сделал правильно, но у меня что-то не работает. Может, у меня неправильно сконфигурирован сервер?

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

В файле php.ini:

  • Если Вы хотите узнать, где расположен Ваш php.ini, выполните
    <?php phpinfo(); ?>
  • file_uploads - возможность запретить или разрешить загрузку файлов в целом. По умолчанию On.
  • upload_max_filesize - максимальный размер файла, который может быть загружен. Если Вам необходимо работать с большими файлами, измените эту настройку. По умолчанию 2М. Не забудьте изменить post_max_size.
  • post_max_size - общее ограничение сверху на размер данных, передаваемых в POST запросе. Если Вам необходимо работать с большими файлами, или передавать несколько файлов одновременно, измените эту настройку. Значение по умолчанию 8М.
  • upload_tmp_dir - временная директория на сервере, в которую будут помещаться все загружаемые файлы. Проверьте, какие на нее выставлены права(если на данном этапе у Вас возникли сложности, смотрите пояснения в конце статьи). Такая директория должна существовать и у пользователя, под которым выполняется Apache, также должны быть права на запись в эту директорию. Если Вы работаете с включенным ограничением open_basedir - то временный каталог должен находиться внутри. Вам не нужно заботиться о ее чистке или об уникальности имен, PHP решает эту проблему за Вас.

В файле httpd.conf:

  • Прежде всего, убедитесь, что Вы используете веб-сервер Apache 1.3 (последняя версия на момент написания статьи - 1.3.27). Если Вы используете Apache 2.0, Вам следует прочитать следующий отрывок из документации:

    Do not use Apache 2.0 and PHP in a production environment neither on Unix nor on Windows.

  • Если Вы получили сообщение "POST Method Not Allowed", это означает, что надо искать что-то похожее на следующие директивы, и использовать ключевое слово Allow:
    <Limit POST >
    	Order allow,deny
    	Allow from all
    </Limit>
  • Проблемы с загрузкой бинарных файлов - классический вопрос "почему бьются файлы при upload". Вот способ решения, предложенный Димой Бородином (http://php.spb.ru): В директории, где лежит скрипт, делаем файл .htaccess, в котором пишем: CharsetDisable On. В файл httpd.conf дописать строки:
    <Location />
    	CharsetRecodeMultipartForms Off
    </Location>

Небольшие пояснения, к этому рецепту: вышеописанная проблема, когда загруженные на сервер архивы не распаковываются и картинки не отображаются, может возникать из-за того, что используется веб-сервер Russian Apache. Директива CharsetDisable отключает модуль charset-processing module, т.е. никакой перекодировки при скачивании файлов, находящихся в данной папке, происходить не будет. Директива CharsetRecodeMultipartForms выключает перекодировку данных, переданных методом POST с заголовком Content-Type: multipart/form-data. Т.е. двоичные данные, переданные с такой настройкой, будут оставлены в первоначальном виде, а все остальное наполнение сайта будет перекодировано согласно текущим настройкам сервера.

Но при этом могут возникнуть осложнения: будьте готовы к тому, что в некоторых случаях текстовые части запросов вам придется перекодировать самостоятельно. Вот что по этому поводу говорится в документации:

Используйте директиву CharsetRecodeMultipartForms, которая появилась в PL23, но при этом вам все-равно придется перекодировать вручную текстовые части запросов. Для этого можно использовать Russian Apache API, доступное в других модулях или Russian Apache Perl API, доступное из mod_perl.

Один из примеров определения кодировки вы можете найти тут: http://tony2001.phpclub.net/detect_charset/detect.phps

Самая свежая документация по Russian Apache находится на его официальном сайте: http://apache.lexa.ru/.

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

Дополнительные возможности

Я хочу сделать вот такую штуку, но у меня никак не получается...

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

На самом деле в этом нет никакой трудности. Этого можно достичь, используя, к примеру, вот такую форму:

<form action="file-upload.php" method="post" enctype="multipart/form-data">
  Send these files:<br>
  <input name="userfile[]" type="file"><br>
  <input name="userfile[]" type="file"><br>
  <input type="submit" value="Send files">
</form>

И не забудьте увеличить post_max_size, если предполагается много файлов

Автоматическая загрузка файлов на сервер

Не стоит забывать, что файлы на диске пользователя - конфиденциальная информация, к которой ни JavaScript, ни уж тем более PHP не имеют ни малейшего отношения. До тех пор, пока пользователь сам не выбрал файл при помощи <input type="file"> ни о какой работе с ним не может идти и речи. И не забывайте, что у данного поля ввода атрибут value защищен от записи.

Хранение файлов в базе данных mySQL

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

  • Необходимо использовать поле типа BLOB
  • Перед тем, как класть в базу, не забыть применить к строке mysql_escape_string
  • При отображении файла необходимо указывать заголовок content/type

Помните, что скрипт отображающий ваш HTML никак не связан со скриптом, который должен выводить изображение. Это должны быть два различные приложения.

Хранение картинок в базе не является хорошем стилем. Гораздо удобней хранить в базе лишь пути к файлам изображений.

Получение свойств изображения.

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

Загрузка файлов, имеющих русско-язычное название

При загрузке на сервер файлов, необходимо проверять их оригинальные имена на предмет наличия "нестандартных" символов (к примеру русских букв). В случае их присутствия необходимо произвести замену. Оригинальное имя файла можно найти в переменной $_FILES['userfile']['name']. Про то, как перекодировать русскоязычную строку в транслит можно можно найти в архивах практически любого форума, посвященного php.

Отображения статуса загрузки или progress bar

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

Краткий очерк о правах на файлы

Проблемы с правами на сервере (upload_tmp_dir)

В *nix-подобных операционных системах каждой папке, файлу, ссылке выставлены соответствие права доступа. Они могут выглядеть как rwx-rw-r- или же как число 754.

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

   Владелец  Группа  Прочие
     (u)       (g)     (o)
     rwx       rwx     rwx

Здесь владелец, члены группы и все прочие пользователи обладают правами чтения файла, записи в него и его выполнения. Права - любая осмысленная комбинация следующих букв:

r 	Право на чтение. (4)
w 	Право на запись. (2)
x 	Право на выполнение (поиск в каталоге). (1)

Для того, что бы загрузка файлов на сервер работала корректно, необходимо реализовать один из двух вариантов

  • Установить владельцем каталога пользователя, с чьими привелегиями выполняется apache. Это можно узнать из файла httpd.conf или просмотрев список процессов на сервере. Права на каталог должны быть 700 (rwx------).
  • Независимо от того, кто является владельцем каталога, установить права 777 (rwxrwxrwx).

    Пример реализации загрузки картинок на сервер.

    <?
    $max_image_width	= 380;
    $max_image_height	= 600;
    $max_image_size		= 64 * 1024;
    $valid_types 		=  array("gif","jpg", "png", "jpeg");
    
    if (isset($_FILES["userfile"])) {
    	if (is_uploaded_file($_FILES['userfile']['tmp_name'])) {
    		$filename = $_FILES['userfile']['tmp_name'];
    		$ext = substr($_FILES['userfile']['name'], 
    			1 + strrpos($_FILES['userfile']['name'], "."));
    		if (filesize($filename) > $max_image_size) {
    			echo 'Error: File size > 64K.';
    		} elseif (!in_array($ext, $valid_types)) {
    			echo 'Error: Invalid file type.';
    		} else {
     			$size = GetImageSize($filename);
     			if (($size) && ($size[0] < $max_image_width) 
    				&& ($size[1] < $max_image_height)) {
    				if (@move_uploaded_file($filename, "/www/htdocs/upload/")) {
    					echo 'File successful uploaded.';
    				} else {
    					echo 'Error: moving fie failed.';
    				}
    			} else {
    				echo 'Error: invalid image properties.';
    			}
    		}
    	} else {
    		echo "Error: empty file.";
    	}
    } else {
    	echo  '
    	<form enctype="multipart/form-data" method="post"> 
    	<input type="hidden" name="MAX_FILE_SIZE" value="64000"> 
    	Send this file: <input name="userfile" type="file"> 
    	<input type="submit" value="Send File"> 
    	</form>';
    }
    ?>
    

    Еще один прмер реализации, с использованием PEAR (подсказан kvn-ом). Оригинальный пакет находится по адресу: http://pear.php.net/packages/HTTP_Upload

    <html><body>
    <form action="<?php echo $HTTP_SERVER_VARS['PHP_SELF'];?>?submit=1" 
    	method="post" enctype="multipart/form-data">
       Send these files:<br>
      <INPUT TYPE="hidden" name="MAX_FILE_SIZE" value="100000">
       
       <input name="userfile" type="file"> <-<br>
       <input name="otherfile[]" type="file"><br>
       <input name="otherfile[]" type="file"><br>
       <input type="submit" value="Send files">
    </form>
    </body></html>
    <?php
    error_reporting(E_ALL);
    if (!isset($submit)) {
        exit;
    }
    require 'HTTP/Upload.php';
    echo '<pre>';
    //print_r($HTTP_POST_FILES);
    $upload = new http_upload('es');
    $file = $upload->getFiles('userfile');
    if (PEAR::isError($file)) {
        die ($file->getMessage());
    }
    if ($file->isValid()) {
        $file->setName('uniq');
        $dest_dir = './uploads/';
        $dest_name = $file->moveTo($dest_dir);
        if (PEAR::isError($dest_name)) {
            die ($dest_name->getMessage());
        }
        $real = $file->getProp('real');
        echo "Uploaded $real as $dest_name in $dest_dir\n";
    } elseif ($file->isMissing()) {
        echo "No file selected\n";
    } elseif ($file->isError()) {
        echo $file->errorMsg() . "\n";
    }
    print_r($file->getProp());
    echo '</pre>';
    ?>
    




  • For comment register here
       2003-11-27 21:47
    Хорошая статья, только честно говоря, я это уже все давно прошел :)
    Только вот интересовал один вопрос, который был мельком затронут, про права на папку 777.
    Вопрос: Если поставить права 777 на папку, то сможет ли какой-нибудь перл-скрипт или прога на "C" другого аккаунта на хостинг-площадке получить доступ к этой папке (удалить ее, переименовать и т.д.)? В PHP есть соотетсвующий параметр, который не даст управлять файлами за пределами директории, указанной в этом параметре. А вот с перлом как быть? Есть ли какая-то защита от угрозы "потерять" папку с правами 777? Понимаю, что вопрос больше по UNIX, а не по PHP, но все таки вопрос актуальный.

       2003-11-28 11:10
    Для того, что бы такой беды не было, права надо ставить 755 либо 700 и каждый VirtualHost запускать под своим UID-ом. Владелец у папки должен быть соответствующий

       2003-11-28 16:43
    2young, так и делаю если имеется доступ к httpd.conf, изменить не проблема.
    Но если доступа нет, тогда придется конечно повозиться - узнать под каким именем и группой запускается апач, потом сменить владельца, группу на папку. Ладно. Разобрался, спасибо.

       2003-12-02 15:50
    насколько мне известна такая проблема решается на уровне CGIwrap или suexec на уровне сис админа.

       2004-01-08 05:28
    Наконец-то нашёл! Много интересного почерпнул. Но не нашёл ответ на свою проблему. У меня не аплодятся файлы более (примерно) 6 Кб. Ниже 6 Кб всё нормально. Где копать не могу понять. Apache/1.3.22 (Win32). PHP Ver. 4.0.4. Если проблема знакома. Ответьте пожалуйста.

       2004-02-05 19:29
    В php.ini устанавливается ограничение на размер файла для аплоада... там и смотри

       2004-02-06 15:11
    Статья хорошая, но я долго присматривался и заметил ошибку, поправьте если не так @move_uploaded_file($filename, "/www/htdocs/upload/")) здесь отсутствует имя файла в директории upload. По-моему лучше делать так как в мануале:
    $uploaddir = '/www/htdocs/upload/';
    $uploadfile = $uploaddir.$_FILES['userfile']['name'];
    @move_uploaded_file($filename, $uploadfile);
    ну или менять имя фала на нужное.

       2004-06-01 16:54
    Вопрос такой, допустим написанна прога на бейсике которая посылает файлы на веб-сервер, можно ли сделать приемник на пхп, подскажите пожалуйста, очень надо. огромное спасибо.

       2004-06-03 13:54
    2Valery: Конечно можно.
    Если VB Application правильно посылает файлы на сервер, согласно всем стандартам, используйте стандартный код для загрузки файлов.

       2004-08-05 12:19
    подскажите, а как быть с загрузкой на сервер флеш-файлов? клиент желает с закачивать не только картинки .GIF или .JPEG в раздел, а еще и файлы .SWF

       2004-08-12 19:17
    1) Зачем делать filesize($filename) если есть переменная $_FILES['userfile']['size']? Не ясно.

    2) Попробуйте в поле запроса ввести имя файла с расширением, что присутствует в массиве в вышеприведённом примере или просто указать неверный путь к изображению. Например vasya.gif. Теперь нажмите кнопку подачи запроса. Произойдёт ошбка, т.к. PHP создаст временный файл с размером = 0, а в вышепрведённом сценарии не проверяется временный файл на размер. Следовательно:

    Warning: getimagesize() [function.getimagesize]: Read error! in c:\internet\home\localhost\1.php on line 19
    Error: invalid image properties.

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

    if(isset($_FILES["product_foto"]) && is_uploaded_file($_FILES["product_foto"]["tmp_name"]) && $_FILES["product_foto"]["size"]){
    //работаем с файлом

       2004-10-02 15:26
    Хорошая статья, внятно все изложено. Спасибо.

       2004-10-13 15:25
    А что значит "будьте готовы к тому, что в некоторых случаях текстовые части запросов вам придется перекодировать самостоятельно"
    Разъясните, плиз.

       2004-10-17 10:26
    Думаю, вместо проверки по расширению файла проще (в том числе и безопаснее) будет проверять его тип (листинг1):
    $_FILES['userfile']['type']

       2005-01-05 14:22
    Лично мне статья принесла большую пользу: понял, почему у меня "битые" файлы на сервер ложатся, как ни изворачивайся.
    Единственное, что бы я в статье уточнил для совсем начинающих, это про файл .htaccess.

    Достаточно создать такой файл (пустой) в ЛЮБОЙ директории (куда помещается php-скрипт), внести в него (без всяких <Locale />) строку: CharsetRecodeMultipartForms Off
    И ВСЕ БУДЕТ НОРМАЛЬНО. По крайней мере, мне это помогло. Быстро, легко и эффективно.

    Спасибо за объяснение проблемы. Я бы еще долго бился, как рыба об лед, если бы не такие статьи в интернете.

    Удачи! :-)))

       2005-02-28 02:27
    Цогласен с Ktulhu есть очепятка ...
    if (@move_uploaded_file($filename, "/www/htdocs/upload/")) {
    надо
    if (@move_uploaded_file($filename, "/www/htdocs/upload/".$_FILES['userfile']['name'])) {

       2005-04-13 07:11
    Как отобразить статус процесса (например %) закачки на сервер? Есть какие-нибудь дополнительные модули для реализации? Как я понял, стандартными средствами это невозможно сделать.

       2007-01-10 17:39
    &gt;&gt;Как отобразить статус процесса (например %) закачки на сервер? Есть какие-нибудь дополнительные модули &gt;&gt;для реализации? Как я понял, стандартными средствами это невозможно сделать.
    Javascript, AJAX, Flash

       2007-03-03 20:53
    Статья устарела, теперь прогресс бар средствами php возможен:
    http://progphp.com/progress.phps (исходник)
    http://progphp.com/progress.php (пример)

       2007-11-30 17:50
    Kronos открой исходник и посмотри...везде js...

    Загрузка файла на сервер. Возможности. Примеры. Настройка сервера. upload, php, apache, проблема, пример загрузки, загрузка изобраджения, сервер, русский апач, права на файлы

     
     
     
        © 1997-2008 PHPClubTeam
    []