目次


PHP の学習

第 2 回 ファイルをアップロードし、ファイルの情報を XML または JSON 形式で保管し、表示する

Comments

コンテンツシリーズ

このコンテンツは全#シリーズのパート#です: PHP の学習

このシリーズの続きに乞うご期待。

このコンテンツはシリーズの一部分です:PHP の学習

このシリーズの続きに乞うご期待。

始める前に

このチュートリアルでは、PHP でセッションを使用する方法、DOM を使用して XML データを操作する方法、PHP で JSON データを作成、使用、読み取る方法を学びます。

このチュートリアルについて

このチュートリアルでは、Web ベースのワークフロー・アプリケーションを構築する作業をデモンストレーションすることにより、PHP の使用方法を説明します。PHP の構文と関数、HTML フォームの送信方法とデータベースの操作方法、そして新規ユーザーがアカウントを登録するためのプロセスを作成する方法などの基本事項については、「PHP の学習: 第 1 回」で説明しました。

今回のチュートリアルでは、ユーザーが各自のブラウザーを使用してファイルをシステムにアップロードできるようにした後、最初に XML、次に JSON を使用して、各ファイルに関する情報を保管して表示します。

第 3 回では、HTTP 認証の使用方法と、Web でアクセスできないディレクトリーにあるファイルに対してストリームによる処理を行うことでファイルを保護する方法を説明します。さらに、オブジェクトの作成方法と例外の使用方法に関するトピックも取り上げます。

このチュートリアルで説明する内容は以下のとおりです。

  • セッションおよびセッション情報を作成して使用する方法
  • ブラウザーからファイルをアップロードする方法
  • DOM (Document Object Model) を使用して XML を作成する方法
  • DOM を使用して XML データを操作する方法
  • JSON (JavaScript Object Notation) データを作成する方法
  • JSON データを読み取って使用する方法

このチュートリアルの対象読者

このチュートリアルは、ワークフロー・アプリケーションの構築プロセスを通して、PHP を扱う上でのさまざまな側面を説明することを目的とした 3 部構成のシリーズの第 2 回です。PHP の基礎知識を基に、ブラウザーからファイルをアップロードする方法や、セッションについて、あるいは PHP で XML または JSON を処理する方法について学ぶには、このチュートリアルに従ってください。

このチュートリアルでは、読者にこのシリーズの第 1 回で説明したレベルの PHP の基礎知識があることを前提とします。これには、ループや if-then 文などの制御構造に加え、関数、HTML フォームの送信方法やデータベースの操作方法などの基礎知識も含まれます。XML に精通していると役に立ちますが、必須ではありません (以上のトピックについての詳細は、「参考文献」を参照してください)。

前提条件

Web サーバー、PHP、そしてインストールされていて使用できる状態のデータベースが必要です。ホスティング・アカウントを使用している場合は、サーバーに PHP V5 がインストール済みで、MySQL データベースにアクセスできる限り、そのアカウントを使用することができます。そうでない場合は、以下のパッケージをダウンロードしてインストールしてください。

XAMPP
Windows、Linux、あるいは Mac のどれを使用しているかに関わらず、このチュートリアルに必要なすべてのソフトウェアを取得するのに最も簡単な方法は、Web サーバー、PHP、MySQL データベース・エンジンをまとめてパッケージ化した XAMPP をインストールすることです。この方法を選ぶとしたら、XAMPP をインストールした後、コントロール・パネルを実行して Apache および MySQL プロセスを起動すれば、準備は完了です。各種の構成要素を個別にインストールするという選択肢もありますが、その場合には、すべてが連動するように構成する必要があることを念頭に置いてください。XAMPP をインストールする方法では、この構成ステップまでが行われます。
Web サーバー
XAMPP を使用しないことにした場合、Web サーバーにはいくつかの選択肢があります。PHP 5.4 を使用する場合 (このチュートリアルを作成している時点では、XAMPP では PHP 5.3.8 のみが使用されています)、テスト用に組み込み Web サーバーを使用することができます。ただし、本番用には Apache Web サーバーのバージョン 2.x を使用することを前提とします。
PHP 5.x
XAMPP を使用しない場合、PHP 5.x を別途ダウンロードする必要があります。標準ディストリビューションには、このチュートリアルに必要なすべてのものが含まれています。ダウンロードするのはバイナリーで構いません。このチュートリアルには (PHP のコード自体に手を加えたいというのでない限り)、ソースは不要です。このチュートリアルは、PHP 5.3.8 を前提に作成してテストしました。
MySQL
このプロジェクトの一環としてデータをデータベースに保存する必要があるため、データベースも必要です。データベースについても同じく、XAMPP をインストールする場合には、このステップをスキップすることができますが、必要に応じてデータベースを別途インストールすることもできます。このチュートリアルでは、MySQL に焦点を合わせます。PHP では、MySQL が一般的に使用されているためです。MySQL を選択する場合は、Community Server をダウンロードしてインストールすることができます。

開始手順: セッションを作成する

まずは、ログイン・ページを作成することから始めましょう。これにより、セッションを開始して、セッションに情報を取り込み、セッションを終了することが可能になります。

ログイン・プロセス、パート 1

シリーズ第 1 回では、HTML フォームから送信された情報を扱う方法と、データベースを操作する方法を詳しく説明するために、新規ユーザーの登録システムを作成しました。最終的に完成したファイルについては、「参考文献」を参照してください。

ただし、第 1 回でやり残したことが 1 つあります。それは、ユーザーがこの登録システムにログインするためのページを作成することです。そこで、まずはこのページを作成してから、第 1 回で説明した内容をざっと復習します。

最初に新しい空白のファイルを作成し、login.php という名前で registration.php と同じディレクトリーに保存します。この新規ファイルには、リスト 1 のコードを追加してください。

リスト 1. ログイン・フォームを作成する
<?php
   include("top.txt");
   require("scripts.txt");
?>

<h1>Please log in</h1>
<form action="login_action.php" method="post">
   Username: <input type="text" name="username" /><br />
   Password: <input type="password" name="password" /><br />
   <input type="submit" value="Log In" />
</form>

<?php
   include("bottom.txt");
?>

復習すると、ユーザーがこのフォームを送信すると、PHP では usernamepassword の 2 つのエントリーからなる情報の配列、$_POST を作成します。これらの情報をデータベースに照らし合わせて確認した後、このページをブラウザーにロードします。すると、サーバーは top.txt と bottom.txt で参照されているインターフェース要素をページに組み込みます (図 1 を参照)。

図 1. インターフェース要素が組み込まれた login.php ページ
インターフェース要素が組み込まれた login.php ページのスクリーン・キャプチャー
インターフェース要素が組み込まれた login.php ページのスクリーン・キャプチャー

ログイン・プロセス、パート 2

別のページを新規に作成して、login_action.php という名前で保存します。このページには、リスト 2 のコードを追加します。

リスト 2. ログイン情報を処理する
<?php

  include("top.txt");
  require("scripts.txt");

  $dbh = new PDO('mysql:host=localhost;dbname=workflow', 'wfuser', 'wfpass');

  $stmt = $dbh->prepare("select * from users \
                                  where username = :user and password = :pword");

  $stmt->bindParam("user", $name);
  $stmt->bindParam("pword", $pword);

  $name = $_POST["username"];
  $pword = $_POST["password"];

  $stmt->execute();

  if ($stmt->fetch()) {
      echo "You are logged in.  Thank you!";
  } else {
      echo "There is no user account with that username and password.";
  }

  $dbh = null;

  include("bottom.txt");
