連載
» 2000年11月28日 00時00分 公開

PHP4で作るWeb-DBシステム(5):実践編:データベースアプリケーションの作成 後編 (2/4)

[一志達也,株式会社東洋情報システム]

データの取得

 データによって動的に変化する画面には、データを取り出すプログラムが必要です。今回の場合は全画面で必要になります。ここでは、最初に使われる社員検索画面(emp_menu.php)の部署選択一覧を例に説明します。具体的には、部署(Dept)表の部署名称をデータベースから取り出し、その内容をHTMLにあてはめる処理を作成します。

画面1 これからemp_menu.phpとして作成していく画面 画面1 これからemp_menu.phpとして作成していく画面

■Oracleの場合

 PHP4を使ってOracleからデータを取り出す場合、まずはじめにselect文を解析する必要があります。解析とは、構文に間違いがないかなどを確認し、実行する準備を整える作業のことです。解析にはOCIParse関数を使いますが、この関数は引数に接続IDとselect文を要求します。OCIParse関数の構文の詳細は以下のとおりです。なお、Oracle用のemp_menu.php全体はリスト3として掲載してあります。

$stmt = OCIParse($conn, "select deptno, dname from dept");

 この関数は、返り値として解析結果を返します。従って、OCIParse関数の実行時には、OCILogon関数と同様に解析結果を変数に受け取っておきます。このとき変数に格納される結果は以降の命令で必要になるほか、解析時にエラーが発生していないことを判断するためにも利用されます。

 エラーが発生した場合は変数に「FALSE」が格納されますから、必要であればエラー処理を行います。ここでは特にエラー処理を行いませんが、たとえselect文に間違いがなくても、データベースの障害などでエラーが発生することも考えられます。めったにないと分かっていても、本格的なシステム構築時にはエラー処理を行うようにプログラミングするのが理想です。

 実行する問い合わせを解析したら即実行、といきたいのは山々ですが、その前にもう1つ行わなければならない命令があります。それが、問い合わせ結果から得られる列と、その値を格納する変数の関連付けです。この関連付けにはOCIDefineByName関数を使います。

OCIDefineByName($stmt, "DEPTNO", &$deptno);
OCIDefineByName($stmt, "DNAME", &$dname);

 リスト3からも分かるとおり、問い合わせの解析が終わったらselect文に含まれる列の数だけ関連付けを行います。データベースプログラムの作成に慣れていない方は、この命令の必要性を理解しづらいかもしれませんが、理解したときに大切さに気付くはずです。

 なぜならば、この命令が使えるおかげでデータベースからのデータの取得が極端に省力化されるからです。つまり、データベースから取り出した列のデータが、関連付けられた変数へ自動的に格納されるのです。その様子はこれから確認していただくとして、select文を実際に実行する命令を発行します。その命令にあたる関数はOCIExecute関数ですが、その構文は単純です。

OCIExecute($stmt);

 OCIExecute関数は、select文を解析した結果を受け取って、データベースに該当するデータを用意させます。このデータは「結果セット」と呼ばれ、Oracleのデータベース・バッファ・キャッシュという共有のメモリ領域に格納されています。この結果セットをPHP側で受け取り、適切な処理を行うプログラムを記述していけばいいのです。

 すでにリスト3を見てお気付きかもしれませんが、結果セットからデータを取り出す命令は、OCIFetch関数によって実行されます。OCIFetch関数は、実行されるたびに結果セット上のカーソルを1行進め、その行のデータを取り出します。データがあれば先ほどの説明どおり変数にデータを格納しますが、データがない(最終行を超えた)場合は「FALSE」を返します。

 ここで重要なのは、OCIFetch関数は1行ずつデータを取り出し、そのたびに変数に値を格納するということです。先ほどOCIDefineByName関数で関連付けた変数は、いずれもスカラー型変数であって、配列ではありません。従って、1行ずつ取り出しては変数に値を格納していくのです。

 もちろん、一度に2次元配列に結果セットを受け取った方が処理が速いのではないかとも思います。しかし、実際にはデータベースから取り出す要領に違いはありませんし、配列に受け取ってからの処理は同じものになってしまいます。結局は1行ずつ受け取る方が柔軟性も高く、プログラミングも容易になるのです。

 少し話が脱線しましたが、今回紹介しているリスト3の例ではOCIFetch関数をwhile関数の条件式に記述しています。もちろん、このリスト3以外の書き方でも同様の動作を行わせることは可能です。しかし、最も短く的確に命令を記述するには、この方法がベストだといえるでしょう。

