antonio's blog

antonio's blog


Блог о всяком разном, связанном с разработкой ПО. Пишу редко, когда есть время и желание.

Anton Dobkin
Author

Share


Tags


Прогресс загрузки файлов в PHP 5.4

Anton DobkinAnton Dobkin

Всем кому приходилось реализовать на PHP загрузку файлов на сервер с отображением "реального" прогресс бара знают, что задача не из простых. Для создания полноценного прогресс бара загрузки файлов приходилось использовать сторонние расширения, такие как APC или uploadprogress. С выходом PHP 5.4 ситуация в корне изменилась. В PHP был встроен механизм позволяющий контролировать процесс загрузки файлов. Данное решение было добавлено в механизм сессий. Теперь, когда на сервер загружаются файлы, информация о процессе загрузки сохраняется в текущей сессии. Данная информация может быть извлечена и использована для вывода прогресс бара загрузки и дополнительных данных, например, процент загрузки и/или примерное время до окончания процесса.

Параметры настройки

Для настройки данного функционала доступно несколько конфигурационных параметров:

Информация о процессе загрузи

Прогресс загрузки будет доступен когда включен параметр session.upload_progress.enabled, когда массив $POST содержит элемент с названием session.upload_progress.name и значение данного элемента не пустое.

При выполнении данных условий в текущей сессии будет создана переменная, название которой будет сформировано из значения параметра session.upload_progress.prefix и значения элемента с названием session.upload_progress.name массива $POST:

$_SESSION[ini_get(session.upload_progress.prefix).$_POST[ini_get(session.upload_progress.name)]]

В эту переменную будет сохранена информация о процессе загрузи и загружаемых файлах в виде ассоциативного массива со следующей структурой:

$_SESSION[ini_get(session.upload_progress.prefix).$_POST[ini_get(session.upload_progress.name)]] = array(
    'start_time'                => дата запроса,
    'content_length'            => размер данных, 
    'bytes_processed'           => кол-во принятых и обработанных байт,
    'done'                      => флаг завершения загрузки всех данных(true/false),
    // Информация о файлах
    'files' => array(
        // Информация о первом файле
        0 => array(
            'field_name'        => название поля в форме,
            'name'              => имя файла,
            'tmp_name'          => имя файла во временном каталоге,
            'error'             => код ошибки,
            'done'              => флаг завершения загрузки данного файла(true/false),              
            'start_time'        => время начала загрузки данного файла,    
            'bytes_processed'   => кол-во принятых и обработанных байт данного файла, 
        ),
        // Информация о втором файле
        1 => array(
            …        
        ),
        n => array(
            …        
        ),
    )
);

Простая форма:

<form action="" method="POST" enctype="multipart/form-data">  
   <input type="hidden" name="<?php echo ini_get("session.upload_progress.name"); ?>" value="test" />
   <label for"file1">Файл 1: </label><input type="file" name="file1" />
   <label for"file2">Файл 2: </label><input type="file" name="file2" />
   <label for"file3">Файл 3: </label><input type="file" name="file3" />
   <input type="submit" value="Загрузить" />
</form>  

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

$_SESSION['upload_progress_test'] = array(5) {
  "start_time" => 1311502890,
  "content_length" => 485856963,
  "bytes_processed" => 485856963,
  "done" => true,
  "files" => array(3) {
    [0] => array(7) {
      "field_name" => "file1",
      "name" => "test_file1.pdf",
      "tmp_name" => "/tmp/phpky5BVc",
      "error" => 0,
      "done" => true,
      "start_time" => 1311502890,
       "bytes_processed" => 335026
    },
    [1] => array(7) {
      "field_name" => "file2",
      "name"=> "test_file2.avi",
      "tmp_name"=> "/tmp/phpGVvw2W",
      "error" => 0,
      "done"]=> true,
      "start_time" => 1311502890,
      "bytes_processed" => 435904512
    },
    [2] => array(7) {
      "field_name" => "file3",
      "name" => "test_file3.doc",
      "tmp_name" => "/tmp/php6BVPAq",
      "error" => 0,
      "done" => true,
      "start_time" => 1311502922,
      "bytes_processed" => 49616655
    }
  }
}

Отмена загрузки

Процесс загрузки можно отметить в любой момент, для этого нужно установить значение TRUE элементу cancel_upload массива данных в сессии:

$_SESSION['upload_progress_test']['cancel_upload'] = true;

Это приведет к отмене загрузки файлов и при этом рекурсивно, элементу error, в массивах данных о файлах, будет установлено значение UPLOAD_ERR_EXTENSION