?>

上記のコードでは、ページ上部のインターフェース要素を組み込んだ後、データベースへの接続を開き、データベースの検索に使用する PDO オブジェクトを作成します。そこから後は、ユーザーから送信されたユーザー名とパスワードを使用してユーザー・アカウントを検索するステートメントを作成します。

お気付きかもしれませんが、今回のバージョンと第 1 回で使用したバージョンには少し違いがあります。前回はパラメーターの位置で変数を指定しましたが、今回は (:) コロン構文を使用して、パラメーターに明示的に名前を付けています。その点を除けば、両方ともまったく同じように動作します。ステートメントを実行した後、ユーザー名とパスワードが既存のアカウントに一致すれば、PHP は fetch() を実行して行を取得することができます。一致しなかった場合、行の取得は失敗して false を返します。

セッションを開始する

ユーザーに対し、ログインするように求めます。ユーザーがログインすると、このサイトは個々のユーザーがどのファイルを表示できるのかを把握します。しかし現状では、ユーザーがこのページから離れると、こうした情報が失われてしまうため、ユーザーをログインさせるだけでは十分ではありません。

サーバーがどのリクエストを同じユーザーによるものとしてひとまとめにすればよいかを把握できるようにするには、セッションを作成する必要があります。セッションには、ユーザー名などの情報を関連付けると便利です。

まず始めに、session_start() 関数を使用してセッションを作成します。この関数はまず、セッションが存在するかどうかをチェックし、セッションが存在しなければ、セッションを開始します。

極めて重要な注意点として、session_start() の使用、あるいは実際のところ、セッション情報を設定するためのあらゆる作業には重大な制約事項があります。セッション情報は HTTP ヘッダーの一部であるため、この情報を設定してからでなければ、インターフェース要素を含めた一切のコンテンツをブラウザーに読み込んではなりません (リスト 3 を参照)。

最初にセッションを追加して login_action.php を再構築します。

リスト 3. セッション情報を追加する
<?php
   session_start();

   require("scripts.txt");

   $dbh = new PDO('mysql:host=localhost;dbname=workflow', 'wfuser', 'wfpass');

   $stmt = $dbh->prepare("select * from users
                                   where username = :user and password = :pword");
   $stmt->bindParam("user", $name);
   $stmt->bindParam("pword", $pword);

   $name = $_POST["username"];
   $pword = $_POST["password"];
   $stmt->execute();

   $loginOK = false;
   if ($stmt->fetch()) {
      $loginOK = true;
   }

   $dbh = null;

   include("top.txt");

   if ($loginOK) {
      echo "You are logged in.  Thank you!";
   } else {
      echo "There is no user account with that username and password.";
   }

   include("bottom.txt");
?>

上記リストで行ったことは、ブラウザーへ出力する前にセッション関連のすべての処理が行われるように、ページを再構築することです。

セッションに情報を取り込む

あるリクエストから別のリクエストへと受け渡す情報がなければ、セッションを作成する意味はありません。この例の場合には、ユーザー名と e-メール・アドレスを渡す必要があるため、これらの情報をセッションに追加します (リスト 4 を参照)。

リスト 4. セッション情報を保存する
...
if ($row = $stmt->fetch()) {
    $loginOK = true;
    $_SESSION["username"] = $row["username"];
    $_SESSION["email"] = $row["email"];
}
...

$_SESSION$_POST と同じく連想配列であると同時に、スーパー・グローバル変数でもあるため、その値は複数のリクエストを通して保持され、別のページからでも参照することができます。

既存のセッションを使用する

PHP は session_start() 関数を検出すると、必要に応じて進行中の既存のセッションに参加するか、新規セッションを開始します。他のページはこの方法で $_SESSION 配列にアクセスします。例えばリスト 5 のように、$_SESSION["username"] の値はナビゲーション・セクション内で調べることができます。

リスト 5. セッション情報を使用する
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"<
  <head<
    <title<Workflow Manager</title<
    <link rel="stylesheet" type="text/css" href="style.css" /<
  </head<
  <body<
    <div id="wrapper"<<div id="bg"<<div id="header"<</div<
      <div id="page"<<div id="container"<<div id="banner"<</div<
        <div id="nav1"<
          <ul style='float: left'<
            <li<<a href="#" shape="rect"<Home</a<</li<
            <li<<a href="#" shape="rect"<Upload</a<</li<
            <li<<a href="#" shape="rect"<Files</a<</li<
             <?php
               if (isset($_SESSION["username"]) || isset($username)){
                  if (isset($_SESSION["username"])){
                     $usernameToDisplay =  $_SESSION["username"];
                  } else {
                     $usernameToDisplay = $username;
                  }
            ?<
                  <li shape="rect"<</li<
                  <li<<p style='color:white;'<&nbsp;&nbsp;&nbsp;
                                      Welcome, <?=$usernameToDisplay?<.
</p<</li<
            <?php
               } else {
            ?<
                  <li<<a href="registration.php" 
shape="rect"<Register</a<</li<
                  <li<<a href="login.php" 
shape="rect"<Login</a<</li<
            <?php
               }
            ?<
          </ul<
        </div<
        <div id="content"<
          <div id="center"<

$username 変数についてもチェックしていることを不思議に思うかもしれませんが、その理由は、$_SESSION 変数を設定すると、その変更は次に session_start() が呼び出されるまで適用されないためです。実際の login_action.php ページでは、変更はまだ適用されないものの、$username は使用することができます (図 2 を参照)。

図 2. $username 変数を使用した Login_action.php ページ
$username 変数を使用した Login_action.php ページのスクリーン・キャプチャー
$username 変数を使用した Login_action.php ページのスクリーン・キャプチャー

データをクリアする

セッションが終わったら、セッションのデータをクリアすることも、あるいはセッションを完全に終了することもできます。例えば、セッションを終了せずにユーザーをログアウトさせるとしたら、logout.php という名前のファイルを新規に作成し、リスト 6 のコードを追加します。

リスト 6. セッション情報をクリアする
<?
   session_start();
   unset($_SESSION["username"]);
   unset($_SESSION["email"]);

   include("top.txt");
   echo "Thank you for using the Workflow System.";
   echo "You may <a href=\"login.php\">log in again</a>.";
   include("bottom.txt");
?>

このページを表示するには、Web ブラウザーで http://localhost/logout.php にアクセスします。すると、図 3 のように値がクリアされたことがわかります。

図 3. 値がクリアされたページ
値がクリアされたページのスクリーン・キャプチャー
値がクリアされたページのスクリーン・キャプチャー

これよりも簡単にデータをクリアする方法は、セッションを終了することです (リスト 7 を参照)。

リスト 7. セッションを終了する
<?
   session_start();
   session_destroy();

   include("top.txt");
   echo "Thank you for using the Workflow System.";
   echo "You may <a href=\"login.php\">log in again</a>.";
   include("bottom.txt");
?>

この場合も、同じく session_start() を使用して現行セッションに参加してからでないと、セッションを破棄できないことに注意してください。このステップにより、セッションに関連付けられたすべての値がクリアされます。

ファイルをアップロードする

このセクションでは、ユーザーがサンプル・ワークフロー・アプリケーション内で、ファイルをアップロードして保存できるように、アップロード・フォームを作成します。

アップロードの仕組み

HTML フォームでは、テキスト情報に加え、文書を送信することもできます。そこで、HTML フォームを使用して、ユーザーがファイルをシステムに追加できるようにします。このプロセスの仕組みは以下のとおりです。

  1. ユーザーがフォームをロードします。このフォームでは、ユーザーがアップロードするファイルを選択できます。
  2. ユーザーがフォームを送信します。
  3. ブラウザーは、ユーザーが選択したファイルとそのファイルに関する情報をリクエストに含めてサーバーに送信します。
  4. サーバーは、送信されたファイルを一時ストレージの場所に保存します。
  5. フォームの送信を処理する PHP ページが、ファイルを一時ストレージから永続ストレージに移します。

上記のプロセスを開始するために、まずは実際のフォームを作成します。

アップロード・フォーム

ファイルをアップロードするためのフォームは、登録ページとログイン・ページで使用したフォームと似ていますが、2 つの重要な違いがあります (リスト 8 を参照)。

リスト 8. アップロード・フォームを作成する
<?php
   include("top.txt");
?>

<h3>Upload a file</h3>

<p>You can add files to the system for review by an administrator.
Click <b>Browse</b> to select the file you'd like to upload, 
and then click <b>Upload</b>.</p>

<form action="uploadfile_action.php" method="POST"
      enctype="multipart/form-data">

    <input type="file" name="ufile" \>
    <input type="submit" value="Upload" \>

</form>

<?php
   include("bottom.txt");
?>

enctype 属性はブラウザーに対し、情報を特定のフォーマットで送信するように指示します。上記で指定しているのは、単なる名前と値のペアのリストではなく、複数のセクションからなる情報を許容するフォーマットです。

ファイルの入力ボックスでは、ユーザーが「Browse... (参照...)」をクリックしてファイルを選択することができます (図 4 を参照)。

図 4. アップロードするファイルを選択する
アップロードするファイルを選択する方法のスクリーン・キャプチャー
アップロードするファイルを選択する方法のスクリーン・キャプチャー

top.txt に、ファイルへのリンクを追加します (リスト 9 を参照)。

リスト 9. アップロード・フォームをインターフェースに追加する
...
   <li><a href="#" shape="rect">Home</a></li>
   <li><a href="uploadfile.php" shape="rect">Upload</a></li>
   <li><a href="#" shape="rect">Files</a></li>
                    ...

これで、ファイルをアップロードして、アップロードされた情報を見ることができます。

アップロードされる情報

ブラウザーからファイルをアップロードすると、PHP はそのファイルに関する情報の配列を受け取ります。この情報は、入力フィールド名に基づく名前で $_FILE 配列に格納されます。例えば、フォームに「ufile」という名前のファイル入力フィールドがある場合、このファイルに関するすべての情報は、配列 $_FILE['ufile'] に格納されます。

この配列では、ユーザーが複数のファイルをアップロードできるようになっています。それぞれのファイルが固有の名前になっている限り、各ファイル固有の配列が作成されます。

ここで、$_FILE を配列と呼んでいることに注意してください。シリーズ第 1 回では、フォームの中でパスワード用に同じ名前を持つ複数の値を渡すときに、配列の値自体が配列になるという状況がありました。

今回の例では、$_FILE 配列の値のそれぞれが連想配列になります。例えば、ufile ファイルには以下の情報が含まれています。

  • $_FILE['ufile']['name']— ファイルの名前 (例えば、uploadme.txt)
  • $_FILE['ufile']['type']— ファイルのタイプ (例えば、image/jpg)
  • $_FILE['ufile']['size']— アップロードされたファイルのサイズ (バイト数)
  • $_FILE['ufile']['tmp_name']— アップロードされたファイルのサーバー上での一時的な名前と場所
  • $_FILE['ufile']['error']— このアップロードによって発生したエラー・コード (該当する場合)

存在するはずのファイル情報はわかっているので、ファイルを処理する前に、ファイルが実際にアップロードされたかどうかを確認するようにします。

ファイルの有無を確認する

ファイル関連の処理を行う前に、ファイルが実際にアップロードされたかどうかを知る必要があります。それには、このフォームのアクション・ページ uploadfile_action.php を作成し、リスト 10 のコードを追加します。

リスト 10. アップロード済みのファイルを確認する
<?php

   session_start();

   include("top.txt");

   if(isset($_FILES['ufile']['name'])){
       echo "<p>Uploading: ".$_FILES['ufile']['name']."</p>";
   } else {
       echo "You need to select a file.  Please try again.";
   }

   include("bottom.txt");
?>

ユーザーがアップロードするファイルを指定していなければ、ブラウザーから $_FILES['ufile']['name'] が渡されることはありません (isset() は、変数の値がヌルの場合にも false を返すことに注意してください)。

次は、ファイルを保存します。

ファイルを保存する

アップロードされたファイルの保存を開始する前に、その保存先を決めます。ファイルが承認されるまでは、そのファイルには Web サイトからアクセスできないようにすることが望ましいため、メインのドキュメント・ルートの外部にディレクトリーを作成します。

この例では /var/www/hidden/ を使用します。このディレクトリーにすべてのファイルが格納されることになるため、定数を定義するのが得策だと思います。定数は変数と似ていますが、定数の場合、いったん設定した値を変更することはできません。scripts.txt を開いて、以下のリスト 11 に示す定義を追加してください。

リスト 11. 定数を作成する
...
}
   define("UPLOADEDFILES", "/var/www/hidden/");
