Zend_Db_Tableをもっと手軽に使う(概要編) #ZFhimekuri

「[Zend Framework 日めくり Calendar 2012](http://atnd.org/events/23579)」の3日目の記事です。何となくQiitaで書いてみます。

[Zend Framework](http://framework.zend.com/) でデータベースを扱う時の定番モジュールがZend\_Dbです。[公式のQuickStart](http://framework.zend.com/manual/ja/learning.quickstart.create-model.html)がわかりやすいんですが、TableDataGatewayをDataMapperから使うというガチガチ厳密な使い方をしているので、「難しそう」「面倒くさそう」という印象を受けるかもしれません。

もうちょっとお手軽に、**不真面目に**使う書き方を考えてみます。解説を長々と書くとダレそうなので2日間に分割します。今日は概要だけ。

# 用語の整理
`Zend_Db`について少し解説。主要なクラスはこの３つです。

* `Zend_Db` … DAL（[PDO](http://jp2.php.net/manual/ja/book.pdo.php)のWrapper）
* `Zend_Db_Table` … O/R Mapper(Table Data Gateway)
* `Zend_Db_Table_Row` … O/R Mapper(Row Data Gateway)

`Zend_Db`は言ってしまえばPDOのWrapperで、「どんなデータベースでも同じインターフェースで操作できるよ」というだけの代物。素のPDOより対応データベースが少し多くなっています。インターフェースの統一が主目的であり、極薄でオーバーヘッドもあまり無いので、ZFではとりあえず使っておくといいです。単体で使うことも可能です。

`Zend_Db_Table`と`Zend_Db_Table_Row`がZFにおけるO/R Mapperの部分です。O/R Mapperと聞くとActiveRecordを思い浮かべる人が多いと思いますが、ZFの場合はTable Data Gateway / Row Data Gatewayというデザインパターンを採用しています。
参考： http://otndnld.oracle.co.jp/columns/arai-semi/data_access/2/

ActiveRecordより抽象度が一段低く、「テーブルを１つのクラスとみなす」「テーブル単位で操作を行う」「ドメインロジックを持たない」などの特徴を持ちます。ちょっと長くなりそうなのでこの辺の特徴は追々書きます。

**テーブル単位の操作が基本**になるので、JOINは基本的に行ってくれません。JOINを多用する場合は向いていませんので、Zend\_Dbを直接使ったほうがいいです。しかし**テーブル単位の操作に限るのであれば、超便利**なコンポーネントです。

# サンプルソース

githubに置きました。昔作ったパフォーマンス計測用の適当なアプリにブランチを切ってあります。
https://github.com/hirak/performance/tree/DbTable_test

僕が考える、Zend\_Dbをカジュアルに使うポイントは４つ。

* DBの初期化はapplication.iniに書く
* スキーマは`Zend_Db_Table_Definition`を利用してConfigファイルにする。テーブルのクラス定義なんて省略しちゃえ
* テーブル取得のコードをグローバル関数化して短くする
* `Zend_Db_Table_Row_Abstract`を拡張してActiveRecordにしてしまう。これで拡張ポイントができるので、DataMapperは省略してしまえ


# Zend\_Db\_Tableの書き方のサンプル

もろもろ準備が出来上がった場合、Controllerなどからの呼び出しコードはこんな感じになります。`Zend_Db_Table_Row`の拡張がベースになるので、Rowクラスを生成して操作を行います。

```php
<?php
//常にZend_Db_Tableを作成するところから開始します。
$posts = AR('posts');

//テーブルクラスにデータを検索してもらったり
$latestPost = $posts->find(1)->current();

//テーブルクラスに新しいRowデータの雛形を作ってもらったり
$newpost = $posts->createRow();
$newpost->title = '新しいポスト';
$newpost->text = '新しい記事だよー';
$newpost->save();

```

ActiveRecordだと`Posts::find(1)`みたいにstaticメソッドで検索やinsertを済ませるものがありますが、ZFは操作のために必ず`Zend_Db_Table`のインスタンスが必要です。

## 検索（SELECT）

主キーでの検索には`Zend_Db_Table::find()`を使います。

```php
<?php
//主キーでの検索
list($post) = AR('posts')->find(1);
//もしくは
$post = AR('posts')->find(1)->current();

var_dump($post->toArray());

//$postはそのまま配列風アクセスやオブジェクトアクセスが可能
echo $post->name, "\t", $post['text'], "\n";
```

キーは複数渡せる仕様なので、戻り値が常に`Zend_Db_Table_Rowset`になり、配列風になっています。
そのため、先頭の一つしか必要ない場合はlist()を使うか->current()で絞り込む必要があります。個人的にはlistで書くのが好きです。

もうちょっと複雑な検索の場合は`Zend_Db_Table::fetch()`や`Zend_Db_Table::fetchRow()`を使います。こちらはwhereやorderが使えます。

```php
<?php
//複雑な検索
// ORDER BY created DESC LIMIT 1 で最新のPOSTを取得する
$latestPost = $posts->fetchRow(null, 'created DESC');
```

## 更新（UPDATE）


テーブルクラスからfindしたりcreateRowしたRowオブジェクトは、「生きています」。編集してsave()すると、そのままDBに保存することが可能です。この辺がRow Data Gatewayの特徴ですね。

```php
<?php
list($post) = $posts->find(1);
$post->title = '修正したよ';
$post->text = '修正した記事だよー';
$post->save();
```


## 新規作成（INSERT）

テーブルクラスからcreateRowすると何も入っていない空っぽのデータが作成されます。
こいつにデータを突っ込んでsave()するとINSERT文の効果があります。

```php
<?php
$newPost = $posts->createRow();
$newPost->name = '新しい記事だよー';
$newPost->text = '新しい記事だよー';
$newPost->save();
```


## 削除（DELETE）

更新と同じく、Rowを取得してからdeleteすることができます。

```php
<?php
list($post) = $posts->find(1);
$post->delete();
```

# 後半・解説編に続く
http://qiita.com/items/1598