Zend Framework - Zend_Db編

HOME | TOP

■ Zend_Dbの概要

Zend_Dbコンポーネントは、データベース処理を担当するクラスになります。

データベース処理は、『接続』して『データの出し入れ』を行う処理になります。


『接続』するためにはアダプタクラスのオブジェクトを作成します。
アダプタオブジェクトが作成できれば、基本的なデータベース処理(データ検索・更新)はこれ一つでできるようになります。


『データの出し入れ』は、テーブルクラスを作成して行います。
テーブルクラスはZend_Db_Table_Abstractクラスを継承して作成します。
あとは足りないテーブル固有の情報(テーブル名・主キーなど)を設定していくだけです。

クラス継承した時点で既にデータの出し入れを行うメソッドも継承されていてそのまま使うことができます。
もしテーブル独自の処理が必要なら自作のメソッドを追加していくこともできます。


ここまでで少し矛盾なのは、『接続』を行うアダプタオブジェクトにデータベースの検索・更新機能があって、データ出し入れ用に作成した『テーブルクラス』にもデータを扱えるメソッドが始めから用意されているということです。

が、ご安心ください。まずアダプタオブジェクトを作成(接続)したら、テーブルクラスでこれを使ってデータベースとやり取りできるように登録します。

そしてテーブルクラスのオブジェクトを作成した後、データの出し入れを行うメソッドを使うわけですが、この継承したメソッド(findやinsertなど)の内部では最終的にアダプタオブジェクトの備えるデータベース処理用のメソッドに渡して処理させるからくりになっています。

つまり実質データベース処理してるのはアダプタクラスということになります。
アダプタクラスこそがデータベース処理の総元締めだと言えるでしょう。



■ アダプタクラスで接続

▽ いつ接続する?

アクセスがある度に毎回接続するのはその後の処理を考えると便利ではありますが、アプリケーションの観点から考えると、データベースを利用するアクション(処理)もあれば利用しないアクションもあるので、リクエストを受けた段階で一様に接続させるよりも実際の処理振り分け後(アクション)に必要な時にだけ接続させた方が効率が良さそうに思えます。


ただ、アダプタオブジェクトを作成したら明示的にgetConnection()メソッドを呼ばないと、最初にクエリを実行する時に自動的にデータベースへの接続が行われる仕組みになっています。

このことを利用して、最初に一様にアダプタオブジェクトを作成しておいてもクエリ実行さえしなければデータベースを利用しないアクションでもそれほど負荷はかからないように思えます。

ここでの結論として、最初に共通でアダプタオブジェクトの作成・登録までを行い、明示的getConnection()メソッドでの接続は行わないことにします。



▽ 接続情報ファイル [application/configs/db_info.ini]

▽ データベース接続寸前までを記述したスクリプト [application/config.php]

どの階層のスクリプトから呼ばれても正しい設定ファイルパスになるようにdirname(__FILE__)という定番の書き方をします。

ここまでで止めておけば、あとは実際にクエリを実行した時に初めてデータベースへの接続がされることになります。



では作成したconfig.phpを呼び出してデータベースとのやり取りがうまくいくか試してみましょう。

▽ コントローラスクリプト [htdocs/index.php]

とりあえずコントローラスクリプトとして、試しに表示させてみました。

このコントローラスクリプトは、いずれMVCのフロントコントローラとなることを見越したスクリプトになります。



つまりフロントコントローラになると下記のような書き方をしてconfig.phpを呼び出すことになります。

▽ フロントコントローラ [htdocs/index.php]

フロントコントローラから呼び出す初期化・設定ファイルは極力1つで済ませたい。(願望)
フロントコントローラに記述するものとして考えられるのは「データベース接続」や「自作プラグイン」や「アクション側に渡す設定値」「独自のルーティング指定」などが考えられますが、プラグインクラスをわざわざ自作するほどのことはないと思うので設定関連は大して膨れ上がらないと予測できます。
つまりconfig.php一つで十分だろう。。