?>

これで、この定義をアップロード・ページで使用することができます。ただし、そのページには scripts.txt もインクルードすることが条件です (リスト 12 を参照)。

リスト 12. アップロードされたファイルを保存する
<?php
   include("top.txt");
   include("scripts.txt");

   if(isset($_FILES['ufile']['name'])){
       echo "Uploading: ".$_FILES['ufile']['name']."<br>";

       $tmpName = $_FILES['ufile']['tmp_name']; 
       $newName = UPLOADEDFILES . $_FILES['ufile']['name'];  

       if(!is_uploaded_file($tmpName) ||
                            !move_uploaded_file($tmpName, $newName)){
            echo "FAILED TO UPLOAD " . $_FILES['ufile']['name'] .
                 "<br>Temporary Name: $tmpName <br>";
       } else {
            echo "File uploaded.  Thank you!";
       } 

   } else {
     echo "You need to select a file.  Please try again.";
  }
   include("bottom.txt");
?>

上記のコードでは、最初にファイルの現在の場所とそのテンポラリー名 (tmp_name) を取得し、上記で定義した定数を使用して、ファイルの保存先を決定します (定数は $ で始まらないことに注意してください)。

次に、if-then 文で 2 つの処理を行います。移動しようとしているファイルが、ユーザーがだまして操作させようとしている /etc/passwd などのファイルではなく、実際にサーバーにアップロードされたファイルであることを確認します。is_uploaded_file() 関数から false が返された場合、その否定である !is_uploaded_file()true となり、PHP はエラー・メッセージを表示する処理に移行します。

is_uploaded_file() が true を返した場合、これはファイルがアップロードされたファイルであることを意味するため、move_uploaded_file() 関数によって、現在の場所から新しい場所へのファイルの移動を試行することができます。ファイルを移動できなければ、この関数は false を返します。その場合もこの関数の否定形が true になるため、エラー・メッセージが表示されます。

つまり、ファイルがアップロードされたファイルでない場合、またはファイルを移動できない場合には、エラー・メッセージが表示され、それ以外の場合には成功を示すメッセージが表示されるということです (図 5 を参照)。

