この章のトピックは、次のとおりです。
AnyCoアプリケーションの作成前に、カスケード・スタイルシート・ファイルstyle.cssを作成します。次を含めます。
/* style.css */
body {
background: #FFFFFF;
color: #000000;
font-family: Arial, sans-serif;
}
table {
border-collapse: collapse;
margin: 5px;
}
tr:nth-child(even) {background-color: #FFFFFF}
tr:nth-child(odd) {background-color: #EDF3FE}
td, th {
border: solid #000000 1px;
text-align: left;
padding: 5px;
}
#header {
font-weight: bold;
font-size: 160%;
text-align: center;
border-bottom: solid #334B66 4px;
margin-bottom: 10px;
}
#menu {
position: absolute;
left: 5px;
width: 180px;
display: block;
background-color: #dddddd;
}
#user {
font-size: 90%;
font-style:italic;
padding: 3px;
}
#content {
margin-left: 200px;
}
これはアプリケーションに単純なスタイルを提供し、メイン・コンテンツの左側にメニューを配置します。表の行は1行置きに色を変えます。第1章「Oracle DatabaseでのPHPの導入」の図1-1を参照してください。
AnyCoアプリケーションに再利用可能なコンポーネントを提供するため、SessionおよびPageという2つのクラスを作成します。
Sessionクラスは、Webユーザー認証が追加されるクラスです。これは、中間層上にあるWebユーザーのセッション情報を保存および取得するコンポーネントも提供するため、これによりアプリケーションはステートフルになります。PHPセッションは、DRCPの概要で説明したOracleセッションとは直接関係しません。PHPセッションには、問合せ結果の現在の表示ページの開始行番号などのデータを格納できます。後続のHTTPリクエストはこの値をセッション・ストレージから取得し、結果の次のページを表示できます。
ac_equip.inc.phpという名前の新規のPHPファイルを作成し、初期の状態として次を含めます。
<?php
/**
* ac_equip.inc.php: PHP classes for the employee equipment example
* @package Equipment
*/
namespace Equipment;
/**
* URL of the company logo
*/
//define('LOGO_URL', 'http://localhost/ac_logo_img.php');
/**
* @package Equipment
* @subpackage Session
*/
class Session {
/**
*
* @var string Web user's name
*/
public $username = null;
/**
*
* @var integer current record number for paged employee results
*/
public $empstartrow = 1;
/**
*
* @var string CSRF token for HTML forms
*/
public $csrftoken = null;
}
?>
このファイルはネームスペース宣言(ここではEquipment)から始まっています。
コメント・アウトされているLOGO_URL定数については、第12章「BLOBのアップロードと表示」で後述します。
$username属性にはWebユーザーの名前が格納されます。$empstartrow属性には、現在表示されている従業員セットの最初の行番号が格納されます。これにより、図1-1「サンプル・アプリケーションの概要」のように、「Next」と「Previous」ボタンを使用して従業員データのページ移動が可能になります。$csrftoken値については、第9章「データの挿入」で説明します。
Sessionクラスに2つの認証メソッドを追加します。
/**
* Simple authentication of the web end-user
*
* @param string $username
* @return boolean True if the user is allowed to use the application
*/
public function authenticateUser($username) {
switch ($username) {
case 'admin':
case 'simon':
$this->username = $username;
return(true); // OK to login
default:
$this->username = null;
return(false); // Not OK
}
}
/**
* Check if the current user is allowed to do administrator tasks
*
* @return boolean
*/
public function isPrivilegedUser() {
if ($this->username === 'admin')
return(true);
else
return(false);
}
authenticateUser()メソッドで実装されるユーザー認証は、洗練性に欠け、安全性も高くありません。通常、PHP Webアプリケーションはそのアプリケーション専用のユーザー認証を実行します。ここでは、adminとsimonにのみアプリケーションの使用を許可します。認証の詳細は、次を参照してください。
http://www.oracle.com/technetwork/articles/mclaughlin-phpid1-091467.html
isPrivilegedUser()メソッドは、現在のユーザーに権限があるかどうかを示すブール値を返します。AnyCoアプリケーションでは、これを使用して、ユーザーにさらに別のレポートの閲覧と新規データのアップロードを許可するかどうかを判断します。権限の必要なこれらの操作を、AnyCoのadminにのみ許可します。
PHPでは、ユーザーがHTMLページ間を移動するときに、永続的と考えられるセッション値を格納できます。デフォルトでセッション・データはPHPサーバーのディスク上のファイルに保存されます。セッション・データは一意のcookie値によって識別され、ユーザーがcookieをオフにしている場合はURLで渡される値で識別されます。cookieの使用によりPHPは、ローカル・セッション・ストレージを適切なWebユーザーに関連付けることができます。
PHPセッションを使用すると、中間層Apacheプロセスをランダムに使用して、ユーザーのHTTPページ・リクエストをシームレスに処理することができ、この処理中には各ユーザーの現在のセッション・データにアクセスできます。PHPでは、セッション失効を処理する方法やセッション・データをデータベースへ格納する方法など、セッション処理について広範囲のカスタマイズを加えることができます。詳細は、PHPドキュメントを参照してください。
AnyCoアプリケーションのセッション値を格納、フェッチおよびクリアするため、次の3つのメソッドをSessionクラスに追加します。
/**
* Store the session data to provide a stateful web experience
*/
public function setSession() {
$_SESSION['username'] = $this->username;
$_SESSION['empstartrow'] = (int)$this->empstartrow;
$_SESSION['csrftoken'] = $this->csrftoken;
}
/**
* Get the session data to provide a stateful web experience
*/
public function getSession() {
$this->username = isset($_SESSION['username']) ?
$_SESSION['username'] : null;
$this->empstartrow = isset($_SESSION['empstartrow']) ?
(int)$_SESSION['empstartrow'] : 1;
$this->csrftoken = isset($_SESSION['csrftoken']) ?
$_SESSION['csrftoken'] : null;
}
/**
* Logout the current user
*/
public function clearSession() {
$_SESSION = array();
$this->username = null;
$this->empstartrow = 1;
$this->csrftoken = null;
}
これらは、PHPのセッション・データへのアクセスを可能にする、スーパーグローバル連想配列$_SESSIONを参照します。Session属性が変更されるとき、AnyCoアプリケーションはsetSession()をコールし、変更後の状態を記録します。その後、別のアプリケーション・リクエストが処理を開始する際、そのスクリプトはgetSession()メソッドをコールして、保存された属性値を取得します。三項演算子"?:"のテストでは、セッション値があればその値が使用され、ない場合はハードコードされたデフォルトが使用されます。
最後に、HTMLフォームのCSRF保護のため、次のメソッドをSessionクラスに追加します。これについては、第9章「データの挿入」の「ac_add_one.phpでのCSRFの例」の項で説明します。
/**
* Records a token to check that any submitted form was generated
* by the application.
*
* For real systems the CSRF token should be securely,
* randomly generated so it cannot be guessed by a hacker
* mt_rand() is not sufficient for production systems.
*/
public function setCsrfToken() {
$this->csrftoken = mt_rand();
$this->setSession();
}
Pageクラスには、HTML出力のブロックを出力するメソッドを実装し、アプリケーションの各Webページが同じ外観になるようにします。
ac_equip.inc.phpファイルで、Sessionクラスの閉じ括弧とPHPの閉じタグ?>の間に、新規のPageクラスを追加します。クラスの初期の状態は次のようになります。
/**
* @package Equipment
* @subpackage Page
*/
class Page {
/**
* Print the top section of each HTML page
* @param string $title The page title
*/
public function printHeader($title) {
$title = htmlspecialchars($title, ENT_NOQUOTES, 'UTF-8');
echo <<<EOF
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=utf-8">
<link rel="stylesheet" type="text/css" href="style.css">
<title>$title</title>
</head>
<body>
<div id="header">
EOF;
// Important: do not put white space on the 'EOF;' line before or after the tag
if (defined('LOGO_URL')) {
echo '<img src="' . LOGO_URL . '" alt="Company Icon"> ';
}
echo "$title</div>";
}
/**
* Print the bottom of each HTML page
*/
public function printFooter() {
echo "</body></html>\n";
}
}
printHeader()メソッドは、HTMLページの導入部(スタイルシートなど)を出力し、ページ・タイトルを出力します。
PHPの'heredoc'は、大きなHTMLコンテンツ・ブロックを出力します。テキスト内の変数$titleは展開され、その値が表示されます。閉じタグ'EOF;'は行の先頭に配置し、その後に空白が含まれないようにします。このようにしなかった場合、PHPパーサーがファイルの残りの部分を文字列テキストと見なすため、PHP変数と思われるものが出現したときに、解析エラーがランダムに生成されます。
後述の例でLOGO_URLを定義した場合、ヘッダーにはロゴも表示されます。これは現在ac_equip.inc.phpの上部でコメント・アウトされています。
printFooter()メソッドは単にHTMLページの本文を終了させています。一般的なアプリケーションではこれを拡張して、サイトの著作権情報など各ページの下部に含めるコンテンツを表示するのに使用されます。
AnyCoアプリケーションには左側にナビゲーション・メニューがあります。これを出力するためPageクラスにメソッドを追加します。
/**
* Print the navigation menu for each HTML page
*
* @param string $username The current web user
* @param type $isprivilegeduser True if the web user is privileged
*/
public function printMenu($username, $isprivilegeduser) {
$username = htmlspecialchars($username, ENT_NOQUOTES, 'UTF-8');
echo <<<EOF
<div id='menu'>
<div id='user'>Logged in as: $username </div>
<ul>
<li><a href='ac_emp_list.php'>Employee List</a></li>
EOF;
if ($isprivilegeduser) {
echo <<<EOF
<li><a href='ac_report.php'>Equipment Report</a></li>
<li><a href='ac_graph_page.php'>Equipment Graph</a></li>
<li><a href='ac_logo_upload.php'>Upload Logo</a></li>
EOF;
}
echo <<<EOF
<li><a href="index.php">Logout</a></li>
</ul>
</div>
EOF;
}
ユーザーごとにメニューをカスタマイズするため、ユーザー名とユーザーの権限ステータスが渡されます。これらの値はSessionクラスから渡されます。
このマニュアルの以降の章で、リンクの中で参照されるPHPファイルを作成します。これらのファイルを作成しないでリンクをクリックすると、所定のエラーが発生します。
これで、Db、SessionおよびPageというAnyCoアプリケーションで使用される3つのクラスが完成しました。
AnyCoアプリケーションの開始ページはログイン・ページです。index.phpという新規のPHPファイルを作成します。NetBeansの場合は、このファイルの既存のコンテンツを置き換えます。index.phpファイルには次を含める必要があります。
<?php
/**
* index.php: Start page for the AnyCo Equipment application
*
* @package Application
*/
session_start();
require('ac_equip.inc.php');
$sess = new \Equipment\Session;
$sess->clearSession();
if (!isset($_POST['username'])) {
$page = new \Equipment\Page;
$page->printHeader("Welcome to AnyCo Corp.");
echo <<< EOF
<div id="content">
<h3>Select User</h3>
<form method="post" action="index.php">
<div>
<input type="radio" name="username" value="admin">Administrator<br>
<input type="radio" name="username" value="simon">Simon<br>
<input type="submit" value="Login">
</div>
</form>
</div>
EOF;
// Important: do not put white space on the 'EOF;' line before or after the tag
$page->printFooter();
} else {
if ($sess->authenticateUser($_POST['username'])) {
$sess->setSession();
header('Location: ac_emp_list.php');
} else {
header('Location: index.php');
}
}
?>
index.phpファイルはsession_start()コールから始まります。コードで$_SESSIONスーパーグローバルを使用する場合は、これをコードに必ず含め、出力の作成前にコールする必要があります。
Sessionクラスのインスタンスが作成され、既存のセッション・データは$sess->clearSession()コールによって破棄されます。これにより、このファイルをログアウト・ページに使用できるようになります。index.phpがロードされると、Webユーザーがアプリケーションからログアウトさせられます。
ファイルは大まかに2つのパートに分類できます。HTMLフォームを作成する部分と、それを処理する部分です。実行パスはPHPスーパーグローバル$_POSTで決定されます。このファイルを初めて実行するときには、$_POST['username']が設定されないため、HTMLフォームがページ・ヘッダーとフッターとともに表示されます。このフォームを使用してWebユーザーはAdministratorまたはSimonとしてログインします。
フォームの発行アクションのターゲットは、このindex.phpです。そのため、ユーザーがブラウザでフォームを発行すると、同じPHPファイルが実行されます。発行メソッドが"post"であるため、PHPはスーパーグローバル$_POSTにフォーム値を移入します。今回は'if'文の2つ目の分岐が実行されます。
そしてユーザー認証が行われます。ラジオ・ボタンの入力値'admin'と'simon'が、$sess->authenticateUser()に渡される値です。有効なユーザーがセッション・データに記録されます。次にPHPはHTTPヘッダーを送り返し、ブラウザをac_emp_list.phpにリダイレクトさせます。このファイルは次の項で作成します。
ユーザーが$sess->authenticateUser()で認証されなかった場合、ログイン・フォームが再び表示されます。
header()コールの実行前にスクリプトでテキストが表示されないようにします。
この時点でのアプリケーションを実行するには、ブラウザにindex.phpをロードします。NetBeansの場合は、「Run」→「Run Project」を使用するか、[F6]を押します。ブラウザに次のように表示されます。
