導入
Zend Optimizerを使用している場合、オプティマイザのバグのため、
最適化レベルを下げ、定数 DB_DATAOBJECT_NO_OVERLOAD = 0
を定義する必要があります。
さもないと、PHP がセグメンテーション違反を起こします。
PHP4でのFIXできないバグのため、
参照渡しの引数を伴うオーバーロードは使用できません
(PHP5 ではうまく動作します)。
もし、参照渡しの必要がある場合、定数 DB_DATAOBJECT_NO_OVERLOAD = 0
を定義してください。
DB_DataObject は SQL ビルダおよび
PEAR::DB の上位データモデリングレイヤです。
主な目的は、以下の通りです。
-
オブジェクト変数に基づく SQL の生成とステートメントの実行
-
データとそれに関連する周辺のソースコードのグループ化
-
データにアクセスし操作するためのシンプルで首尾一貫した API の提供
これはどういったことでしょうか?
より良く書かれた PHP アプリケーションやフレームワークを見ると、
データベースの表などにアクセスする部分をラップするクラスを使用する、
といった共通のアプローチを採っていることに気づくでしょう。
最初の例は次のような person オブジェクトとしてよく見かけられます。
<?php
require_once 'DB.php';
$db = DB::connect('mysql://someuser:somepass@localhost/pear_dbdo');
$db->setFetchMode(DB_FETCHMODE_ASSOC);
class MyPerson
{
// gets an array of data about the seleted person
function getPerson($id)
{
global $db;
$result = $db->query('SELECT * FROM person WHERE id=' . $db->quote($id));
return $result->fetchRow();
}
// example of checking a password.
function checkPassword($username, $password)
{
global $db;
$hashed = md5($password);
$result = $db->query(
'SELECT name FROM person WHERE name=' . $db->quote($username)
. ' AND password = ' . $db->quote($hashed)
);
return $result->fetchRow();
}
}
// get the persons details..
$array = MyPerson::getPerson(12);
echo $array['name'] . "\n";
?>
この例は、次のような SQL のテーブルで動作します。
CREATE TABLE IF NOT EXISTS `person` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(64) NOT NULL,
`password` varchar(32) NOT NULL,
`birthDate` date NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=13 ;
INSERT INTO `person` (`id`, `name`, `password`, `birthDate`) VALUES
(12, 'John', '098f6bcd4621d373cade4e832627b4f6', '1984-02-23');
このアプローチの主な利点は、単一の表に対する似たようなアクションを
グループ化する事ができるということと、重複するコード (例えば、2
つのメソッドが同様のことを行っている) を見つけやすいということでしょう。
また、グローバル変数 $db をここで使用していることに気づくでしょう。
実際にあなたはほとんどの場合で、
全てのクラスで共通のデータベース接続を使用しているでしょう。
では、これをどの様にするべきでしょうか?
次のステップは、値の格納場所としてオブジェクト変数を使用することです。
<?php
require_once 'DB.php';
$db = DB::connect('mysql://someuser:somepass@localhost/pear_dbdo');
$db->setFetchMode(DB_FETCHMODE_ASSOC);
class MyPerson
{
var $id;
var $name;
var $birthDate;
// gets an array of data about the seleted person
function get($id) {
global $db;
$result = $db->query('SELECT * FROM person WHERE id=' . $db->quote($id));
$array = $result->fetchRow();
foreach ($array as $key => $value) {
$this->$key = $value;
}
}
function getAge() {
return date('Y') - date('Y', strtotime($this->birthDate));
}
}
// now get the person and display the age.
$person = new MyPerson();
$person->get(12);
echo "{$person->name} is ". $person->getAge() . " years old\n";
?>
ご覧の通り、データの現在の行がデータコンテナにストアされ、
オブジェクトのデータを操作するためにメソッドを追加する、
あるいは、他の関連するオブジェクト (例えば、データベース間の表の関連)
をコールする事ができます。
次のステップですが、なぜデータベースで検索や取得を実行するためにメンバ変数を利用しないのでしょうか。
<?php
require_once 'DB.php';
$db = DB::connect('mysql://someuser:somepass@localhost/pear_dbdo');
$db->setFetchMode(DB_FETCHMODE_ASSOC);
class MyPerson
{
var $id;
var $name;
var $birthDate;
// does the query based on the value of $this->name
function find() {
global $db;
$this->result = $db->query('SELECT * FROM person WHERE name=' . $db->quote($this->name));
}
// fetches a row of data and sets the object variables to match it.
function fetch() {
$array = $this->result->fetchRow();
if (empty($array)) {
return false;
}
foreach ($array as $key=>$value) {
$this->$key = $value;
}
return true;
}
}
// now get the person and display the age.
$person = new MyPerson();
$person->name = "John";
$person->find();
while ($person->fetch()) {
echo "a {$person->name} has a birthday on {$person->birthDate}<br/>\n";
}
?>
ご覧の通り、find メソッドがコールされる前に値がオブジェクトに
アサインされることで、クエリに対する条件を設定することができます。
DB_DataObject はこれと同じように動作しますが、
whereAdd() メソッドでさらなる条件を追加する、あるいは、selectAdd()
メソッドで選択を制限することもできます。
当然、この方法を進めていき、
データベースの各表に対する小さなコンテナを大量に作成することもでき、
全てのコードはうまく各表と対応するでしょう。しかしながら、
上の例から分かるように、
全てのクラスはメソッドの共通セットのようになります。
-
get, find, fetch などデータをフェッチするメソッド
-
データストアから更新、登録、削除するメソッド
-
関連するオブジェクトのフェッチの自動化するメソッド
このため、これを改善するために
DB_DataObject
が誕生し、上にある問題に対する統一解として第一歩を踏み出しました。
しかし、多くの問題の複雑さが増すにつれ、
より詳細が調査されていきました。
その結果、DataObjects を持つようになり、
以下の機能を持つほどに成長しました。
-
(データベース接続に対する)
共通のシンプルな設定メソッド
-
データベース情報のための高速でシンプルなストア。
これにより、データを素早く配置するために主キーを利用することが
できます。
-
何が行われているのかをはっきりさせるためのデバッガ
-
基本的なデータの検証 -
文字列と整数型はチェックすることができます。
-
複雑な JOIN の作成や副問い合わせ (リンク)
による関連データの取得
-
現在のデータベース表の定義に基づいたクラスの生成と更新
(自動生成の章を参照してください)
-
setFrom() メソッドや toArray()
メソッドを使用することによる他のパッケージとの連携の容易さ
では、どの様なクラスになるでしょうか?
実際の DataObject コードの最後あたり..
<?php
// this is the common configuration code - place in a general site wide include file.
// this the code used to load and store DataObjects Configuration.
$options = &PEAR::getStaticProperty('DB_DataObject','options');
// the simple examples use parse_ini_file, which is fast and efficient.
// however you could as easily use wddx, xml or your own configuration array.
$config = parse_ini_file('example.ini',TRUE);
// because PEAR::getstaticProperty was called with an & (get by reference)
// this will actually set the variable inside that method (a quasi static variable)
$options = $config['DB_DataObject'];
// this is normally contained in your DataObjects file (autogenerated by the generator)
require_once 'DB/DataObject.php';
// by extending the base class DB_DataObject - you inherit all the common methods defined in it.
class DataObjects_Person extends DB_DataObject {
var $id; // this is a primary id (it's specified in a config file - explained later)
var $name;
var $friend;
// this is a simple function to get the persons friends..?
function getFriends()
{
$personObject = $this->factory('person');
// look for all people with their friend number matching this persons id.
$personObject->friend = $this->id;
// do the select query.
$personObject->find();
$array = array();
// fetch the results into the object.
while ($personObject->fetch()) {
// use the clone to copy - not really needed but get used to it for PHP5
$array[] = clone($personObject);
}
// return the results.
return $array;
}
}
// and this goes on your display code
// create a new person class..
$person = DB_DataObject::Factory('person');
// get the person using the primary key.
$person->get(12);
// get the friends.
$friends = $person->getFriends();
// DB_DataObjects is designed to make print_r useable to debug your applications.
print_r($friends);
?>
上の例は DB_DataObject
のコンポーネントを表しています。オプションを設定することで、
全てのコアオブジェクトは、生成された ini
ファイルからデータ定義を自動的に読み込み、
データベースにどの様にアクセスすればよいか知ることができます
(複数のデータベースもサポートしています -
設定の節を参照してください)。
上のクラス定義には、あなたは全ての共通のメソッドを無視し、
データ特有のコードを定義する必要があるだけである、
という事を示しています。また、
複数行のデータを処理するメソッドも併せて 1 つ示されています。
後半は、どの様にクエリを発行して単一行の結果を取得するか
が示されています。
$person->get()
はデータベースに接続し、クエリを実行し、結果をフェッチし、
クエリから返されたデータをオブジェクト変数に割り当てます。
SELECT * FROM person WHERE id=12;
SELECT * FROM person WHERE friend=12;
データベースの値を変更するには、オブジェクト変数の値を変更しコールするだけ
<?php
$person = DB_DataObject::factory('person');
$person->get(12);
$person->name = 'Fred';
$person->update();
?>
大まかには、メソッド名は関連する SQL ステートメントと同じです。