図 5. 成功メッセージ
成功メッセージのスクリーン・キャプチャー
成功メッセージのスクリーン・キャプチャー

これでファイルを入手できたので、その情報を記録して、後で取得できるようにする必要があります。

XML を使用する: DOM

このセクションでは、ユーザーがアップロードした文書に関する情報を記録する XML ファイルを作成します。

XML とは何か?

最近では、何らかの XML を使わなければ、大したプログラミングをすることができません。幸い、XML を理解するのは簡単です。皆さんはおそらく、XML の類である HTML を扱った経験があるでしょう。例えば、リスト 13 に記載する HTML V4.01 のページについて考えてみましょう。

リスト 13. HTML の例
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
            "http://www.w3.org/TR/html4/loose.dtd">
<HTML>
<HEAD>
   <TITLE>Workflow System</TITLE>
</HEAD>
<BODY>
   <H1 align="center">Welcome to the Workflow System!</H1>
   We're glad you could make it.
   <P>
   Don't forget to log in!
</BODY>
</HTML>

このページには、XML との共通点が多数あります。まず 1 つは、ページは HEADBODYなどの要素で構成され、これらの要素のそれぞれが開始タグ (<BODY> など) と終了タグ (</BODY> など) でマークアップされていることです。要素の中には、他の要素 (TITLE など) やテキスト (Workflow System など) を含めることができます。また、要素に属性を設定することもできます (align="center" など)。

ただし XML には、HTML には該当しない制約がいくつかあります。その一例は、XML は「整形式」でなければならないことです。整形式とは、すべての開始タグ (<H1> など) に対し、終了タグ (</H1> など) が必要となることを意味します。つまり、<P> タグには終了タグ </P> が必要です (例外として、空要素の場合は省略して <P /> と記述することもできます)。また、XML では大/小文字が区別されるため、<P> タグを閉じるには、</p> タグではなく </P> タグを使用しなければなりません。

情報を保管する方法

次の 2 つのセクションにわたり、ユーザーからアップロードされた文書に関する情報を一覧にする XML ファイルを扱います。このファイルでは文書ごとに、ステータス、アップロードしたユーザー、そして文書自体に関する情報を一覧にします。さらに、システム全体の統計情報も一覧に含めます。

このファイルは、リスト 14 に示すようなものになります。

リスト 14. ファイルについて記述する XML
<?xml version="1.0"?>
<workflow>
   <statistics total="2" approved="1"/>
   <fileInfo status="approved" submittedBy="roadnick2">
      <approvedBy>tater</approvedBy>
      <fileName>timeone.jpg</fileName>
      <location>/var/www/hidden/</location>
      <fileType>image/jpeg</fileType>
      <size>2020</size>
   </fileInfo>
   <fileInfo status="pending" submittedBy="roadnick">
      <approvedBy/>
      <fileName>timeone.jpg</fileName>
      <location>/var/www/hidden/</location>
      <fileType>image/jpeg</fileType>
      <size>2020</size>
   </fileInfo>
</workflow>

上記の例には、読みやすくするためにスペースを追加してありますが、これがファイルの全体的な構造です。文書ごとに固有の fileInfo 要素があり、この要素の属性として文書のステータスと所有者に関する情報が含まれており、ファイル自体に関する情報は要素の内容として含まれています。ファイル自体に関する情報は、統計文書の中でも一覧にされます。

これらの情報を操作するには、DOM (Document Object Model) を使用します。

DOM とは何か?

DOM (Document Object Model) とは、XML データを情報の階層ツリーとして表現する手段です。例として、上記の fileInfo 要素を見てみましょう (リスト 15 を参照)。

リスト 15. 単一のファイルについて記述する XML
   <fileInfo status="approved" submittedBy="roadnick2">
      <approvedBy>tater</approvedBy>
      <fileName>timeone.jpg</fileName>
      <location>/var/www/hidden/</location>
      <fileType>image/jpeg</fileType>
      <size>2020</size>
   </fileInfo>

XML文書に含まれる個々の情報は、一種のノードとして表現されます。例えば上記には、fileInfo 要素ノード、status 属性ノード、複数のテキスト・ノード (tatertimeone.jpg2020 など) があります。要素の間にある個々のホワイト・スペースでさえも、テキスト・ノードとしてみなされます。

DOM は、ノードを親子関係で配列します。上記の例で言うと、approvedBy 要素は fileInfo 要素の子であり、tater テキストは approvedBy 要素の子です。この関係は、fileInfo 要素には 11 の子ノード (要素が 5 つ、そしてホワイト・スペースのテキスト・ノードが 6 つ) があることを意味します。

DOM オブジェクトを扱えるようにするための API には、さまざまなものがあります。初期の頃の PHP のバージョンでは DOM 風の構造を実装していましたが、その構造は、(W3C によって維持管理されている) 実際の DOM 勧告のメソッドおよび定義とはあまり一致していませんでした。そのため、PHP V5 には DOM 標準に大幅に近くなった新しい DOM 操作一式が導入されています。

これからこの新しい API を使用して、文書の情報ファイルを作成し、変更します。

オブジェクトについての簡単な説明

PHP でのオブジェクトとオブジェクト指向プログラミングについてはシリーズ第 3 回で詳しく説明しますが、以降のセクションで説明する DOM API と SAX API では、オブジェクトとオブジェクト指向プログラミングの両方を使用しています。説明を進める前に、オブジェクトとは何か、そしてオブジェクトはどのような動作をするのか、といったことについて基本的な内容を理解する必要があります。

オブジェクトとは、関数の集まりであると考えてください。例えば、リスト 16 のようなクラス (つまり、オブジェクト・テンプレート) を作成することができます。

リスト 16. 極めて単純なクラス
class Greeting{
    function getGreeting(){
       return "Hello!";
   };
   function getPersonalGreeting($name){
       return "Hello, ".$name."!";
   }; 
}

オブジェクトを作成すると、そのオブジェクトの関数を参照することができます (リスト 17 を参照)

リスト 17. オブジェクトを作成して使用する
$myObject = new Greeting();
$start = $myObject->getPersonalGreeting("Nick");

上記の例で作成している myObject オブジェクトは、文字列や数値などと同じように、変数を使用して参照することができます。オブジェクトの関数を参照するには、-> 表記を使用します。その点を除けば、通常の関数を呼び出す場合と同じです。

今のところ、オブジェクトについては、これだけ知っていれば十分です。

情報を保存するための準備をする

文書の情報ファイルの作成を開始する準備ができました。最終的に、save_document_info() という名前の関数を作成することになるので、まずはこの関数を呼び出す処理を uploadfile_action.php に追加します (リスト 18 を参照)。

リスト 18. save_document_info() 関数を呼び出す
  if(!is_uploaded_file($tmpName) ||
     !move_uploaded_file($tmpName, $newName)){
    echo "FAILED TO UPLOAD " . $_FILES['ufile']['name'] .
         "<br>Temporary Name: $tmpName <br>";
  } else {

    save_document_info($_FILES['ufile']);

  } 
...

渡す必要があるのはアップロードされたファイルに関する情報だけです。したがって、save_document_info() の処理を楽にするために、$_FILES 配列ごと渡すのではなく、ufile ファイルに関する情報だけを渡します。

次は、関数を作成します。

関数

ここからは DOM の助けを借りて XML ファイルの操作を続けます。

DOM 文書を作成する

最初に、データを操作するために使用できる、DOM の Document オブジェクトを作成します。scripts.txt ファイルを開いて、リスト 19 のコードを追加します。