Рабочий пример

Посмотрим как это работает на простом примере, реализовав простую загрузку файлов на сервер. Файлы будут отправляться на сервер через AJAX с помощью плагина form для jQuery. Данные о процессе загрузки будем запрашивать дополнительными запросами к серверу через AJAX, каждые пол секунды. На основе полученных данных будет заполнять прогресс бар

Страница загрузки файлов (index.php):

<?php  
session_start();  
?>
<html>  
<head>  
<script type="text/javascript" src="jquery-1.6.2.min.js"></script>  
<script type="text/javascript" src="jquery-ui-1.8.14.custom.min.js"></script>  
<script type="text/javascript" src="jquery.form.js"></script>  
<link href="jquery-ui-1.8.14.custom.css" rel="stylesheet" type="text/css" />  
<style type="text/css">  
.ui-progressbar-value { 
    background-image: 
    url(images/pbar-ani.gif);
    padding-left:10px;
    font-weight:normal;
}

#upload_form {
    display:block;
}
#progress {
    display: none;
}

#progress #bar {
    height: 22px;
    width: 300px;
}
</style>  
<script type="text/javascript">  
var t;  
/* Функция получения информации о процессе загрузки по AJAX */
progress = function(){  
    $.ajax({
        url: 'upload.php',
        dataType: 'json',
        success: function(data){
            if(data.percent) {
                $("#bar").progressbar({
                    value: Math.ceil(data.percent), // Заполняем прогресс бар
                });
                $('.ui-progressbar-value').text(data.percent+'%'); // Отображаем на прогресс баре процент загрузки
            }
        }
    });
}
$(document).ready(function() {
    /* Отправка формы загрузки по AJAX */    
    $('#form').ajaxForm({
        type: 'POST',
        success: function() { 
            clearTimeout(t);
            $('#progress').html('<b>Файл был загружен!</b>');
        },
        beforeSubmit: function() {
            /*  Перед отправкой данных на сервер прячем форму, показываем прогресс бар и запускаем таймер */
            $('#upload_form').hide();
            $('#progress').show();
            t = setInterval("progress()", 10);
        }
   });
   /* Отправка запроса на отмену загрузки */
   $('#cancel-form').ajaxForm({
       success: function() { 
           clearTimeout(t);
           $('#progress').html('<b>Загрузка была отменена!</b>');
        }
   });
});
</script>  
</head>  
<body>  
<div id="upload">  
    <div id="upload_form">
        <!-- Форма загрузки файлов на сервер -->
        <form id="form" action="upload.php" method="POST" enctype="multipart/form-data">
            <input type="hidden" name="<?php echo ini_get("session.upload_progress.name"); ?>" value="test" />
            <label for="file1">Файл для загрузки 1:&nbsp;</label><input type="file" name="file1" /><br /><br />
            <label for="file2">Файл для загрузки 2:&nbsp;</label><input type="file" name="file2" /><br /><br />
            <label for="file3">Файл для загрузки 3:&nbsp;</label><input type="file" name="file3" /><br /><br />
            <input type="submit" value="Загрузить" />
        </form>
    </div>
    <div id="progress">
        Загрузка файлов<br /><br />
        <!-- Прогресс бар -->
        <div id="bar"></div><br />
        <!-- Форма отмены загрузки файлов -->
        <form id="cancel-form" action="cancel.php" method="POST" enctype="multipart/form-data">
            <input type="submit" value="Отмена" />
        </form>
    </div>
</div>  
</body>  
</html>  

Сценарий (upload.php):

<?php  
/*
 * Здесь все просто, берем данные о процессе загрузки из сессии
 * и возвращаем их в JSON формате
 */
session_start();  
$percent = 0;
$data = array();

if(isset($_SESSION['upload_progress_test']) and is_array($_SESSION['upload_progress_test'])) {  
    $percent = ($_SESSION['upload_progress_test']['bytes_processed'] * 100 ) / $_SESSION['upload_progress_test']['content_length'];
    $percent = round($percent,2);
    $data = array(
         'percent' => $percent, 
         'content_length' => $_SESSION['upload_progress_test']['content_length'], 
         'bytes_processed' => $_SESSION['upload_progress_test']['bytes_processed']
    );
}
echo json_encode($data);  
?>

Сценарий отмены загрузки файлов (cancel.php):

<?php  
session_start();  
$_SESSION['upload_progress_test']['cancel_upload'] = true;
?>

RFC: Upload progress in sessions

Anton Dobkin
Author

Anton Dobkin

Comments