PHP V5.2 の新機能、第 5 回: ファイル・アップロードの進行状況を追跡する方法

$_POST 配列と APC_UPLOAD_PROGRESS を使ってリアルタイムの進行状況表示バーを作成する

PHP V5.2 では、開発者がファイル・アップロードの進行状況をリアルタイムで追跡する際に利用できるフックを追加しています。5 回からなる連載「PHP V5.2 の新機能」の最終回となるこの記事では PHP 進行状況表示バーの作成手順を通して、ファイルのアップロードを監視する方法とそのためのコードを作成する方法を紹介します。

Tracy Peterson (tracy@tracypeterson.com), Freelance Writer, Freelance Developer

Tracy Peterson は、経験豊富な LAMP 開発者であるとともに、ソフトウェア・プロジェクト・マネージャーでもあります。現在、カリフォルニア州サンホセ在住です。



2007年 5月 15日

Web 2.0 はインターネットで最も注目を浴びている流行語で、投資者たちは Web 2.0 の名前を冠したあらゆるものに投資しようと列を作っています。数百万のWeb サイトとそこでホストされるアプリケーションを記述する用語はたくさんありますが、Web 2.0 を説明すると、数百万のインターネット・ユーザーに意見を表明する手段を提供するWeb サイトの 1 つのカテゴリーということになります。ユーザーが集まって共通の興味に関する意見とデータを共有する場を提供するという点で独特なWeb 2.0 のサイトでは、急速に大量のコンテンツが生成されています。

ユーザーのそれぞれが、コーヒー・ショップの感想や会社への道順など、何らかのコンテンツを提供します。その好例が、個人がビデオをアップロードして、他のユーザーからそのビデオを見た感想をもらう場を提供するYouTube です。YouTube は目下、Web 2.0 を観察する人々のお気に入りとなっています。かつて、これほどまでに短時間で人気を集めたインターネット・サイトはありませんでした。この人気の理由は、ありとあらゆる種類のコンテンツ、そしてユーザーがコメントという形でコンテンツに対する意見を言えることにあります。単なるコメントだけではありません。ユーザーはビデオに対してコメントしたビデオをアップロードすることさえできるのです。

テキスト・フィールドにご注意

ファイルを受け付ける多くの Web サイトでは非常に恐ろしい Browse ボタンを誇らしげに見せびらかしていますが、その横にはユーザーにファイルを一度に 1 つずつアップロードするよう注意するテキスト・フィールドがあります。ビデオの場合は言うまでもなく、写真、それに小さなファイルの集まりで構成される他のアイテムでさえもファイルをアップロードするには時間がかかります。それぞれのファイルは特有のアップロード方法が必要なため、アップロードするのはかなり厄介な作業で、大きなファイルのアップロードともなると、せっかちなユーザーはうんざりしかねません。ここで重要になるのが、ユーザーがアップロードを諦めてサイトから出て行かないように、フィードバックを提供して明るい見通しを持たせることです。

幸い、ファイル・アップロード・プロセスに追加する PHP V5.2 の新しいフックを使えば、ユーザーにリアルタイムのアップロード進行状況を表示することが可能です。そこで、この記事ではPHP V5.2 を使用してユーザーのための進行状況表示バーを作成する方法を紹介します (ソース・コードについては「ダウンロード」セクションを参照)。


ユーザーの心を完全につかむフック

PHP V5.2 の新しい「フック」は実際にはデータ・ポイントで、正しいライブラリーがインストールされて構成されていればファイル転送プロセス中に使用できます。これらのフックが使用するのは、APC(Alternative PHP Cache) と呼ばれる機能です。PHP スクリプトがアップロードされたファイルを受け取ると、インタープリターは自動的に$_POST 配列で APC_UPLOAD_PROGRESS という名前の隠しフィールドを調べます。この隠しフィールドがキャッシュされる変数となって、アップロードに関する情報を格納し、スクリプトでアクセスできるようにします。情報をキャッシュに入れてすぐに利用できるようにすることで、ユーザーに視覚的フィードバックを表示してユーザー・エクスペリエンスの改善を図ることが可能になります。

この記事では、PHP 内での APC コードの識別に加え、HTML フォームへの APC コードの実装について説明し、またキャッシュ情報にアクセスする方法についても説明します。このデータを表現する方法はAjax から FLEX に至るまでさまざまですが、この記事では、これらのフロンドエンド技術に必要となるデータへのアクセスを準備する方法に焦点を絞ることにします。


セットアップ方法