while (OCIFetch($stmt)) {
  print('<OPTION VALUE="'.$deptno.'">'.$dname.'</OPTION>'."\n");
}

 このようにwhile文とOCIFetch関数を使えば、取り出した結果セットの最初から最後まで、簡単にデータを取り出せるようになります。こうして取り出したデータは、列ごとに変数に格納されていますから、ループ中にHTMLタグを生成できます。今回の例では部門名称の一覧を作成しますので、<OPTION>タグを動的に生成します。

 このとき問題となるのは、動的なHTMLタグは一部だけということです。しかし、心配することはありません。一般的なプログラム言語と同様に、PHPにも文字列を連結する手段が用意されていますから、それを使えば簡単に問題を解決できます。

 PHPにおいて、文字列の連結は「.」(ピリオド)で行います。静的な文字列は「"」か「'」で囲んでやれば区別されますから、静的なHTMLタグと動的に変化する部分(部門番号の値と、部門名称の値)を区切って変数に置き換えます。実行されたときには、変数に格納された値と文字列が連結されて、1つのHTMLタグが生成されることになるのです。

 こうしてみると非常に簡単ですが、リスト3でポイントとなるのは静的な文字列を「'」で囲んでいるところです。これは、HTMLタグの一部に「"」が使われているためで、それ以上の理由はありません。ちなみに、最後に連結されている「\n」だけは、「'」で囲むとエラーになるために「"」で囲んでいます。

print('<OPTION VALUE="'.$deptno.'">'.$dname.'</OPTION>'."\n");

 この「\n」は改行コードを入れるためのもので、必須ではありません。生成されたHTMLの見栄えを気にする場合はこのコードを含め、改行するようにした方がいいでしょう。

■PostgreSQLの場合

 PostgreSQLの場合も、取り出しまでの手順に関しては先に紹介したOracleの場合と大差ありません。違いがあるとすれば、関数の名称やデータを取り出す手法というところでしょう。そのほかに挙げておくとすれば、データベースへの接続時と同じ問題で、データの操作権限が必要になるところも違います。

 PostgreSQLの場合はユーザーを指定していませんが、新しく追加したApacheのプロセスオーナー(通常nobody)に、データの操作権限を与えておく必要があります。操作権限を与えるには、PostgreSQLのオーナーでLinuxにログインし、psqlを対象となるデータベースを指定して起動します。

 psqlが起動したら、

grant select, insert, update, delete on emp to nobody;

のように入力して、特定の表に関する権限を付与します。この例では、emp表への全操作権限をnobodyユーザーに付与しています。権限の種類や表などは状況に合わせて変更してください。

 それでは本題のデータ取得についてですが、PostgreSQLの場合は、Oracleと違ってselect文を解析する(Parseする)必要がありません。そのため、いきなりSQLを実行する命令、pg_Exec関数から手順を始めることになります。この関数を実行すると、問い合わせの結果セットが作られ、その検索結果を指定するためのIDが返されます。

 この点はOracleの場合と共通していますから、解説の必要はないでしょう。関数からの返り値を変数に格納しておきます。ここでリスト4を確認していただくとお分かりいただけると思いますが、今回はpg_Exec関数からの返り値をif文で判定するようにしています。

$result = pg_Exec($conn, "select deptno, dname from dept");
if ($result) {
  do {
    @$row = pg_Fetch_Row ($result, $i);
    if ($row) {
     print('<OPTION VALUE="'. $row[0]. '">'. $row[1]. '</OPTION>'. "\n")

     $i++;
    }
  } while ($row);
}

 こうすることで、データベースの問題などでpg_Exec関数がエラーになっても、きちんと判定して処理できるようになります。今回の例は判定するだけで特にエラー処理を加えていませんが、プログラムの実行中止やメッセージを組み入れるといいでしょう。

 結果セットが作成されたら、あとはOracleの場合と同じく、1行ずつデータを取り出していくだけです。PostgreSQLには、1行ずつだけでなく配列に取得する方法も用意されています。しかし、パフォーマンスなどに大差がないことと、Oracleとの比較を行いやすくするため、ここでは1行ずつ取得する方法を使うことにします。

 PostgreSQLの結果セットから、1行のデータを取り出す命令がpg_Fetch_Row関数です。Oracleの場合と違い、この関数でデータを取り出す際には、取り出そうとする行番号を指定する必要があります。例えば、「$result」という結果セットの1行目のデータを取り出すのであれば、

$row = pg_Fetch_Row($result, 0);