リスト 19. DOM 文書を作成する
...
   define("UPLOADEDFILES", "/var/www/hidden/");

   function save_document_info($fileInfo){

       $doc = new DOMDocument('1.0');

   }
?>

Document オブジェクト (この例では $doc という名前) を作成する実際のプロセスは至って簡単です。DOMDocument クラスは、PHP V5 のコアの一部となっています。データに対するほとんどの操作は、このオブジェクトを使用して実行します。

要素を作成する

実際に使用できる Document を作成したので、このオブジェクトを使用して文書のメイン (つまり、ルート) 要素を作成します (リスト 20 を参照)。

リスト 20. 要素を作成する
...
function save_document_info($fileInfo){

   $doc = new DOMDocument('1.0');
   $root = $doc->createElement('workflow');
   $doc->appendChild($root);

}
...

上記のコードでは、$doc オブジェクトに対し、新しい要素ノードを作成して $root 変数にその要素ノードを返すように指示します。次に Document に対し、その要素を自身の子として追加するように指示します。

この文書をファイルとして保存するとどうなるでしょう?

文書をファイルに保存する

PHP を使用して XML を処理するメリットの 1 つは、PHP には Document の内容をファイルに保存するための簡単な手段が用意されていることです (信じられないかもしれませんが、普通はファイルへの保存をこれほど簡単にはできません)。実際に何が生成されているのかを確認するために、リスト 21 のコードを save_document_info() に追加します。

リスト 21. DOM 文書を保存する
...
function save_document_info($fileInfo){

   $doc = new DOMDocument('1.0');
   $root = $doc->createElement('workflow');
   $doc->appendChild($root);

   $doc->save(UPLOADEDFILES."docinfo.xml");

}
...

先ほど UPLOADEDFILES 定数を定義したので、ここでは同じディレクトリー内に新しいファイルを配置して、この定数を参照するだけで済みます。現時点でブラウザーからファイルをアップロードすると、docinfo.xml ファイルはリスト 22 のような内容になっているはずです。

リスト 22. 基本ファイル
<?xml version="1.0"?>
<workflow/>

最初の行について気に掛ける必要はありません。これは XML 宣言であり、標準的なものですが、(ほとんどの場合は) オプションです。

要素は保存されていますが、要素には実際に子 (つまり、内容) を追加していないため、空要素として記述されています。

今度は、さらに複雑な要素を追加してみましょう。

属性を作成する

ファイルに実際の情報を追加するところから始めます。前のステップをテストしたおかげで、すでに docinfo.xml が作成されています。最初のファイルの情報が完全に保存されるまでは、docinfo.xml ファイルの作成が続いているものと見なすことができます。

まず、statistics 要素を作成します (リスト 23 を参照)。

リスト 23. 要素に属性を設定する
...
function save_document_info($fileInfo){

   $doc = new DOMDocument('1.0');
   $root = $doc->createElement('workflow');
   $doc->appendChild($root);

   $statistics = $doc->createElement("statistics");
   $statistics->setAttribute("total", "1");
   $statistics->setAttribute("approved", "0");
   $root->appendChild($statistics);

   $doc->save(UPLOADEDFILES."docinfo.xml");

}
...

新しい要素を作成するために、引き続き Document を使用していますが、今回作成する要素は statistics という名前のものです (Document は、大半のオブジェクトにとって「ファクトリー」の役割を果たします)。

$statistics という名前の Element オブジェクトを作成した後は、このオブジェクトの組み込み関数を使用して total 属性と approved 属性の 2 つを設定することができます。この作業が完了したら、$doc の子としてではなく $root の子として、この要素を追加します。その違いは、ファイルを保存するとわかります (リスト 24 を参照)。

リスト 24. 生成されたファイル
<?xml version="1.0">
<workflow><statistics total="1" approved="0"/></workflow>

このファイルでは、2 つの点に注目してください。1 つは、statistics 要素が workflow 要素の子になっていることです。もう 1 つは、このファイルには余分なホワイト・スペースがなく、statistics 要素が workflow の最初の子となっています (目にすることが多いのはプリティー・プリントとして記述された XML ですが、その場合はテキスト・チャンクのそれぞれが子ノードです)。

次は、実際の情報を追加する場合について見ていきましょう。

ファイル情報の要素を作成する

実際のファイル情報の要素を作成する準備は整っています。このプロセスでは、上記で学んだ手法を使用します (リスト 25 を参照)。

リスト 25. 実際のファイル情報を作成する
...
function save_document_info($fileInfo){

   $doc = new DOMDocument('1.0');
   $root = $doc->createElement('workflow');
   $doc->appendChild($root);

   $statistics = $doc->createElement("statistics");
   $statistics->setAttribute("total", "1");
   $statistics->setAttribute("approved", "0");
   $root->appendChild($statistics);

   filename = $fileInfo['name'];
   $filetype = $fileInfo['type'];
   $filesize = $fileInfo['size'];

   $fileInfo = $doc->createElement("fileInfo");

   $fileInfo->setAttribute("status", "pending");
   $fileInfo->setAttribute("submittedBy", $_SESSION["username"]);

   $approvedBy = $doc->createElement("approvedBy");

   $fileName = $doc->createElement("fileName");
   $fileNameText = $doc->createTextNode($filename);
   $fileName->appendChild($fileNameText);

   $location = $doc->createElement("location");
   $locationText = $doc->createTextNode(UPLOADEDFILES);
   $location->appendChild($locationText);

   $type = $doc->createElement("fileType");
   $typeText = $doc->createTextNode($filetype);
   $type->appendChild($typeText);

   $size = $doc->createElement("size");
   $sizeText = $doc->createTextNode($filesize);
   $size->appendChild($sizeText);

   $fileInfo->appendChild($approvedBy);
   $fileInfo->appendChild($fileName);
   $fileInfo->appendChild($location);
   $fileInfo->appendChild($type);
   $fileInfo->appendChild($size);

   $root->appendChild($fileInfo);

   $doc->save(UPLOADEDFILES."docinfo.xml");

}
...

上記は大量のコードですが、新しい部分はほんのわずかしかありません。まず、関数に渡された情報からファイルに関する実際の情報を抽出します。続いて、追加するすべての情報を格納する fileInfo 要素を作成します。この要素に status 属性と submittedBy 属性を設定した後、要素の子の作成に取り組みます。

approvedBy 要素は簡単です。ファイルはまだ承認されていないため、この要素は空のままです。一方、fileName 要素には子としてテキストを追加しなければならないので、少し厄介ですが、幸いテキストを追加するプロセスもかなり単純です。この要素を作成した後、Document を使用して新規テキスト・ノードを作成し、その内容としてファイル名を含めます。そのテキスト・ノードを、fileName 要素の子として追加することができます。

このように作業を進めて、最終的に fileInfo の子となるすべての要素を作成します。その作業が完了したら、作成したすべての要素を fileInfo の子として追加します。そして最後に、fileInfo 要素自体をルート要素 (workflow) に追加します。

最終的な結果は、リスト 26 のようになります (読みやすくするためにスペースが追加されています)。

リスト 26. 生成された文書
<?xml version="1.0"?>
<workflow>
   <statistics total="1" approved="0"/>
   <fileInfo status="pending" submittedBy="roadnick">
      <approvedBy/>
      <fileName>signed.pem</fileName>
      <location>/var/www/hidden/</location>
      <fileType>application/octet-stream</fileType>
      <size>2754</size>
   </fileInfo>