PHP V5.2 のデフォルトでは、APC は有効に設定されていません。新規フックは APC の一部なので、この拡張をインストールして PHPインタープリターから確実に使用できるようにする必要があります。それには、php_apc 拡張ファイルをダウンロードしてください。この記事で使用しているのはWAMP システムです。WAMP は Windows® 向けに無料で入手できるパッケージ化された PHP で、Apache およびMySQL が組み込まれています。WAMP は優れたユーザー・インターフェースを持つだけでなく、構成オプションをサポートするメニューの管理も簡単です。

APC を WAMP でセットアップする手順は以下のとおりです。

  1. 「参考文献」を参照して、ライブラリーと WAMP をダウンロードします。
  2. WAMP をインストールします。
  3. php_apc.dll ファイルを PHP の拡張フォルダーに配置します。拡張フォルダーの場所は、デフォルトでは <wamproot>/php/extとなっています。
  4. システム・トレイの WAMP メニューで、PHP settings>PHP Extensions>Add Extension を選択します。
  5. ポップアップ表示されたコマンド行インターフェースに php_apc.dll と入力し、Enter を押します。
  6. テキスト・エディターを使って <wamproot>/php/php.ini を開き、apc.rfc1867 = on という行を追加します (場所はどこでも構いません)。ローカル側でテストして、大きなサイズのファイルをアップロードする際の進行状況を実際に見てみたいという場合は、ディレクティブとしてapc.max_file_size = 200Mupload_max_filesize = 200M、および post_max_size = 200M も追加します。ただし、稼働中の実動サーバーではこのディレクティブを追加しないでください。帯域幅とディスク・スペースの割り当てを使い果し、言うまでもなく他のユーザーの処理速度を落とすことになってしまいます。
  7. PHP を再起動します。

これで、APC のセットアップと初期化は完了です。APC の RFC1867 機能 (ファイル・アップロードの追跡を可能にする機能) がオプションとして有効になり、ファイル・アップロードを調べてリアルタイムの状況を表示できるようになります。


受信可能なアカウント

ファイルを受信するには、まず、ファイルを受信するためのフォームをセットアップする必要があります。都合のよいことに、HTML にはファイル用の標準フィールド・タイプが用意されています。すべてのHTML フォームのフィールドと同じく、このタイプにも論理的に file という型の名前が付けられています。また、デフォルトでブロックの右側に表示される便利な Browse ボタンもあります。

リスト 1. upload.php の HTML フォーム
<?php
   $id = $_GET['id'];
?>

<form enctype="multipart/form-data" id="upload_form" 
      action="target.php" method="POST">

<input type="hidden" name="APC_UPLOAD_PROGRESS" 
       id="progress_key"  value="<?php echo $id?>"/>

<input type="file" id="test_file" name="test_file"/><br/>

<input onclick="window.parent.startProgress(); return true;"
 type="submit" value="Upload!"/>

</form>

このフォームには PHP ページを作成する必要があります。アップロードを追跡するには固有のキーが必要となるためです。最終的にはこのキーが、ページを呼び出すために使用するURL に GET値として組み込まれます。GET 値は、後で取得する APC キャッシュのエントリー・キーの値となります。この値を渡すためにフォームのフィールドに必要となるのが、APCにファイルのアップロード状況を格納する必要があることを認識させるための特殊な名前を持つ隠しフィールドです。このフィールドの名前は APC_UPLOAD_PROGRESSで、これが前述したキャッシュ・プロセスを開始するフックです。PHP がキャッシュ内の正しい項目にアクセスすることを確実にするため、隠しフィールドの値として取得した固有のID を使用してこの値のキーを作成します。ユーザーがフォームを送信すると (送信ボタンについてはこの後説明します)、ブラウザーはファイルとともに、このキーをサーバーに送信されるPOSTデータの一部として送信します。

このページがブラウザーにロードされると、図 1 のとおり、非常に簡単なフォームが表示されます。

図 1. アップロード・フォーム
図 1. アップロード・フォーム

ユーザーがページ全体をリロードせずにファイルを送信できるようにするため、このフォームは別のファイル内の iframe に組み込むことになります。フォームのアクション・ページ (target.php) だけを使用してデータを取得しようとしても、キャッシュ情報を表示することはできません。このページはアップロードが完了するまで情報を返さないからです。そのため、この新しいフックの最も一般的な使用例はAjax で作成されています。Ajax で作成すると、フォームを送信してからもウィンドウを最新表示せずに同じウィンドウでアップロードの状況をそのまま調べることができます。

このスクリプトを動作させるには、次に収容ページに取り掛かります。このページで iframe をセットアップし、アップロードされたファイルの情報を受信します。また、進行状況インジケーターのためのデータを取得してこのインジケーターの状況を表示する一連のJavaScript 関数も必要です。