フロントコントローラであるindex.phpにはできる限りこの記述以外はごちゃごちゃ書かない方向でいきたいです。(切望)



ここまできたらいっそのこと接続情報ファイルを分けないで書いた方がよりシンプルかも知れません。

[application/config.php] その2

うう〜ん、どーでしょー



これまで書いてきたconfig.phpやdb_info.iniと似たような構成でZend_ApplicationやZend_Tool関連で使われるファイル

application/Bootstrap.php
application/configs/application.ini


があるのでそちらに合わせた方がいいのか迷うところですが、余計な設定や記述が増えそうなので避けました。
よりシンプルで頑丈なアプリケーションを考えれば当然のことですね。


■ テーブルクラスの作成

アプリケーションで使うデータベースに存在するテーブルはすべてクラス化します。(1つのテーブルごとに1つのテーブルクラスを作成)

Zend_Db_Table_Abstractクラスを継承し、テーブル固有の情報を記述することでテーブルクラスを作成することができます。
作成したテーブルクラスは、オブジェクト作成(new演算子を使う)した時点で既にそのテーブルを操作する機能(メソッド)を持ち合わせています。
そのテーブルに独自の処理を追加したい場合は自作メソッドを追加することができます。



▼ データベース作成

先にデータベースが出来ていないとテーブルクラス作成に進めないので、適当に作ります。

テーブル作成



▼ テーブルクラス作成

まずは外部キーを持たないテーブルDouguからクラス化します。

[application/models/Dougu.php]

Douguテーブルは、物(あるいは人)の名前が一覧であってそれにユニークな通し番号を割り当てたいわゆるマスタですね。基本的に書き換えのない名簿のようなものです。マスタ系は外部から参照されることが多くなると思います。

テーブル名とクラス名を共通にすると、$_nameでの指定は省略可になります。

テーブルクラス作成がたった3行のテーブル固有情報の設定で済んでしまいました。



参照先と参照元が紛らわしいですね。
参照してくる側(参照元)とされる側(参照先)

[参照元] -----> [参照先]

だいたいマスタ系は参照される側(参照先)という感じですかね。





今度は外部キーを持つテーブルMonoをクラス化します。

[application/models/Mono.php]

$_schemaと$_primaryの書き方までは外部キーなしと同じです。

参照している外部キーに対して、名前を付けて(ここでは"ref_dougu")そこに自身のカラム名と参照先テーブル名と参照先カラム名を連想配列で指定しています。

と、ここまででテーブルクラス作成は終わりです。
覚えてしまえば工程としてはそれほどでもないのがテーブルクラス作成といった感じです。





▼ 動きを確認してみる

これまで書いてきたデータベース接続用の『db_info.ini』『config.php』、フロントコントローラ『index.php』、テーブルクラス『Dougu.php』『Mono.php』に加えてアクションコントローラおよびビュースクリプトを用意して、データベースを使ったMVCアプリケーションとしての全体の動きを見ていきます。



▽ ディレクトリ構造
 application/       (ウェブ非公開)
     +- config.php ..フロントコントローラから呼び出される設定関連
     |   +- configs/
     |       +- db_info.ini ..データベース接続情報
     +- models/        M(モデル関連置き場)
     |   +- Dougu.php
     |   +- Mono.php
     +- views/         V(ビュー関連置き場)
     |   +- scripts/
     |       +- index/
     |           +- index.phtml ..ビュースクリプト
     +- controllers/   C(コントローラ関連置き場)
         +- IndexController.php ..アクションコントローラ

 htdocs/            (ウェブ公開)
     +- index.php ..フロントコントローラ
     +- .htaccess ..リライト設定




▽ アクションコントローラ [application/controllers/IndexController.php]

▽ ビュースクリプト [application/views/scripts/index/index.phtml]

▽ 表示結果
[やくそう] 8ゴールド x 5個 = 40ゴールド
[どくけしそう] 10ゴールド x 3個 = 30ゴールド
[キメラのつばさ] 25ゴールド x 1個 = 25ゴールド
トータル=95ゴールド