</workflow>

当然のことながら、誰かが文書をアップロードするたびに情報ファイルを上書きし続けることはできません。そこで、次に既存の構造を扱う方法について見ていきます。

既存の文書をロードする

ファイルに情報を追加する方法を説明したので、今度はそれ以降のアップロードでファイルを扱う方法を見ていきます。最初にファイルがすでに存在するかどうかを確認し、その結果に応じて処理を行います (リスト 27 を参照)。

リスト 27. ファイルがすでに存在するかどうかを確認する
...
function save_document_info($fileInfo){

   $doc = new DOMDocument('1.0');

   $xmlfile = UPLOADEDFILES."docinfo.xml";

   if(is_file($xmlfile)){
      $doc->load($xmlfile);
      $workflowElements = $doc->getElementsByTagName("workflow");
      $root = $workflowElements->item(0);
   } else{
      $root = $doc->createElement('workflow');
      $doc->appendChild($root);

      $statistics = $doc->createElement("statistics");
      $statistics->setAttribute("total", "1");
      $statistics->setAttribute("approved", "0");
      $root->appendChild($statistics);
   }

   $filename = $fileInfo['name'];
   $filetype = $fileInfo['type'];
   $filesize = $fileInfo['size'];

   $fileInfo = $doc->createElement("fileInfo");
...
   $fileInfo->appendChild($size);

   $root->appendChild($fileInfo);

   $doc->save($xmlfile);

}

リスト 27 では、ファイルの場所を表す変数を作成します。これは、ファイルの場所を参照している箇所が複数あるからです。次に、そのファイルがすでに存在しているかどうかを確認します。存在する場合は、新規オブジェクトを作成する代わりに load() 関数を呼び出します。

この静的関数 ― シリーズ第 3 回で詳しく説明しますが、現時点では、静的関数とは、オブジェクトからではなくクラスから呼び出すことができる関数であると理解しておいてください ― は、ファイル内で表されている要素、テキストなどがすべて取り込まれている状態の Document オブジェクトを返します。

Document オブジェクトを取得した後は、workflow 要素が必要になります。この要素に、最終的に新しい fileInfo 要素を追加しなければならないためです。workflow 要素を取得するには、文書内で workflow という名前を持つすべての要素のリストを取得し、そのリストの最初の要素を選択します。

その後は、新しい fileInfo 要素をそのまま追加します。すると、その要素が元の要素に続いて表示されます。リスト 28 を参照してください (読みやすくするためにスペースが追加されています)。

リスト 28. 情報が追加されたファイル
<?xml version="1.0"?>
<workflow>
   <statistics total="1" approved="0"/>
   <fileInfo status="pending" submittedBy="roadnick">
      ...
   </fileInfo>
      <fileInfo status="pending" submittedBy="roadnick">
      <approvedBy/>
      <fileName>timeone.jpg</fileName>
      <location>/var/www/hidden/</location>
      <fileType>image/jpeg</fileType>
      <size>2020</size>
   </fileInfo>
</workflow>

一方、statistics についてはどうでしょう?明らかに、正しい統計ではなくなっているため、修正が必要です。

既存のデータを操作する

情報を文書に追加するだけでなく、文書にすでに含まれている情報を変更することもできます。例えば、以下の方法で statistics 要素の total 属性を更新することができます (リスト 29 を参照)。