スローされたものをキャッチする

送信されたフォームにファイルが含まれている場合、そのファイルは永久的な場所に保管されるまでいったん、サーバーの一時的な場所に送信されます。一時ストレージ内のファイルは、$_FILES連想配列から利用できます。PHP での標準的なファイル・アップロード関数を使用して、パスを選択してサーバーに保管することも、あるいは処理することもできます。

リスト 2. target.php ファイル
<?php  

if($_SERVER['REQUEST_METHOD']=='POST') {
  move_uploaded_file($_FILES["test_file"]["tmp_name"], 
  "c:\\sw\\wamp\\www\\" . $_FILES["test_file"]["name"]);
  echo "<p>File uploaded.  Thank you!</p>";
}

?>

上記ではまず、POST 変数 (フォームから渡される) が設定されているかを調べて、フォーム・データを受信したことを示しています。フォーム・データを受信して、それがファイルであることを期待する場合は、グローバル配列$_FILES も使用する必要があります。アップロードされたファイルは、そのファイルで何を実行するかに応じて安全な場所に移します。この例では、ファイルを単に\sw\wamp\www に移しているだけです (これはもちろん、まったく任意の場所なので、自由に場所を選んでください)。この一連のアクションを完了した時点で、ユーザーに感謝の言葉を述べています。

ここではほぼ完全を期して実際のファイル処理を含めていますが、この記事の話題は進行状況表示バーなので、実際のファイルを受信したときに何を行うかは問題ではありません。


進行状況の作成

そろそろ、実際のアップロード進行状況を返すスクリプトも必要になってきました。リスト 3 に、極めて単純なバージョンを示します。

リスト 3. getprogress.php ファイル
<?php
if(isset($_GET['progress_key'])) {

  $status = apc_fetch('upload_'.$_GET['progress_key']);
  echo $status['current']/$status['total']*100;

}
?>

上記のスクリプトではまず、前述した $id 値である progress_key を検索しています (どこから渡されるのかはすぐにわかるのでご心配なく)。次に apc_fetch() を呼び出します。これは、APC キャッシュからデータを返す関数です。正しいファイル情報が必要なので、ここでは $_GET['progress_key'] として表される固有の ID が必要となります。この固有の ID に相当するのが、apc_fetch() の呼び出しで指定しているパラメーター upload_xxxxxx の xxxxxx の部分です。upload_ の部分はPHP によって自動的に前に付加されます。

データを取得したら、今度は JSON 拡張を使って JavaScript で使いやすいフォーマットに情報を設定し、必要であればオブジェクト全体を返します。$status オブジェクトは、以下のフィールドを持つ配列です。

total
ファイルの合計サイズ
current
現時点までに受信したファイルのサイズ
rate
1 秒ごとのバイト数で表したアップロード速度
filename
ファイルの名前
name
変数の名前
temp_filename
PHP がファイルの一時コピーを保管している場所
cancel_upload
アップロードがキャンセルされたか (1)、またはキャンセルされていないか (2)
done
アップロードが完了したか (1)、または完了していないか (2)

この例で必要なのはアップロードが完了したパーセンテージですが、必要に応じてこれ以外の情報も選択できます。


JavaScript によるバーの表示

いよいよ実際の進行状況表示バーを作成する段階にきました。作業を単純にするため、このスクリプトではリスト 4 に示すように、CSS を使ってバーをエミュレートするdiv を作成します。この div は JavaScript で制御することができます。

リスト 4. メインの progress.php ファイル
<html>
<head><title>Upload Example</title></head>
<body>

<script type="text/javascript">

var counter = 0;

function startProgress(){
    document.getElementById("progressouter").style.display="block";
    fire();
}

function fire(){
   if (counter < 101){
     document.getElementById("progressinner").style.width =
                                                     counter+"%";
     counter++;
     setTimeout("fire()",100);
   }
}

</script>

<div id="progressouter" style=
    "width: 500px; height: 20px; border: 6px solid red; display:none;">
   <div id="progressinner" style=
       "position: relative; height: 20px; background-color: purple; width: 0%; ">
   </div>
</div>

<span onclick="startProgress()">Start me up!</span>

</body>
</html>

このページは、外枠として機能する div の内側に、もう 1 つ div 要素が含まれています。スクリプトは外枠を基準に内部 div をサイズ変更して進行状況を表示します。ユーザーが Start me up!テキストをクリックすると、startProgress() スクリプトは fire() 関数を呼び出します。この関数はカウンターの値をチェックし、値が 100 を超えていなければ内部 divを外側 div 幅の該当するパーセンテージに設定します。次にカウンターをインクリメントして、ブラウザーにこの一連の動作を 1/10 秒で繰り返すように指示します。

