Zend_Form i wysyłanie plików - cz. 3

by Mateusz Tymek — on Zend Framework, PHP, JavaScript/AJAX

Head's up! This post was written back in 2009 and is very likely to contain outdated information.

Oto trzeci artykuł dotyczący wysłania plików na serwer przy użyciu komponentu Zend_Form. Tym razem pokażę jak wyświetlić użytkownikowi pasek postępu.

Pasek nie zadziała przy standardowej konfiguracji PHP. Wymagane jest jedno z dwóch rozszerzeń: APC lub uploadprogress. Zend Framework automatycznie wykryje które z nich jest dostępne i użyje go.

Założenia

Chcemy uzyskać prosty pasek pokazujący postęp wysyłania pliku w tle. Niech strona w określonym odstępie czasu (np 1/10 sekundy) wysyła zapytania do serwera, który zwróci żądaną informację.

Podobnie jak w poprzednim artykule przedstawione rozwiązanie musi być elastyczne - niech zadziała także gdy serwer nie obsługuje paska postępu lub gdy użytkownik wyłączy obsługę Java Script.

Podstawą będzie aplikacja napisana w poprzednim artykule.

Przygotowanie - APC

APC można zainstalować na różne sposoby w zależności od naszego środowiska. W Debianie wystarczy polecenie:

apt-get install php-apc

Minimalna konfiguracja (np w pliku php.ini):

extension=apc.so
apc.rfc1867 = On

Oprócz włączenia rozszerzenia ustawienie zmiennej apc.rfc1867 jest wymagane do obsługi paska postępu.

Serwer

Zadaniem serwera (oprócz przyjęcia pliku) będzie poinformowanie klienta o postępie wysyłania. Pamiętajmy że w aplikacji internetowej serwer może odbierać kilka plików jednocześnie (od różnych użytkowników). Jak więc zidentyfikować "nasz" proces pobierania?

Okazuje się, że Zend Framework w pewnym sensie robi to za nas - obok pola typu FILE w formularzu zostaje umieszczony ukryty element - progress_key - jego zawartość jednoznacznie identyfikuje pobieranie.

Utwórzmy nowy plik (uploadStatus.php) w tym samym miejscu co index.php:

<?php
set_include_path(
    realpath('../../library')
    . PATH_SEPARATOR . get_include_path()
);

require_once 'Zend/Loader/Autoloader.php';
Zend_Loader_Autoloader::getInstance();

if (isset($_GET['progress_key'])) {
    $adapter = new Zend_ProgressBar_Adapter_JsPull();
    $upload = Zend_File_Transfer_Adapter_Http::getProgress($adapter);
    Zend_File_Transfer_Adapter_Http::getProgress($upload);
}

Jeżeli identyfikator jest dostępny, to tworzymy odpowiedni adapter i wysyłamy użytkownikowi informacje o stanie pobierania. Metoda getProgress() wysyła dane w formacie JSON, zatem przeglądarka otrzyma coś takiego:

{"current":12721174,"max":24009624,"percent":52.98,"timeTaken":1,"timeRemaining":1,
"text":"12.13MB - 22.9MB","finished":false}

To wszystko czego potrzebujemy po stronie serwera. Resztą zajmie się...

Kod JavaScript

W momencie wysłania formularza (jeżeli serwer obsługuje APC lub uploadprogress) trzeba utworzyć pasek postępu. Będzie to element <div>, wypełniony czarnym kolorem, o rozmiarze proporcjonalnym do postępu pobierania. Niezbyt ładny :-).

Jak poznać czy serwer ma zainstalowane odpowiednie rozszerzenie? Okazuje się że tylko w takim przypadku do formularza zostaje dołączone ukryte pole progress_key. Zatem pasek będzie tworzony wyłącznie w sytuacji gdy takie pole istnieje:

$('#uploadForm').submit(function() {
    $('#uploadForm #submit').attr('disabled', 'disabled');
    $('#information').html('<p>Wysyłanie pliku - proszę czekać</p>');

    if ($('#progress_key').length > 0) {  // czy element progress_key istnieje?
        progressBar = $('<div id=\"progressBar\">')
                            .css('width', '1px')
                            .css('height', '10px')
                            .css('background-color', 'black')
                            .appendTo('#information');
        setTimeout("window.uploadProgress()", 10);
    }

    return true;
});

Po wysłaniu formularza zostaje wykonana funkcja uploadProgress(). Jej zadaniem jest wywołać skrypt który napisaliśmy wcześniej (uploadProgress.php), odczytać informacje i ustawić odpowiednią długość paska postępu:

window.uploadProgress = function() {
    $.ajax({
      type: "GET",
      url: "uploadStatus.php?progress_key=" + $('#progress_key').val(),
      dataType: "json",
      success: function(data) {
          if (data.percent) {
              $('#progressBar').width(data.percent + '%');
          }
          if (!data.finished) {
              setTimeout("window.uploadProgress()", 100);
          }
      }
    });
}

Jeżeli wysyłanie nie zostało zakończone (zmienna data.finished ma wartość false), to funkcja wykona się znowu za 1/10 sekundy, ponownie aktualizując pasek postępu.

Podsumowanie

W ten sposób nasza aplikacja otrzymała nowoczesny, animowany pasek postępu. Uwaga: przy testowaniu rozwiązania wysyłając niewielki plik poprzez lokalny serwer Apache nie zobaczymy paska - plik zostanie wysłany zbyt szybko. Do testowania najlepiej wysyłać pliki po 20-30MB (można tymczasowo pozwolić na to ustawiając odpowiednie zmienne w php.ini).

Przykład

Dołączony przykład to kolejna, bardziej rozbudowana wersja aplikacji z poprzednich artykułów.

Pobierz przykład.

Przydatne linki


comments powered by Disqus