この章では、図1-1「サンプル・アプリケーションの概要」に示す、AnyCoアプリケーションのメインの表示ページを作成します。一度に5件の従業員レコードを表示し、従業員リストのページ間を移動できるようにします。
この章のトピックは、次のとおりです。
新規のPHPファイルac_emp_list.php
を作成し、初期状態で次を含めます。
<?php /** * ac_emp_list.php: list of employees * @package Employee */ define('NUMRECORDSPERPAGE', 5); session_start(); require('ac_db.inc.php'); require('ac_equip.inc.php'); $sess = new \Equipment\Session; $sess->getSession(); if (!isset($sess->username) ││ empty($sess->username)) { header('Location: index.php'); exit; } $page = new \Equipment\Page; $page->printHeader("AnyCo Corp. Employees List"); $page->printMenu($sess->username, $sess->isPrivilegedUser()); printcontent($sess, calcstartrow($sess)); $page->printFooter(); // Functions ?>
NUMRECORDSPERPAGE
定数は、表示する従業員レコードの件数を決定します。
$sess->getSession()
コールを使用して保存されているセッション・データを取得した後、ユーザーにページの表示権限があるかどうかを確認するための基本的な検証を行います。ここでの要件は、username
が設定されていることです。これが否である場合、ブラウザはログイン・ページindex.php
にリダイレクトされます。本番アプリケーションではここでさらに検証を追加して、タイムスタンプの確認を行い、一定のアイドル時間後にユーザーに再ログインを要求するなどします。ハッカーがセッション・データを見ることやセッションを偽装することを困難にするため、ユーザー名を暗号化することもできます。
ファイルの本文は、HTMLページのヘッダー、メニュー、コンテンツおよびフッターを出力します。
このファイルでは、前に使用したオブジェクト指向の手法は続けずに、PHPの通常の手続き型の手法を示します。Functions
コメントの後に、ページ・コンテンツを出力する関数を追加します。
/** * Print the main body of the page * * @param Session $sess * @param integer $startrow The first row of the table to be printed */ function printcontent($sess, $startrow) { echo "<div id='content'>"; $db = new \Oracle\Db("Equipment", $sess->username); $sql = "SELECT employee_id, first_name ││ ' ' ││ last_name AS name, phone_number FROM employees ORDER BY employee_id"; $res = $db->execFetchPage($sql, "Equipment Query", $startrow, NUMRECORDSPERPAGE); if ($res) { printrecords($sess, ($startrow === 1), $res); } else { printnorecords(); } echo "</div>"; // content // Save the session, including the current data row number $sess->empstartrow = $startrow; $sess->setSession(); }
これはEMPLOYEES
表で問合せを実行します。Db::execFetchPage()
メソッドはDb::execFetchAll()
とほぼ同じで、これについては後述します。表示するレコードがある場合、printrecords()
によってレコードが表示され、ない場合は、printnorecords()
によって表示するレコードがないことを示すメッセージが表示されます。コンテンツ出力の最終段階は、新しい開始行番号を使用してセッションを更新することです。
トップ・レベルのprintcontent()
コールでは、calcstartrow()
を使用して開始行番号を決定しています。次の関数をac_emp_list.php
に追加します。
/** * Return the row number of the first record to display. * * The calculation is based on the current position * and whether the Next or Previous buttons were clicked * * @param Session $sess * @return integer The row number that the page should start at */ function calcstartrow($sess) { if (empty($sess->empstartrow)) { $startrow = 1; } else { $startrow = $sess->empstartrow; if (isset($_POST['prevemps'])) { $startrow -= NUMRECORDSPERPAGE; if ($startrow < 1) { $startrow = 1; } } else if (isset($_POST['nextemps'])) { $startrow += NUMRECORDSPERPAGE; } } return($startrow); }
「Next」ボタンと「Previous」ボタンを含むフォームとともに行が表示されます。開始行は、どのボタンがクリックされたかと、ユーザーが到達しているデータ・セット内の位置を基に計算されます。
printrecords()
をac_emp_list.php
に追加し、フェッチしたレコードを表示します。
/** * Print the Employee records * * @param Session $sess * @param boolean $atfirstrow True if the first array entry is the first table row * @param array $res Array of rows to print */ function printrecords($sess, $atfirstrow, $res) { echo <<< EOF <table border='1'> <tr><th>Name</th><th>Phone Number</th><th>Equipment</th></tr> EOF; foreach ($res as $row) { $name = htmlspecialchars($row['NAME'], ENT_NOQUOTES, 'UTF-8'); $pn = htmlspecialchars($row['PHONE_NUMBER'], ENT_NOQUOTES, 'UTF-8'); $eid = (int)$row['EMPLOYEE_ID']; echo "<tr><td>$name</td>"; echo "<td>$pn</td>"; echo "<td><a href='ac_show_equip.php?empid=$eid'>Show</a> "; if ($sess->isPrivilegedUser()) { echo "<a href='ac_add_one.php?empid=$eid'>Add One</a>"; echo "<a href='ac_add_multi.php?empid=$eid'> Add Multiple</a>\n"; } echo "</td></tr>\n"; } echo "</table>"; printnextprev($atfirstrow, count($res)); }
この関数のロジックは、test_db.php
で説明したものとほぼ同じです。'EOF;'トークンは行の先頭に配置し、その後に空白が含まれないようにします。
権限のあるユーザーには、各従業員に備品を割り当てるためのリンクがさらに表示されます。HTML表の最後の部分では、printnextprev()
をコールして「Next」ボタンと「Previous」ボタンを表示します。
printnextprev()
をac_emp_list.php
に追加します。
/** * Print Next/Previous buttons as needed to page through the records * * @param boolean $atfirstrow True if the first array entry is the first table row * @param integer $numrows Number of rows the current query retrieved */ function printnextprev($atfirstrow, $numrows) { if (!$atfirstrow ││ $numrows == NUMRECORDSPERPAGE) { echo "<form method='post' action='ac_emp_list.php'><div>"; if (!$atfirstrow) echo "<input type='submit' value='< Previous' name='prevemps'>"; if ($numrows == NUMRECORDSPERPAGE) echo "<input type='submit' value='Next >' name='nextemps'>"; echo "</div></form>\n"; } }
printnextprev()
のロジックは、次のような境界ケースも処理します。
最初のページには「Previous」ボタンを表示しません。
ページ全体が表示されなかった場合は「Next」ボタンを表示しません。
最後に、printnorecords()
をac_emp_list.php
に追加して、表示するレコードがなくなったときにメッセージを表示します。
/** * Print a message that there are no records * * This can be because the table is empty or the final page of results * returned no more records */ function printnorecords() { if (!isset($_POST['nextemps'])) { echo "<p>No Records Found</p>"; } else { echo <<<EOF <p>No More Records</p> <form method='post' action='ac_emp_list.php'> <input type='submit' value='< Previous' name='prevemps'></form> EOF; } }
注意: EOF; トークンは行の先頭に配置し、その後に空白が含まれないようにします。 |
ここで2つのケースが考えられます。表に行が含まれていなかった場合と、ユーザーが表のページ間を移動していて「Next」をクリックしたときに表示するデータがなかった場合です。後者のケースは、表内の行数がNUMRECORDSPERPAGE
の倍数のときに発生します。
アプリケーションを実行する前に、Db::execFetchPage()
メソッドを作成する必要があります。ファイルac_db.inc.php
で、Db
クラスに新しいメソッドを追加します。
/** * Run a query and return a subset of records. Used for paging through * a resultset. * * The query is used as an embedded subquery. Don't permit user * generated content in $sql because of the SQL Injection security issue * * @param string $sql The query to run * @param string $action Action text for End-to-End Application Tracing * @param integer $firstrow The first row number of the dataset to return * @param integer $numrows The number of rows to return * @param array $bindvars Binds. An array of (bv_name, php_variable, length) * @return array Returns an array of rows */ public function execFetchPage($sql, $action, $firstrow = 1, $numrows = 1, $bindvars = array()) { // $query = 'SELECT * FROM (SELECT a.*, ROWNUM AS rnum FROM (' . $sql . ') a WHERE ROWNUM <= :sq_last) WHERE :sq_first <= RNUM'; // Set up bind variables. array_push($bindvars, array(':sq_first', $firstrow, -1)); array_push($bindvars, array(':sq_last', $firstrow + $numrows - 1, -1)); $res = $this->execFetchAll($query, $action, $bindvars); return($res); }
Oracleデータベースには行のサブセットを返すためのLIMIT
句がないため、コール元の問合せをネストする必要があります。PHPのarray_push()
関数は、外側の問合せの開始行番号と終了行番号で使用される追加のバインド変数を、コール元の問合せのバインド変数に追加します。
SQLテキストは連結されるため、SQLインジェクションのリスクに注意してください。ユーザー入力はこの関数に絶対に渡さないようにします。