となります。

 pg_Fetch_Row関数を実行すると、指定した配列(この場合は$row)に取り出したデータが格納されます。この配列は、取り出した行に含まれる列数分の要素を持っていて、select文で指定した順に格納されています。今回の例では「Select deptno, dname from dept」としていますから、$row[0]にはdeptno列の値が格納されるわけです。

 select文で取り出す列に変数を関連付け、自動的に次の行を読み出していくOracleの場合と違うのがお分かりいただけるでしょうか。PostgreSQLの場合は、取り出す行も指定しなければなりませんし、値も配列から取り出さなければなりません。その違いは、プログラミングしていく上で便利でもあり不便でもあります。

 便利なところは、行番号を指定すれば何行目からでも取り出せるなど、プログラムの自由度が高いところです。また、変数と反復制御を使って、プログラムの手間を軽減させられるというメリットもあります。その逆に、列の順序を意識しなければならなかったり、メンテナンス時に理解しづらいというデメリットもあります。

 このように一長一短のある方法ではありますが、今回の例である部門名称選択一覧の作成を実際にプログラミングするとリスト4のようになります。pg_Fetch_Row関数は、OracleのOCIFetch関数のようにwhile文の条件に使うことができません。そこで、リスト4のようにdo while文を使い、後判定で最終行に達したことを判定します。

do {
  @$row = pg_Fetch_Row ($result, $i);
  if ($row) {
    print('<OPTION VALUE="'. $row[0]. '">'. $row[1]. '</OPTION>'. "\n");
    $i++;
  }
} while ($row);

 pg_Fetch_Row関数の実行時に、「@$row...」としているのは、その行でのエラーを無視させるためです。今回の例では、最終行を取り出した後にもう一度pg_Fetch_Row関数を実行してしまいます。その結果がFALSEになると最終行まで達したと判断してループを終了させているのです。

 ところが、pg_Fetch_Row関数は結果がFALSEになるとエラーとして出力してしまいます。これでは画面上にエラーメッセージが表示されてしまいますので、このようにエラーを無視するようにしておき、最終的な判断をwhile($row)で行っているわけです。また、タグの表示部分もエラーになったまま通過する可能性があるので、$rowに格納された結果を判断するようにしています。

 これらの判断さえ付け加えておけば、カウンタとなる変数「$i」をカウントアップしながら、1行ずつ取り出していくことができるようになります。繰り返しになりますが、タグを生成する部分では、$rowに列番号をつけて値を取り出しています。文字列の連結などについては、Oracleの場合と同様ですから特に問題ないでしょう。

 こうして作成したPHPプログラムを実行すると、PostgreSQL、Oracleともに同じHTML、すなわち同じ画面を出力します。試しにデータを変更してみると、画面に出力される部門の一覧にも変化があることがお分かりいただけると思います。

 これで社員検索画面は完成ですが、同じ一覧が社員の新規追加画面にも必要になります。同様の手順で新規追加画面も作成し、社員検索画面からボタンを押して遷移してみてください。


<?php
  $conn=ocilogon("scott","tiger","orcl");
?>
<HTML>
<HEAD>
<TITLE>社員検索条件入力画面</TITLE>
</HEAD>
<BODY>
<CENTER>
<TABLE><TR>
<TD ALIGN="center" COLSPAN="2"><HR WIDTH="400"></TD>
</TR><TR>
<TD ALIGN="center" COLSPAN="2"><H1>検索条件入力画面</H1></TD>
</TR><TR>
<TD ALIGN="center" COLSPAN="2">社員情報のメンテナンスを行えます。</TD>
</TR><TR>
<TD ALIGN="center" COLSPAN="2">検索条件を入力して検索してください。</TD>
</TR><TR>
<TD ALIGN="center" COLSPAN="2">氏名だけは部分一致検索を行います。</TD>
</TR><TR>
<TD ALIGN="center" COLSPAN="2"><HR WIDTH="400"></TD>
</TR><TR>
<FORM ACTION="emp_query.php" METHOD="post">
<TD ALIGN="right">部門名称:</TD><TD>
<SELECT NAME="inp_dept">
<OPTION SELECTED VALUE="NULL">指定なし</OPTION>
<?php
  $stmt = OCIParse($conn, "select deptno, dname from dept");
  OCIDefineByName($stmt, "DEPTNO", &$deptno);
  OCIDefineByName($stmt, "DNAME", &$dname);
  OCIExecute($stmt);
  while (OCIFetch($stmt)) {
    print('<OPTION VALUE="'.$deptno.'">'.$dname.'</OPTION>'."\n");
  }
  OCIFreeStatement($stmt);