データベースに接続でき、参照元テーブルオブジェクトから参照先データの引き出しがうまく出来たので、作成したテーブルクラスは連動できている(リレーショナル)ことになります。

今回は参照元テーブルオブジェクトを作成し、findParentRow()メソッドを使って参照先を検索しましたが、逆に参照先テーブルオブジェクトを作成して参照元を検索するにはfindDependentRowset()メソッドを使うことになります。

次はテーブルオブジェクトから使えるいろいろなメソッドを書いていきます。



▼ テーブルオブジェクトから使えるメソッド

テーブルオブジェクトを作成し、

$table = new テーブルクラス();

どのようなメソッドが使えるか書いていきます。



主キーで検索
$rows = $table->find(1); // 主キーが1の行検索
$rows = $table->find(array(1, 3, 5)); // 主キーが1,3,5の行検索
$rows = $table->find(1, 'foo'); // 複合キーで検索
$rows = $table->find(array(1, 3),array('foo','bar')); // 複数の行を複合キー検索


全て検索
$rows = $table->fetchAll();


絞り込んで検索
$rows = $table->fetchAll($table->select()->where('dougu_name = ?', $name));

where句を指定する場合にwhere('dougu_id = 1')のようにSQLクォートが不要なカラムもあれば必要なカラムもある。



更に絞り込んで検索
$order = 'dougu_id';
$count = 10;
$offset = 20;

$select = $table->select()->where('dougu_name = ?', $name)
                             ->order($order)
                             ->limit($count, $offset);

$rows = $table->fetchAll($select);


find()やfetchAll()による検索結果はRowオブジェクトの配列(Rowset)で返されるので配列を扱うforeachループを使って値を見ていくことになります。

$rows = $table->find()または$table->fetchAll()による検索;

foreach ($rows as $row) {
    // $rowを参照している'Foo'テーブルのレコードを取得(参照元検索)
    $child_foo = $row->findDependentRowset('Foo');
    // 参照先検索
    $parent_bar = $row->findParentRow('Bar', 'ref_bar');
}


insert
$data = array(
    'dougu_id'   => 6,
    'dougu_name' => 'しあわせのくつ',
    'dougu_gold' => 800000
);

$table->insert($data);


update
$data = array(
    'dougu_name' => '新しあわせのくつ',
    'dougu_gold' => 1800000
);

$where = 'dougu_id = 6';

$table->update($data, $where);


delete
$where = $table->getAdapter()->quoteInto('dougu_name = ?', $name);

$table->delete($where);

SQL式中の値や識別子はクォートされないのでquoteInto()を使う



トランザクション
$adapter = $table->getAdapter(); // アダプタオブジェクト取得

$adapter->beginTransaction();    // トランザクション開始
try {
    $data = array('dougu_id' => 6, 'dougu_name' => 'fuga', 'dougu_gold' => 3600);
    $table->insert($data);
    $table->delete('dougu_id = 5');
    $adapter->commit();   // 処理を確定させる
} catch(Exception $e) {      // 例外が発生したら
    $adapter->rollback(); // 処理を巻き戻す
}


テーブルオブジェクトのgetAdapter()メソッドでアダプタオブジェクトも取得できるので次はプロファイラオブジェクトを使って実行にかかった所要時間を取得してみましょう。

$adapter = $table->getAdapter(); // アダプタオブジェクト取得
$profiler = $adapter->getProfiler(); // プロファイラオブジェクト取得
$profiler->setEnabled(true);         // プロファイラを有効に
$profiler->clear();                  // 初期化

:::::::: ここでいろいろなDB処理 :::::::::

// 実行したSQL文を表示
echo $profiler->getLastQueryProfile()->getQuery(),"<br />\n";
// クエリ実行にかかった所要時間を表示
echo $profiler->getLastQueryProfile()->getElapsedSecs(),"<br />\n";

プロファイラを使うとしたら開発中の確認程度になると思うので、ホントはアクション内に直書きせずに、広域な設定で表示を切り替えられるように書くべきでしょうね。




2010(C)Mingw