結果は図 2 のように表示されます。

図 2. 進行状況表示バーのスクリプト
図 2. 進行状況表示バーのスクリプト

あと必要なのは、スクリプトに任意の値ではなく、完全なパーセンテージで幅を更新させるようにするための方法だけです。


すべてを組み立てる

残るは、すべてを組み立てることだけです。それには、progress.php ページを操作します。

リスト 5. 最終的な progress.php ページ
<?php
   $id = uniqid("");
?>
<html>
<head><title>Upload Example</title></head>
<body>

<script src="http://maps.google.com/maps?file=api&v=2&key=<yourkeyhere>"
            type="text/javascript"></script>

<script type="text/javascript">

function getProgress(){
  GDownloadUrl("getprogress.php?progress_key=<?php echo($id)?>", 
               function(percent, responseCode) {
                   document.getElementById("progressinner").style.width = percent+"%";
                   if (percent < 100){
                        setTimeout("getProgress()", 100);
                   }
               });

}

function startProgress(){
    document.getElementById("progressouter").style.display="block";
    setTimeout("getProgress()", 1000);
}

</script>

<iframe id="theframe" name="theframe" 
        src="upload.php?id=<?php echo($id) ?>" 
        style="border: none; height: 100px; width: 400px;" > 
</iframe>
<br/><br/>

<div id="progressouter" style=
   "width: 500px; height: 20px; border: 6px solid red; display:none;">
   <div id="progressinner" style=
       "position: relative; height: 20px; background-color: purple; width: 0%; ">
   </div>
</div>

</body>
</html>

下から上に向かって説明すると、リスト 1 の upload.php スクリプトを組み込む iframe を追加して、ページの先頭で生成された固有 ID を指定しています。

ここで、送信ボタンは以下のフォームであることに注目してください。

<input onclick="window.parent.startProgress(); return true;"
 type="submit" value="Upload!"/>

このボタンは 2 つのことを行います。まず、このボタンは標準の送信ボタンと同じようにフォームを送信しますが、その前に、メインウィンドウで startProgress() スクリプトを呼び出します。startProgress() スクリプトは進行状況表示バーにそれ自体を表示させるように指示し (バーは最初、表示プロパティーなしで開始します)、次にブラウザーに 1 秒待機してからgetProgress() スクリプトを実行するように指示します。

面白くなってくるのは、この getProgress() スクリプトからです。前にも言ったように、ファイルの進行状況を調べるには、Ajax や同様のメソッドを使用する必要があります。そこで、この例ではフォームがショートカットを使用してGoogle Maps API ライブラリーから GdownloadUrl() 関数を呼び出しています (フォームはライブラリーをページの先頭にインポートすることに注意してください。このライブラリーには独自のキーを取得する必要がありますが、キーはGoogleから無料で入手できます)。

GdownloadUrl() 関数は、URL のコンテンツ (この例では getprogress.php スクリプト) をダウンロードして、そこに定義された匿名関数を実行します。この関数が受け入れる最初のパラメーターは、URLから返されたデータです。この例でのデータはパーセンテージなので、それを使って進行状況表示バーを更新します。最後に、ファイルのダウンロードがまだ完了してない場合は、ブラウザーに1/10 秒で再試行するように指示します (実際には、それほど速くこれらの呼び出しを実行することはできないかもしれませんが、ブラウザーはできる限りの速さで実行します)。

最終的なページでは、ユーザーがファイルのアップロードの進行状況を見て取ることができます。

図 3. Progress.php からの出力
図 3. Progress.php からの出力

まとめ

Web 2.0 の世界でユーザーに推奨されるのは、Web サイトでユーザーが互いに情報とコンテンツを提供し合うことです。個人対個人のこのオープンで自由なデータ交換に対応したフレームワークは、私たち開発者が作成します。これを可能にするツールはかねてから用意されていますが、ユーザー・エクスペリエンスという点ではまだその可能性が十分に発揮されていません。この記事を読んで、サイトへの情報のアップロードに関する進行状況表示バーを表示してリアルタイムのフィードバックをユーザーに提供し、ユーザー・エクスペリエンスとアプリケーションの品質を改善する方法がわかったはずです。


ダウンロード

内容ファイル名サイズ
Sample jar filesos-php-v525.source.zip3KB

参考文献

学ぶために

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source
ArticleID=249807
ArticleTitle=PHP V5.2 の新機能、第 5 回: ファイル・アップロードの進行状況を追跡する方法
publish-date=05152007