?>
</SELECT>
</TD>
</TR><TR>
<TD ALIGN="right">社員番号:</TD><TD><INPUT TYPE="text" NAME="inp_empno"></TD>
</TR><TR>
<TD ALIGN="right">社員氏名:</TD><TD><INPUT TYPE="text" NAME="inp_ename"></TD>
</TR><TR></TR><TR>
<TD COLSPAN="2" ALIGN="center"><INPUT TYPE="submit" NAME="inp_submit" VALUE="検索実行">
<INPUT TYPE="reset" VALUE="条件削除"></TD>
</TR><TR>
</FORM>
<TD ALIGN="center" COLSPAN="2"><HR WIDTH="400"></TD>
</TR><TR>
<TD ALIGN="center" COLSPAN="2">新しく社員を追加する場合は、新規追加ボタンを押してください。</TD>
</TR><TR>
<FORM ACTION="emp_add.php" METHOD="post">
<TD ALIGN="center" COLSPAN="2"><INPUT TYPE="submit" NAME="inp_submit" VALUE="新規追加"></TD>
</FORM>
</TR><TR>
<TD ALIGN="center" COLSPAN="2"><HR WIDTH="400"></TD>
</TR><TR>
<TD ALIGN="right" COLSPAN="2">Copyright 2000, Tatsuya Ichishi</TD>
</TR></TABLE>
</CENTER>
</BODY>
</HTML>
<?php
  OCILogoff($conn);
?>
リスト3 完成したemp_menu.phpの内容(Oracle用)

<?php
  $conn=pg_Connect("host=cinderella dbname=employee port=5432");
?>
<HTML>
<HEAD>
<TITLE>社員検索条件入力画面</TITLE>
</HEAD>
<BODY>
<CENTER>
<TABLE><TR>
<TD ALIGN="center" COLSPAN="2"><HR WIDTH="400"></TD>
</TR><TR>
<TD ALIGN="center" COLSPAN="2"><H1>検索条件入力画面</H1></TD>
</TR><TR>
<TD ALIGN="center" COLSPAN="2">社員情報のメンテナンスを行えます。</TD>
</TR><TR>
<TD ALIGN="center" COLSPAN="2">検索条件を入力して検索してください。</TD>
</TR><TR>
<TD ALIGN="center" COLSPAN="2">氏名だけは部分一致検索を行います。</TD>
</TR><TR>
<TD ALIGN="center" COLSPAN="2"><HR WIDTH="400"></TD>
</TR><TR>
<FORM ACTION="emp_query.php" METHOD="post">
<TD ALIGN="right">部門名称:</TD><TD>
<SELECT NAME="inp_dept">
<OPTION SELECTED VALUE="NULL">指定なし</OPTION>


<?php
  $i = 0;
  $result = pg_Exec($conn, "select deptno, dname from dept");
  if ($result) {
    do {
      @$row = pg_Fetch_Row ($result, $i);
      if ($row) {
        print('<OPTION VALUE="'. $row[0]. '">'. $row[1]. '</OPTION>'. "\n");
        $i++;
      }
    } while ($row);
  }
?>


</SELECT>
</TD>
</TR><TR>
<TD ALIGN="right">社員番号:</TD><TD><INPUT TYPE="text" NAME="inp_empno"></TD>
</TR><TR>
<TD ALIGN="right">社員氏名:</TD><TD><INPUT TYPE="text" NAME="inp_ename"></TD>
</TR><TR></TR><TR>
<TD COLSPAN="2" ALIGN="center"><INPUT TYPE="submit" NAME="inp_submit" VALUE="検索実行">
<INPUT TYPE="reset" VALUE="条件削除"></TD>
</TR><TR>
</FORM>
<TD ALIGN="center" COLSPAN="2"><HR WIDTH="400"></TD>
</TR><TR>
<TD ALIGN="center" COLSPAN="2">新しく社員を追加する場合は、新規追加ボタンを押してください。</TD>
</TR><TR>
<FORM ACTION="emp_add.php" METHOD="post">
<TD ALIGN="center" COLSPAN="2"><INPUT TYPE="submit" NAME="inp_submit" VALUE="新規追加"></TD>
</FORM>
</TR><TR>
<TD ALIGN="center" COLSPAN="2"><HR WIDTH="400"></TD>
</TR><TR>
<TD ALIGN="right" COLSPAN="2">Copyright 2000, Tatsuya Ichishi</TD>
</TR></TABLE>
</CENTER>
</BODY>
</HTML>
<?php
  pg_Close($conn);
?>
リスト4 完成したemp_menu.phpの内容(PostgreSQL用)

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。