リスト 29. statistics の属性を更新する
...
   if(is_file($xmlfile)){
      $doc = DOMDocument::load($xmlfile);
      $workflowElements = $doc->getElementsByTagName("workflow");
      $root = $workflowElements->item(0);

     $statistics = $root->getElementsByTagName("statistics")->item(0);
      $total = $statistics->getAttribute("total");
      $statistics->setAttribute("total", $total + 1);

   } else{
...

まず、既存の workflow 要素への参照を取得したときと同じように、既存の statistics 要素への参照を取得します。この例では、2 つのステップ (要素への参照の取得と、属性の更新) を 1 つにまとめています。要素への参照を取得したら、getAttribute() 関数を使用して、この要素の total 属性の値を取得することができます。そしてその値を setAttribute() で使用して、total 属性の更新後の値を設定することができます。

この結果はご推察のとおりで、リスト 30 に示すようになります (今度は、スペースは一切追加されていません)。

リスト 30. 結果として生成されたファイル
<?xml version="1.0"?>
<workflow><statistics total="2" approved="0"/><fileInfo status="pending"
submittedBy="roadnick">...

技術的には正しい統計とは言え、新規要素が追加された後にカウントして項目数を設定したほうがさらに正確です。そうすれば、カウントを一度設定するだけで済みます (リスト 31 を参照)。

リスト 31. 要素の数をカウントする
...
   if(is_file($xmlfile)){
      $doc->load($xmlfile);
      $workflowElements = $doc->getElementsByTagName("workflow");
      $root = $workflowElements->item(0);
   } else{
...
   $root->appendChild($fileInfo);

   $statistics = $root->getElementsByTagName("statistics")->item(0);
   $total =  $root->getElementsByTagName("fileInfo")->length;
   $statistics->setAttribute("total",$total);

   $doc->save($xmlfile);
}

上記の例では、統計の処理を一番下の、fileInfo 要素が追加された後の位置に移動しました。こうすることで、すべての fileInfo 要素のリストを取得して length プロパティーを参照すれば、要素の数を把握することができます。プロパティーについてはシリーズ第 3 回で詳しく説明します。現時点では、プロパティーとは、オブジェクトに割り当てられた変数であると考えてください。プロパティーの後には括弧がないため、プロパティーと関数は見分けることができます。

XML を使用してファイルを作成する方法を学んだところで、今度は階層データを保管する別の手法に目を向けます。それは、JSON です。

JSON を使用する

今度は XML の代わりに JSON を使用してデータを保管する方法について見ていきます。

JSON とは何か?

JSON (JavaScript Object Notation) とは、XML で保管する場合と同じような方法でデータを保管できるように情報を指定する手段ですが、JSON では冗長さが大幅に軽減されます。データ・ストアの JSON 表現は、例えばリスト 32 のようになります。

リスト 32. サンプル JSON ファイル
{
   "statistics" : {
                  "total" : 2, "approved": 0
                  },
   "fileInfo" : [
       {
         "status" : "pending",
         "submittedBy" : "nickChase",
         "approvedBy" : "",
         "fileName" : "NoTooMiRoadmap.jpg",
         "location" : "\/var\/www\/hidden\/",
         "fileType" : "image\/jpeg",
         "size" : 12813
       },
       {
         "status" : "pending",
         "submittedBy" : "stanley",
         "approvedBy" : "",
         "fileName" : "NoTooMiBeta.pem",
         "location" : "\/var\/www\/hidden\/",
         "fileType" : "application\/octet-stream",
         "size" : 1692
       }
   ]
}

いくつかの異なる表記に注目してください。workflow ルート要素を使用するのではなく、データが 1 つのオブジェクトの一部となり、個々のオブジェクトは波括弧 ({}) で区切られています。オブジェクト全体は、外側の括弧で囲まれています。

それぞれのオブジェクトには、1 つ以上のプロパティーがあります。上記の例の場合、statisticsfileInfo がプロパティーです。

statistics プロパティーの値 (コロン (:) の後に示されています) は、オブジェクトです。これがオブジェクトであると判断できる理由は、波括弧 ({}) でラップされているからです。このオブジェクト自体には totalapproved という 2 つのプロパティーがあり、その値はどちらも整数となっています。各プロパティーは、コンマで区切られます。また、すべての文字列 (プロパティーの名前を含む) は、引用符でラップされることにも注意してください。

fileInfo プロパティーの値は少し異なり、これはオブジェクトではなく、オブジェクトの配列です。配列であることは、角括弧 ([]) でラップされていることから判断できます。この例の場合、配列に含まれるオブジェクトのそれぞれが、波括弧 ({}) でラップされています。波括弧 ({}) でラップされたオブジェクトについては、上述の説明のとおりです。

以上の説明は、次のように要約することができます。

  1. オブジェクトは波括弧 ({}) でラップされます。
  2. オブジェクトはプロパティーの集合です。プロパティーはコロンで分離された名前と値のペアであり、各プロパティーはコンマで区切られます。
  3. 配列はオブジェクトまたはプロパティーの集合であり、各オブジェクトまたはプロパティーはコンマで区切られており、集合全体が角括弧 ([]) でラップされます。

JSON オブジェクトのプロパティーは関数にすることもできますが、それについてはこのチュートリアルでは説明しません (詳細は、「参考文献」を参照してください)。

次は、実際にこのデータを使用する方法について見ていきます。

PHP で JSON データを使用する方法

PHP で JSON データを使用するプロセスには、一般に以下の 3 つのステップがあります。

  1. 作成するオブジェクトと一致する構造でデータを保持する PHP オブジェクトを作成します。その構造は、一から作成することもあれば、既存の JSON データ・ストアから取得することもあります。
  2. PHP オブジェクトを操作します。この操作は、値の変更で構成される場合もあれば、データの追加または削除で構成される場合もあります。
  3. PHP オブジェクトを JSON に変換して保存します。

ワークフロー・データを使用して、以上の 3 つのステップを実行します。

配列とオブジェクトの比較

先ほど見たのは、オブジェクトをテキスト・ベースでシリアライズしたデータです。このデータを PHP で扱うには、データを実際の PHP オブジェクトに変換しなければなりません。それには、2 つの方法があります。

  • 1 つは、実際のオブジェクトに変換するという方法です。その場合は、以下のように PHP のオブジェクト表記を使用します。
    $fileInfo->size
  • もう 1 つの方法では、以下のように配列表記を使用します。その場合、データは (先ほど見たような) 連想配列として扱われます。
    $fileInfo["size"]

どちらの方法を使用するかは、PHP で変数をどのように作成するかによって決まります。

2 つの理由から、これから説明する例では配列表記を使用します。最も明らかな 1 つの理由は、これまで配列については詳しく説明しましたが、オブジェクトについてはまだ取り上げていないためです。もう 1 つの理由は、オブジェクト表記を使用すると、JSON 配列に追加するなどの特定の操作が困難になるか、あるいは不可能になるためです。

JSON オブジェクトを用意する

最初のステップは、データを保持するための基本的な PHP 配列を作成することです。作業を単純にするために、fileInfo 要素に相当する配列を作成します。scripts.txt ファイルを開いて、リスト 33 に記載する関数を作成します。

リスト 33. 基本的な PHP オブジェクトを用意する
function save_document_info_json($file){

   $filename = $file["name"];
   $filetype = $file["type"];
   $filesize = $file["size"];

   $fileInfo["status"] = "pending";
   $fileInfo["submittedBy"] = $_SESSION["username"];
   $fileInfo["approvedBy"] = "";
   $fileInfo["fileName"] = $filename;
   $fileInfo["location"] = UPLOADEDFILES;
   $fileInfo["fileType"] = $filetype;
   $fileInfo["size"] = $filesize;

}

多少並び方が変わっていますが、上記のコードに目新しいところは何もありません。$file 配列から値を抽出してから、それらの値を新しく (動的に) 作成した $fileInfo 配列に割り当てているだけです。

この $fileInfo 配列は 1 つのオブジェクトを表します。これを JSON として保存すると (その作業はこの後すぐに行います)、リスト 34 のような内容になります。

リスト 34. $fileInfo オブジェクトの内容
{
   "status" : "pending",
   "submittedBy" : "stanley",
   "approvedBy" : "",
   "fileName" : "NoTooMiBeta.pem",
   "location" : "\/var\/www\/hidden\/",
   "fileType" : "application\/octet-stream",
   "size" : 1692
}

次は、ネストされたプロパティーを処理する方法を説明します。

JSON を作成して保存する

ここまでは、単純な値を持つ、1 つのオブジェクトを扱ってきました。ここからは、もう少し深く掘り下げていくと、どのようになるかを見ていきます。

前の例では、1 つの fileInfo オブジェクトを作成しましたが、最終的には statistics プロパティーが組み込まれた workflow オブジェクト全体を作成します。けれども、statistics プロパティーはオブジェクトであり、それ自体にプロパティーがあります (リスト 35 を参照)。

リスト 35. プロパティーのプロパティー
function save_document_info_json($file){

   $workflow["statistics"]["total"] = 1;
   $workflow["statistics"]["approved"] = 0;

   $filename = $file['name'];
...

ご覧のように、$workflow 変数を作成した後、statistics プロパティーを作成します。これで、total プロパティーと approved プロパティーのそれぞれを指定することができます。

この方法で、任意の (理にかなった範囲で) 深さまでネストすることができます。

配列に項目を追加する

$workflow 変数を作成できたので、今度はこの変数の fileInfo プロパティーに $fileinfo オブジェクトを追加する必要があります。fileInfo は配列であることを思い出してください。したがって、オブジェクトを追加するには array_push() 関数を使用することができます (リスト 36 を参照)。

リスト 36. 配列に項目を追加する
function save_document_info_json($file){

   $workflow["fileInfo"] = array();
   $workflow["statistics"]["total"] = 1;
......
   $fileInfo["fileType"] = $filetype;
   $fileInfo["size"] = $filesize;

   array_push($workflow["fileInfo"], $fileInfo);
}

array_push() 関数は、1 つ以上の項目をターゲット配列に追加します。この例でターゲット配列に該当するのは、スクリプトの先頭で空の配列として作成した $workflow["fileInfo"] です。これは通常の (連想配列ではない) 配列であるため、最初に項目を追加したら、先頭に戻って以下のように参照することができます。

$workflow["fileInfo"][0]

次は、データを保存する方法について見ていきます。

JSON をシリアライズして保存する

この時点で、JSON として保存するデータ構造を表す変数は作成されているので、今度はそのデータをシリアライズして保存する方法を見ていきます。

実は PHP には json_encode() が用意されていて、$workflow などの変数を極めて簡単に JSON テキストに変換できるようになっています (リスト 37 を参照)。

リスト 37. 配列に項目を追加する
function save_document_info_json($file){

   $jsonFile = UPLOADEDFILES."docinfo.json";

   $workflow["fileInfo"] = array();
   $workflow["statistics"]["total"] = 1;
...
   $fileInfo["fileType"] = $filetype;
   $fileInfo["size"] = $filesize;

   array_push($workflow["fileInfo"], $fileInfo);

   $jsonText = json_encode($workflow);
   file_put_contents($jsonFile, $jsonText);

}

json_encode() 関数が $workflow オブジェクトを JSON テキストに変換した後、file_put_contents() がそのテキストを docinfo.json ファイルに保存します。

あとは upload_action.php ページに対して、XML ベースの関数ではなく、この新しい関数を呼び出すように指示するだけです (リスト 38 を参照)。

リスト 38. 新規関数を呼び出す
...
       if(!is_uploaded_file($tmpName) ||
                            !move_uploaded_file($tmpName, $newName)){
            echo "FAILED TO UPLOAD " . $_FILES['ufile']['name'] .
                 "<br>Temporary Name: $tmpName <br>";
       } else {

           save_document_info_json($_FILES['ufile']);

       }
...

この時点で、新しい文書をアップロードして docinfo.json ファイルの内容を確認することができます。ファイルは、リスト 39 のような内容になっているはずです (読みやすくするためにスペースが追加されています)。

リスト 39. 結果として生成された JSON ファイル
{
 "statistics":{"total":1,"approved":0},
 "fileInfo":[
       {"status":"pending",
        "submittedBy":"stanley",
        "approvedBy":"",
        "fileName":"NoTooMiBeta.pem",
        "location":"c:\/sw\/temp\/",
        "fileType":"application\/octet-stream",
        "size":1692}
       ]
}

このように、JSON 表記を作成するために必要な作業は何もありません。json_encode() 関数が代わりに処理してくれます。変数を再び取得するのも簡単です。その方法を次に説明します。

ファイルから JSON を読み取る

JSON を作成できるようになりましたが、JSON を読み取るにはどうすればよいでしょうか?それには、XML を扱う場合と同じく、既存の docinfo.json ファイルに新しい fileInfo オブジェクトを追加する必要があります。この作業には、これまでに目にしてきた関数の類を使用することができます (リスト 40 を参照)。

リスト 40. 結果として生成された JSON ファイル
function save_document_info_json($file){

   $jsonFile = UPLOADEDFILES."docinfo.json";

  if (is_file($jsonFile)){
      $jsonText = file_get_contents($jsonfile);
      $workflow = json_decode($jsonText, true);
   } else{
      $jsonText = '{"statistics": {"total": 0, "approved": 0}, "fileInfo":[]}';
      $workflow = json_decode($jsonText, true);
   }

   $filename = $file['name'];
...
   array_push($workflow["fileInfo"], $fileInfo);

   $total =  count($workflow["fileInfo"]);
   $workflow["statistics"]["total"] = $total;

   $jsonText = json_encode($workflow);
   file_put_contents($jsonFile, $jsonText);

}

前と同じように、最初にファイルが存在するかどうかをチェックします。ファイルが存在する場合は、$workflow 変数を一から作成するのではなく、file_get_contents() 関数を使用して既存の JSON テキストを抽出してから、json_decode() 関数を使用してそのテキストを PHP 変数に変換します。

json_decode() 関数の 2 番目の引数に注目してください。この引数は、配列を返すかどうかを PHP に対して指示します。デフォルト値 (つまり、値が指定されない場合に関数が使用する値) は false で、これは PHP が配列を返すのではなく、オブジェクトを返すことを意味します。この例では配列が必要であるため、true を値に指定します。

ファイルが存在しないために一から作成する場合の例として、このサンプル・コードでは、プレーンな JSON テキストをそのまま提供するだけでも構わないことを示しています。結局のところ、それが docinfo.json ファイルから取得される内容であるからです。

最後に配列に対して count() を実行することで、ファイルを保存する前に fileInfo オブジェクトの数を調べることができます。これにより、total に結果の値を設定することができます。

このすべてのデータを実際に使用する作業に移る前に、知っておかなければならない極めて重要なことが 1 つあります。

セキュリティーに関する重要な注意点

json_decode() 関数は、単にデータをロードするだけで、それ以外のコードを実行しません。これは、セキュリティー問題に影響されないという意味ではありません。この関数も、他の文字列処理関数がバッファー・オーバーフローなどの手法に対して脆弱なのと同じく、悪用される可能性があります。ただし、それは極めてまれな状況です。

それよりも大きな問題は、JSON データを JavaScript 実装に戻す際にあります。JavaScript の eval() 関数を使用して、JSON データを JavaScript オブジェクトに変換したくなるかもしれませんが、JSON データの内容を正確に把握していて信頼できるのでない限り、この関数を使用してはなりません。代わりに、この目的で作られた JSON ライブラリーを使用してください。

全体的な表示

これまでに学んだ知識をまとめれば、JSON を読み取って upload_action.php ページにファイルのリストを表示するスクリプトを作成することができます。まずは、scripts.txt に関数を作成するところから始めます (リスト 41 を参照)。

リスト 41. データを読み取る
function display_files(){

   echo "<table width='100%'>";
   echo "<tr><th>File Name</th>";
   echo "<th>Submitted By</th><th>Size</th>";
   echo "<th>Status</th></tr>";

   $workflow = json_decode(file_get_contents(UPLOADEDFILES."docinfo.json"), true);

   $files = $workflow["fileInfo"];

   for ($i = 0; $i < count($workflow["fileInfo"]); $i++){


      echo "<tr>";
      echo "<td>".$thisFile["fileName"]."</td>";
      echo "<td>".$thisFile["submittedBy"]."</td>";
      echo "<td>".$thisFile["size"]."</td>";
      echo "<td>".$thisFile["status"]."<td>";
      echo "</tr>";
   }
}

最初に docinfo.json ファイルの内容を分析するために、json_decode() を使用して $workflow オブジェクトを取得します。

このオブジェクトを取得したら、fileInfo プロパティーを抽出することで、すべてのファイルを表す配列を取得することができます。その後、各ファイルをループ処理してそのファイルの情報を表示します。

この関数は、upload_action.php ファイルに追加する必要があります (リスト 42 を参照)。

リスト 42. 表示用関数を呼び出す
...
            if(!is_uploaded_file($tmpName) ||
                            !move_uploaded_file($tmpName, $newName)){
            echo "FAILED TO UPLOAD " . $_FILES['ufile']['name'] .
                 "<br>Temporary Name: $tmpName <br>";
       } else {

           save_document_info_json($_FILES['ufile']);

           echo "<h3>Available Files</h3>";
           display_files();
       }

   } else {
     echo "You need to select a file.  Please try again.";
  }
   include("bottom.txt");
?>

最終結果として、図 6 に示すような HTML の表が表示されます。

図 42. 読み取り可能な情報の表
読み取り可能な情報の表のスクリーン・キャプチャー
読み取り可能な情報の表のスクリーン・キャプチャー

第 3 回では、この表をリンクやその他の情報および機能で拡張する方法について見ていきます。

まとめ

このチュートリアルでは、ワークフロー・アプリケーションの中核部分の作成を開始しました。それは、ユーザーがファイルを追加できるようにすることです。そのために、ユーザーがシステムにログインし、自身を認識するセッションを作成し、ファイルをアップロードすることができるようにしました。次に、ファイルをサーバー上に保存し、ファイルに関する情報を最初は XML、次は JSON で保存しました。この過程をとおして、以下のトピックについて説明しました。

  • セッションを作成する方法
  • 既存のセッションを使用する方法
  • ファイルをアップロードする方法
  • DOM を使用して XML ファイルを作成する方法
  • DOM を使用して XML データをロードする方法
  • DOM を使用して XML データを操作する方法
  • JSON データを作成する方法
  • JSON を使用してデータを操作する方法
  • テキスト・ファイルを保存およびロードする方法

第 3 回では、このアプリケーションを完成させます。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source, Linux, XML
ArticleID=953840
ArticleTitle=PHP の学習: 第 2 回 ファイルをアップロードし、ファイルの情報を XML または JSON 形式で保管し、表示する
publish-date=11212013