エラー処理を様々なレベルで実施し、不正なユーザー入力からデータベース・エラーまで、あらゆるものからWebアプリケーションを保護する必要があります。ユーザー・エクスペリエンスをスムーズにするため、PHPのエラーはWebユーザーに表示されないようにする必要があります。これらは中間層ログ・ファイルで捕捉し、ユーザーにはタスクの再試行または別のタスクの実行の機会のみを与えるようにします。本番システムでは、php.iniのdisplay_errors
設定をOff
にする必要があります。
この章のトピックは、次のとおりです。
データベース・レベルでは、すべてのPHP OCI8エラーをチェックすることをお薦めします。
現状のac_db.inc.php
では、エラー・チェックは接続時にのみ__construct()
で行われています。
... if (!$this->conn) { $m = oci_error(); throw new \Exception('Cannot connect to database: ' . $m['message']); } ...
oci_error()
関数は連想配列を返します。その要素の1つに、Oracleエラー・メッセージのテキストが含まれています。
さらなる演習として、Db
クラスのエラー処理を向上させてください。このような変更を加えても、この演習の残りの部分に影響はありません。各PHP OCI8コールを評価し、戻り値をチェックする場所を決定します。oci_error()
をコールし、メッセージのテキストを取得します。接続エラーについては、前述のように引数をoci_error()
に渡さないでください。接続エラーではoci_error()
は引数を受け取りませんが、oci_parse()
からのエラーをチェックする場合は、接続リソースをoci_error()
に渡します。
$stid = oci_parse($conn, $sql); if (!$stid) { $m = oci_error($conn) ... }
oci_execute()
エラーの場合は、文ハンドルを渡します。
$r = oci_execute($stid); if (!$r) { $m = oci_error($stid) ... }
getempname()
を編集しprintcontent()
で例外をスローして、ac_show_equip.php
でエラーをシミュレートします。このコールに達すると、PHPでランタイム・エラーが発生します。
function printcontent($sess, $empid) {
echo "<div id='content'>\n";
$db = new \Oracle\Db("Equipment", $sess->username);
$empname = htmlspecialchars(getempname($db, $empid), ENT_NOQUOTES, 'UTF-8');
echo "$empname has: ";
throw new Exception;
$sql = "BEGIN get_equip(:id, :rc); END;";
...
ブラウザでアプリケーションを実行し、「Steven King」の「Show」リンクをクリックします。開発作業用にdisplay_errorsをOnに設定しているため、コンテンツ領域にエラーが表示されます。
エラーが、ユーザー名を表示するページの最初の部分に続いて出力されます。display_errors
をOff
に設定した本番サイトでは、この部分的なセクション・コンテンツが表示されます。これは望ましくありません。これを回避するには、PHPの出力バッファを使用します。
ac_show_equip.php
を編集し、printcontent()
をコールする箇所を変更します。PHPのtry-catchブロックでコールを囲み、次のように変更します。
... $page->printMenu($sess->username, $sess->isPrivilegedUser()); ob_start(); try { printcontent($sess, $empid); } catch (Exception $e) { ob_end_clean(); echo "<div id='content'>\n"; echo "Sorry, an error occurred"; echo "</div>"; } ob_end_flush(); $page->printFooter(); ...
ob_start()
関数は、以降生成される出力をすべてバッファに捕捉します。その他のPHP ob_*
関数は、バッファの破棄やブラウザへのフラッシュを実行するためのものです。このコード内の例外ハンドラのob_end_clean()
コールで「Steven King has:」というメッセージが破棄されるため、カスタム・エラー・メッセージを出力できます。
アプリケーションを再度実行すると、次のエラーが表示されます。
オブジェクト指向コードを使用しないで、例外をスローおよび捕捉するには、printcontent()
からブール値を返し、エラーを手動で処理します。実行を停止するには、PHPのtrigger_error()
を使用します。
ac_show_equip.php
のprintcontent()
関数を編集し、一時的な行を変更します。
throw new Exception;
変更後
trigger_error('Whoops!', E_USER_ERROR);
E_USER_ERROR
のようなPHPエラーを捕捉して処理するには、エラー・ハンドラ関数を登録できるPHPのset_error_handler()
関数を使用します。
ac_show_equip.php
の冒頭にset_error_handler()
へのコールを追加します。
...
session_start();
set_error_handler("ac_error_handler");
require('ac_db.inc.php');
require('ac_equip.inc.php');
...
コールされる関数も追加します。
/** * Error Handler * * @param integer $errno Error level raised * @param string $errstr Error text * @param string $errfile File name that the error occurred in * @param integer $errline File line where the error occurred */ function ac_error_handler($errno, $errstr, $errfile, $errline) { error_log(sprintf("PHP AnyCo Corp.: %d: %s in %s on line %d", $errno, $errstr, $errfile, $errline)); header('Location: ac_error.html'); exit; }
これはApacheログ・ファイルへのメッセージの記録と、エラー・ページへのリダイレクトを実行します。このエラー・ページを新規のHTMLファイルac_error.html
で作成します。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <!-- ac_error.html: a catch-all error page --> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title></title> </head> <body bgcolor="#ffffff"> <h1>AnyCo Corp. Error Page</h1> <p>We're sorry an error occurred.<p> <p><a href="index.php">Login</a></p> </body> </html>
アプリケーションを実行してログインします。「Show」をクリックして、従業員の備品を表示します。エラー・ページが表示されます。
システムでApacheエラー・ログを探します。たとえばOracle Linuxの場合は、/var/log/httpd/error_log
内にあります。ログにはPHPで生成されたメッセージが含まれています。
[Wed Apr 27 13:06:09 2011] [error] [client 127.0.0.1] PHP AnyCo Corp.: 256: Whoops! in /home/chris/public_html/ACXE/ac_show_equip.php on line 71, referer: http://localhost/~chris/ACXE/ac_emp_list.php
次の章に進む前に、printcontent()
内の臨時のtrigger_error()
コールを削除するかコメント・アウトします。
... // trigger_error('Whoops!', E_USER_ERROR); ...