PHP Урок 18. Загрузка файлов на сайт по средствам PHP
Предыдущие уроки:
Программируем на PHP - Введение
PHP - Запросы от браузера к серверу
PHP - Как работает сервер
PHP - Урок 4. PHP - интерпретатор
PHP - Урок 5. Переменные сервера и глобальные переменные
PHP - Урок 6. Конструкции print и echo. Кавычки одинарные и двойные и конкатенация строк
PHP - Урок 7. Переменные, константы и условия
PHP - Урок 8. Точка входа в приложение. Настройка mod_rewrite и файл .htaccess
PHP - Урок 9. Массивы и switch. Кодим основной каркас
PHP - Урок 10. COOKIE
PHP - Урок 11. Функции. Добавляем ядро системы core.php
PHP - Урок 12. Обзор модели MVC. Добавляем шаблоны страниц в наше приложение
PHP - Урок 13. Введение в базы данных и SQL. СУБД MySQL. Подключаемся к БД из нашего приложения
PHP - Урок 14. Регистрация пользователей на сайте
PHP Урок 15. Авторизация пользователей
PHP. Урок 16. Проверка авторизации. Функция check()
PHP Урок 17. Добавляем CSS фреймворк Bootstrap и jQuery
Общие принципы
Сегодня загрузку файлов рассмотрим отдельно, а не в контексте нашего приложения. Так как там немного более замудренно, а нам нужно все изучить последовательно. Все вместе немного позже, когда создадим страницу user - она будет отображать и аватарку и данные пользователя, которые будут в отдельной таблице. Сегодня я думаю нужно просто рассмотреть как вообще происходит загрузка разных файлов на сервер. Ну для начала кратко рассмотрим как вообще данные могут передаваться по сети. Когда один компьютер хочет передать данные другому компьютеру через интернет, то с точки зрения программиста он может сделать то же самое что и записать данные в файл. Только в качестве файла будет имя устройства - точнее драйвера сетевой карты. Это будет хорошая попытка, даже возможно в локальной сетке появится неведомая зверушка. Правда больше ничего полезного не произойдет. Дело в том, что нужно не только отправить данные в сеть, но и отправить их в таком виде, чтобы их могли обработать устройства, входящие в компьютерную сеть (маршрутизаторы и другие компьютеры). Для этого данные должны соответствовать определенному формату протоколу. Чтобы не заморачиваться форматированием данных практически всегда используется интерфейс сокетов (от Беркли). И кстати вполне успешно. Так вот при передачи по ним данных указывается протокол и дополнительные настройки. В нашем случае используется TCP. Сам TCP по отношению к программисту работает так же как открытый файл. То есть передаваемые и получаемые по сети данные рассматриваются как потоки байт. Поверх протокола TCP работает протокол HTTP. Это текстовый протокол. Он умеет принимать данные и анализировать текстовые команды, а потом принимает решение что с ними делать и в частности какой ответ создать и переслать клиенту. Ну по этому поводу я писал ранее. Этот короткий экскурс для того, чтобы мы вспомнили, что для сервера и самой сети особой разницы нету какие данные мы по ней передаем. Текстовые или двоичные - это важно протоколам, которые лежат выше протокола TCP (или соответствующего ему протоколу транспортного уровня). Кстати двоичные файлы по сети можно передавать и в виде текста, например картинки, для этого они предварительно кодируются в некоторую последовательность символов напоминающую хеш, а затем обратно в двоичный вид. Примером может служить алгоритмы base58, base64 и т.п. А так как разницы для сети нету, что именно мы по ней передаем мы легко можем передать картинку или другой файл посредством того-же POST-запроса (специальных методов для загрузки файлов у протокола HTTP нет). Для отправки файла мы создаем в HTML нашей страницы ту же форму, но с некоторыми изменениями. В частности вот HTML-форма из нашей будущей странице user (user.tpl.php):<p>Изменить фото профиля</br>
<form action="/api/avafile.php" method="post" enctype="multipart/form-data">
<p><input type="file" name="content" /></p>
<input class="btn btn-default" type="submit" value="Поставить" />
</form>
</p>
Первое, что мы здесь можем заметить - это атрибут тега form - enctype="multipart/form-data"
. С помощью этого атрибута мы сообщаем браузеру, что собираемся передавать не текстовые, а двоичные данные (то есть какой либо файл). Чтобы браузер смог подготовиться, например, начать формирование дополнительных заголовков для POST запроса.
Второе что здесь есть это тег <input type="file" name="content" />
. Он позволяет вызвать окно выбора файла (стандартное для операционной системы). Когда вы выберите файл, то он в своем истинном двоичном виде, будет скопирован в буфер (целиком или по частям - не важно).
Из этого буфера он будет пересылаться на сервер. После того как TCP отправит пакеты с файлом на нужный сервер, дальнейшая судьба файла браузер не волнует. Что там с ним будет делать сервер абсолютно не интересно ему.
Когда сервер получает эти данные, то он смотрит заголовок (content-type) и находит, что это не просто текстовые данные, а файл. Поэтому он не формирует массив $_POST, а делает следующие 2 вещи:
Файл копируется во временную папку на сервере это может быть, например, /tmp/A1xZ9e какой нибудь. Также временную папку можно указать самостоятельно в настройках, хотя это редко требуется.
Вместо массива $_POST создается массив $_FILE в котором содержится вся необходимая информация о файле. Это двумерный массив, первый ключ - это имя, указанное в
<input type="file" name="content" />
(в данном случае content). Ну по аналогии с любым атрибутом name тега input. Чтобы различать если мы захотим одновременно прислать несколько разных файлов.
Второй ключ массива - это характеристики загруженного файла. Вот приведу основные:
// Это функции обертки - в качестве $param используется значение атрибута name тега input, описанного выше.
// изначальное имя файла (которое было у него на вашем устройстве).
function post_filename($param) {
return $_FILES[$param]['name'];
}
// Тип файла
function post_filetype($param) {
return $_FILES[$param]['type'];
}
// Размер файла
function post_filesize($param) {
return $_FILES[$param]['size'];
}
// Расположение временного файла
function post_file($param) {
return $_FILES[$param]['tmp_name'];
}
Тут более интересна последняя функция. Она сообщает нам временное имя файла - то место куда попадает файл при загрузке. А так как во временной папке файлы долго не держатся, его нужне сразу скопировать из временной в постоянную папку. Например у нас эта папка будет называться /content.
Туда нам его нужно либо переместить, либо скопировать. И соответственно для этого у нас есть нужные функции:
move_uploaded_file
copy
Предварительно после загрузки и перед сохранением, файл можно обработать. Изменить его размеры, сжать и т.п.
Для этого часто используют готовые библиотеки и классы дабы не изобретать велосипед. В частности в нашем приложении я использую такой подход:
<?php
define(ROOT, $_SERVER['DOCUMENT_ROOT']);
require(ROOT.'/sys/core.php');
require(ROOT.'/sys/classes/SimpleImage.php');
//var_dump($_FILES);
if($_SERVER['REQUEST_METHOD'] == 'POST'){
$pdo = init();
$pdo->exec("SET NAMES utf8");
$cookie_id = $_COOKIE['id'];
$cookie_hash = $_COOKIE['hash'];
$this_id = check($pdo, $cookie_id, $cookie_hash);
//print $this_id;
if(!$this_id) die();
$tmpfile = $_FILES['content']['tmp_name'];
$pubtime = time();
switch($_FILES['content']['type']){
case 'image/jpeg':
$type = 1;
break;
case 'image/png':
$type = 2;
break;
case 'image/gif':
$type = 3;
break;
default:
$type = 0;
}
//print $tmpfile.' '.$type.' '.$pubtime;
if(!empty($tmpfile) && ($type === 1 || $type === 2 || $type === 3)){
//var_dump($_FILES);
$img = new abeautifulsite\SimpleImage($tmpfile);
//var_dump($img);
$iname = ROOT.'/content/'.$this_id.'/m_ava.jpg';
$img->best_fit(640, 480)->save($iname, 100, 'jpg');
$iname = ROOT.'/content/'.$this_id.'/s_ava.jpg';
$img->best_fit(120, 90)->save($iname, 100, 'jpg');
header('location: /user');
}else{
print 'Неверный формат изображения';
}
}
Здесь я использую класс SimpleImage и его метод best_fit благодаря чему сохраняю аватарку в 2 экземплярах разного размера 640x480 и 120x90.
@rusldv Поздравляю! Вы добились некоторого прогресса на Голосе и были награждены следующими новыми бейджами:
Награду за количество голосов
Вы можете нажать на любой бейдж, чтобы увидеть свою страницу на Доске Почета.
Чтобы увидеть больше информации о Доске Почета, нажмите здесь
Если вы больше не хотите получать уведомления, ответьте на этот комментарий словом
стоп
Голосуя за это уведомление, вы помогаете всем пользователям Голоса. Узнайте, как здесь.
@rusldv Поздравляю! Вы добились некоторого прогресса на Голосе и были награждены следующими новыми бейджами:
Награду за Количество комментариев
Вы можете нажать на любой бейдж, чтобы увидеть свою страницу на Доске Почета.
Чтобы увидеть больше информации о Доске Почета, нажмите здесь
Если вы больше не хотите получать уведомления, ответьте на этот комментарий словом
стоп
Голосуя за это уведомление, вы помогаете всем пользователям Голоса. Узнайте, как здесь.