Home

日記
Zend Frameworkをどう使うか その8Edit

さっきのテンプレート展開の例で<?=h($foo); ?>みたいな記述を実現するために、

function showTemplate($_templateFile, $_templateVars)
{
extract($_templateVars);
include $_templateFile;
}

みたいな関数を用意していた。同じようなことがZend_Viewでもできれば、

<?php echo $this->escape($this->foo); ?>

は、

<?php echo $this->escape($foo); ?>

と書けるようになる。$this->の連続がなくなってずいぶん見通しが良くなった気がする。この程度ならば、Zend_View_Abstractを継承した、

class MyView extends Zend_View_Abstract
{
protected function _run()
{
extract($this->_vars);
include func_get_arg(0);
}
}

とか作れば簡単に実現できるじゃんと思ったんだけど、ダメだった。というのは、$this->_varsってZend_View_Abstractでprivate宣言されているのね。せめてprotectedにならんかなー。あるいはZend_View_Abstractに、

public function getAllVars()
{
return $this->_vars;
}

なんてメソッドを追加してもらえないだろうか。っつーかなんでDmytro Shteflyuk’s Home &#187; Zend Framework: Using Smarty as template engineのコメント欄で紹介されていたSmarty用のViewがわざわざassignをoverrideしていたのか、ようやくわかったよ。

この仕様ってどうなんだろうなー。継承されたクラスからも、テンプレート変数一覧には直接触らせないようにしなければならないだろうか? 単に「protectedにする理由がないからprivate」にしただけならば、protectedにして欲しいなー。そうすれば、もっとまっとうなSmarty用のViewを書くこともできそうだし。

まあ最悪、Zend_View_Abstractの$this->_varsおよびその関連メソッドを丸ごとMyViewで上書きしちゃえば、俺のやりたいことはできるようになるだろうけど、できればそういうアプローチ自体を公式に意識した作りになっていて欲しい。という話はZend FrameworkのMLに投げなきゃだめなのか。英語で議論するのめんどいなー。

MLの敷居の高さは

英語云々と言うよりは、ML投稿者の誰が誰やらさっぱり分からないことだな。Zend Frameworkのソースには開発者の名前が書かれていないし、MLに発言している人も特に名乗ったりしていない。また、関係者だと分かるような署名をつけている人も見あたらない。

この状態だと、どの発言がどういう立場でのものか分からないんで、どういうアプローチで発言していいのかさっぱり分からない。たとえば、Re: [fw-general] Smarty Plugin for viewで書かれているZend_Viewをもっと抽象化して他のテンプレートエンジンに対応できるようにしよう、なんて案には賛同する*1わけだけど、人間関係的な文脈が読めないんで、どうアプローチしていいのかさっぱりわからない。ひとまず各投稿者の名前とかでググって、どういう人がどういう立場でどういう発言をしているのか把握するところからはじめないとダメなのかな。

*1 けど、俺の場合ははそこまできれいなアプローチにしなくても、単に$_varsをprotectedしてくれるだけで十分なんだけどね

Published At2006-03-22 00:00Updated At2006-03-22 00:00

日記
ソニー損保の第一印象わろしEdit

コンビニ払いで申し込んだのに、支払期限までに振り込まなければ申し込みは無効になるとか注意書が出ていたのに、支払期限の翌日が前の保険が切れる日なんで間に合わないと保険の空白期間ができてしまうのに、コンビニ払いの振り込み用紙が支払期限の翌日に届きやがったよ。

支払期限の前日になっても支払用紙が届かないんで、電話してクレジットカードで払い込んだから間に合ったけど、なかなか出だしからやりやがるな。休日祝日が絡む日程でぎりぎりに申し込んだっつー状況も原因の一つだろうけど、保険とか取り扱ってるところならばその辺ちゃんと管理してくれや。

Published At2006-03-23 00:00Updated At2006-03-23 00:00

日記
Zend_View_Smartyを作ってみたEdit

Zend_ViewをSmarty対応するためのZend_View_Smartyを作ってみた。

ただし、「Zend Frameworkをどう使うか その8」でぐだぐだ書いたとおり、現在(Zend Framework 0.1.2)のZend_View_Abstractの実装では、Viewの値を保持する変数である$_varsに、継承したクラスからアクセスできない。

そこで、Zend_View_Abstractを以下のように書き換えて使っている。

-    private $_vars = array();
+    protected $_vars = array();

Zend_View_Smartyを使うと、呼び出し側のコードは、

$config = array(
'scriptPath' => '/path/to/views', // for Zend_View
'compile_dir' => '/path/to/templates_c', // for Smarty
);
$view = new Zend_View_Smarty($config);
$view->foo = 'FOO';
$view->now = time();
echo $view->render('template.tpl');

というように、通常のZend_Viewを使った場合と同様に書ける。一方呼び出されるテンプレートファイルtemplate.tplは、

<p>foo is {$foo|escape}.</p>
<p>now is {$now|date_format:'%D %T'}

なんてSmarty記法を使って書ける。

Smartyのキャッシュ機構を使いたい場合は、以下のようになる。

$config = array(
'scriptPath' => '/path/to/views', // for Zend_View
'compile_dir' => '/path/to/templates_c', // for Smarty
'cache_dir' => '/path/to/cache', // for Smarty
);
$view = new Zend_View_Smarty($config);
$view->getSmarty()->caching = true;
if (!$view->isCached('template.tpl')) {
$view->foo = 'FOO';
$view->now = time();
}
echo $view->render('template.tpl');

キャッシュ制御にcache_idやcompile_idを使いたい場合は、isCachedやrenderの引数として渡すのではなく、$view->setCacheId('[CACHE_ID]')や$view->setCompileId('[COMPILE_ID]')などのセッタメソッドを使って、あらかじめViewオブジェクトにセットする。あと上記サンプルコードにもあるけれども、$view->getSmarty()でViewの内部にもつSmartyオブジェクトにアクセスできるんで、Smartyに関する細かい制御を行いたい場合は、それ経由で行えばいい。

また、SmartyからZend_Viewの機能にアクセスするためのSmarty用のプラグインも作ってみた。helperプラグインは、

{helper name="[HELPER NAME]" params....}

という記法で、Zend_Viewのhelper機能を呼び出すことができる。また、renderプラグインは、

{render file="[TEMPLATE_FILE]"}

という記法で、Zend_Viewのrenderメソッドを呼び出すことができる。

Smartyにも標準でincludeのような外部テンプレートを呼び出す機能は用意されているが、SmartyのincludeだとSmartyオブジェクトのtemplate_dirをベースにテンプレートファイルが検索されることになる。一方Zend_View::renderではaddScriptPathなどによる複数ディレクトリからのテンプレートファイル検索が行われるので、テンプレートファイルのパスの問題を一元化するために、renderプラグインを経由した方がいい。

といった感じで、Zend_Viewのインターフェースや機能を一通り使いつつも、Smartyの機能も一通り使えるようなViewクラスを作ってみたわけだけれども、実際問題Zend Framework 0.1.2の実装ではこういう書き方はできないわけで、できればその辺を何とかしてもらいたい。もうちょっといろいろ試してみてから、MLにでもその辺の話を投げてみよう。誰か代わりに投げてもらってもいいです。英語で書くのめんどい。

MLに投げた

いい反応があるとうれしいんだけど。

Published At2006-03-23 00:00Updated At2006-03-23 00:00

日記
Zend Frameworkをどう使うか その9だっけ?Edit

っつーかZend FrameworkのControllerとView以外の構成要素は、単に今時っぽさ+重要度の高さ順にピュアPHP5で書きおろしている、PEARライブラリの焼き直しみたいなもんだよなー。

PEARがPHP4の負の遺産を継承してしまってPHP5フル対応が進まないんで、もうPEARをPHP5用としても継続させるのはあきらめて、PEAR相当のライブラリのピュアPHP5版をこっちで再構築するつもりなのじゃなかろうか。ただ、そういっちゃうとPEAR関係者ともめそうだから、「こっちはライブラリ集ではなくて、フレームワークなんです。別物です」と言い張るために、Zend Frameworkという名前を付けたとか(邪推)。

まあそれはそれでいいんだけど、PEARよりもZend Frameworkの方がライセンスとかの扱いが怪しいのが気になるなー。各ソースのヘッダに書かれているライセンスのURLはNot Foundだし。まあ配布アーカイブにはLICENSE.txtが入っているけど。

ところでこのZend Framework Licenseって、Zend Framework全体ではなく、構成するライブラリ単体にも適用されるんだよね。Zend_View_Smartyみたいなコードは、Zend_View_Abstractを継承しているからZend Framework License 1.0のソースに依存しているわけだけど、Zend_View_Abstract自体を再配布しているわけじゃないから、Zend Framework Licenseの影響は受けないよね。Zend Framework Licenseなコード自体を改変したりして再配布する際には、Zend Framework Licenseを維持する必要があるんだろうけど。

とかいうライセンス話はおいておいて、一応ControllerとView以外の構成要素も一通り見ていくか。

Published At2006-03-23 00:00Updated At2006-03-23 00:00

日記
Zend Frameworkをどう使うか その10だと思うEdit

さて、Zend_Db周りを見てみよう。「確かO/Rマッパーとして、Zend_Db_DataObjectが作られると発表されていたはずだけど、0.1.2にはそんなの入ってないなー。まだできてないのかなー」と思いつつマニュアルを読んだら、Zend_Db_Table、Zend_Db_Table_Row、Zend_Db_Table_RowsetあたりがO/Rマッパーな機能を持っているのね。名前が変わったのか。

で、Zend_DbのコアであるZend_Db_Adapterが、DB操作全般の基本機能をすべて持っている、PEAR DB相当のもの。PEAR DBと比べるとメソッド名とかずいぶんきれいに整理されつつ、便利そうな類似メソッドもいろいろ増えていて、非常に良さそう(ちなみに俺はMDBとかMDB2とかは使ってないんで、そっちとは比べられない)。

と思いつつよく見たら、Zend_Db_Adapterの設計(インターフェース)って、PDOの機能を元にしているのね。PDOが使える場合はZend_Db_AdapterはPDOのラッパー的に動作し、PDO以外のDBドライバを使う場合は、Zend_Db_Adapter側(の各ドライバエンジン。Zend_Db_Adapter_*とかZend_Db_Statement_*とか)でPDO相当の機能を実装して互換性を保とうってアプローチか。というわけで、Zend_Db周りを理解するためには、PDOに関する基礎知識が必要そう。まあでも面倒くさいからそっちのお勉強は省略。多分Zend_Db_Adapterの機能を関数ベースで実装したものなんだろう。

ところでZend_DB::factory(≒PDO)って、PEAR DBな頃のようなDSN文字列は受け取ってもらえず、配列形式のDBコネクション設定を渡さなきゃいけないみたいだね。たとえばマニュアルには、

$params = array ('host'     => '127.0.0.1',
'username' => 'malory',
'password' => '******',
'dbname'   => 'camelot');
$db = Zend_Db::factory('pdo_mysql', $params);

なんて例が書かれているけど、これって、

$dsn = 'pdo_mysql://malory:****@127.0.0.1/camelot';
$db = Zend_DB::factory($dsn);

を受けつけてくれても良さそうな気がするんだけど、なんでそうなってないんだろう? 従来のURI形式の文字列だと表現しきれないような細かい設定に対応するためかなー。でも過去(PEAR)互換性はキープしてくれてもいいと思うんだけどなー。DBコネクション設定なんて設定ファイルに一行で書いておいてDBライブラリにそれを渡すだけ、って感じにしたいのに。

そういえばZend_Db_Adapter(≒PDO)では、エスケープ関連の機能が充実していて、単純エスケープメソッドだけでなく、

$where = $db->quoteInto('foo = ? and bar = ?', 1, 'BAR'); // foo = 1 and bar = 'BAR'

みたいにprepareもどきな書き方ができる。あと、

$result = $db->query(
'select * from tbl where foo = :foo and bar = :bar',
array('foo' => 1, 'bar' => 'BAR')
); // select * from tbl where foo = 1 and bar = 'BAR'

みたいにprepareを使うこともできる模様。この辺の記法が充実していると、SQL文を書くときにいろいろきれいに書けるようになって便利。O/Rマッパーを使ったところで、いざとなったらSQL文(whereだけとかでも)は書かなきゃいけなくなるわけだし、そういう時の利便性を考えていろいろ用意しているっぽい。

って書いているうちにずいぶん長くなってきたからいったん休憩。

Published At2006-03-24 00:00Updated At2006-03-24 00:00

日記
Zend Frameworkをどう使うか その11Edit

Zend_Dbの続き。

Zend_Db_Adapterは、エスケープが書きやすくなったところがPEAR DBと比べての一番大きな違いかなー。メソッド名がわかりやすくなって、PEAR DBでは

$db->autoCommit(false);

だったのが、

$db->beginTransaction();

とふつうに書けるようになってたりするのも、可読性が上がっていいと思うけど。

ちなみにqueryメソッドで直接SQL文を渡す方法以外に、insertやupdateメソッドでは、

$table = 'tbl';
$row = array('foo' => 1, 'bar' => 'BAR');
$db->insert($table, $row); // insert into tbl(foo, bar) values(1, 'BAR');

みたいな書き方ができるみたいだけど、この書き方はうれしいかどうかちょっと微妙。PHPコードだけを見たら、それなりの利点(コードの見やすさ)は感じるんだけど、このPHPコードがDBに対してどういう操作をするのか、いまいちわかりにくい。

O/Rマッパーくらい突き抜けちゃえば、その利点の大きさは認めるけど、こういう中途半端な形でSQL文の隠蔽を行うくらいならば、ふつうにplaceholderとかを使ったSQL文へのパラメータ埋め込みを書いた方が、トータルでの可読性は高いんじゃなかろうか。

っつーかまあ、どうせZend_Dbには標準でO/Rマッパーもあるから、この中途半端な記法は、本来はO/Rマッパー機能の中の人(メソッド)が使うために用意されているんだろうけどね。

ちなみにZend_Db_Adapter周りの機能は、Zend_Db_Adapter+Zend_Db_Statement(PDOを利用する場合は、PDOStatementがそのまま使われる)の組み合わせで実現されているんで、Zend_Db_Adapter周りを一通り知りたければ、本当はZend_Db_Statementの方も追う必要がある。PDOがPDOクラスとPDOStatementクラスの二つから構成されているのと、相似関係にあるわけだね(ってことで、前に書いた多分Zend_Db_Adapterの機能を関数ベースで実装したものなんだろうって予想は間違いだった。PDOは関数ベースではなく、PHPの組み込みクラスとして実装されていた)。まあでもこの辺は深追いしても大して面白くなさそうだから追わない。

Published At2006-03-24 00:00Updated At2006-03-24 00:00

日記
オランダ語?Edit

Zend FrameworkのMLにZend_View_Smartyの件でメールを投げてみたら、すぐにレスポンスメールが返ってきたんだけど、なにやら読めない言語で書いてある。.nlドメインってオランダ? ドイツ語とちょっと似ているけど、やたらと母音を連続する単語が多いな。文中に含まれる日付らしき要素(これは英語と似ているからだいたいわかる)から察するに、多分自動応答の不在通知メールだろう。まあMLでは良くあることだけど(文字化けではなく)読めない言語で返ってくるとなかなか焦る。

Published At2006-03-24 00:00Updated At2006-03-24 00:00

日記
Zend Frameworkをどう使うか その12Edit

もちろんこんなことになるなら、連番ではなくちゃんとしたセクションタイトルを付けるべきだったと後悔しているわけですよ。またZend_Dbネタの続きで、今度はselectクエリー生成ユーティリティクラスであるZend_Db_Selectクラスについて。

第一印象としては、Zend_Db_Selectに関しても、Zend_Db_Adapter::insertとかZend_Db_Adapter::updateとかと同様に、微妙に中途半端な感じがする。ちなみにZend_Db_Selectクラスの具体的な使い方としては、

// $db - Zend_Db_Adapterオブジェクト
$select = $db->select();
$select->from('tbl', '*'); // select * from tbl
$select->where('foo = ?', 1); // where foo = 1
$select->order('bar'); // order by bar
$select->limit(10, 5); // MySQLなら limit 10, 5
$sql = $select->__toString(); // select * from tbl where foo = 1 order by bar limit 10,5

って感じ。ちなみに上記サンプルには使ってないけど、他にもgroupとかhavingとかSQL文法要素のパターン一通り*1のメソッドがあって、メソッド名から想像したとおりに動く。

なんかもう「うわー」って感じがしない? こういうユーティリティクラスはそれなりに便利だとは分かる(昔自分でも作ったよ)けど、O/Rマッパー全盛の時代にこれをメインに使うのはちょっとビミョーな気がする。

ただ、こういうクラスが作られた理由は何となく想像できる。

というのは、後で触れるZend_Db_TableによるO/Rマッパー機能には、PEAR DB_DataObjectには存在したデータオブジェクト同士のjoin機能がなくなっている。DB_DataObjectでは、[dbname].links.iniにリンク情報を記述しておけば、

$foo = new FooTable(); // DB_DataObjectによるfooテーブルオブジェクト
$bar = new BarTable(); // DB_DataObjectによるbarテーブルオブジェクト
$foo->joinAdd($bar); // links.iniの設定に基づいて二つのテーブルをjoinする
$foo->find(); // select文を発行

なんて感じで、joinを使ったselect文をデータオブジェクト操作のレベルで書けたけど、Zend_Db_Table(_Row)からはそういう機能はすっぱり削除された。実際にはもちろんjoinを使ったselectなんて山のように使うわけで「O/Rマッパーのレベルでのjoin機能を削った代わりに、joinを伴う複雑なselectの管理はこっちでやってね」というんで作られたのが、Zend_Db_Selectなんじゃなかろうか。

まあ確かに、DB_DataObjectのjoin機能は結構微妙なできではあったし*2、その完成度を高めるってのはかなり難しい*3ことは分かるけど、こうなっちゃうと使い勝手としてはDB_DataObjectから後退している気がするんだよなー。単体ではきれいな仕様&実装ではあると思うけど。

ちなみにZend_Db_Selectを使ったjoinのやり方は、こんな感じになる。

$select = $db->select();
$select->from('foo', '*'); // select foo.* from foo
$select->join('bar', 'foo.id = bar.id', '*'); // select bar.* ... join bar on foo.id = bar.id
$sql = $select->__toString(); // select foo.*, bar.* from foo join bar on foo.id = bar.id

うーん、俺なら素直にSQL文+placeholderで書いちゃう気がするなー。Zend_Db_Selectを使っても、結局O/RマッパーであるZend_Db_Tableとは異なるオブジェクトに対する操作になってしまうし、だったらO/Rマッパーとは異なる形でSQL文を隠蔽するZend_Db_Selectを使うよりは、まだSQL文を直接書いた方がわかりやすい気がする。Zend_Db_Selectを使う場合は、生成されるSQL文を完全に頭の中にイメージしていないと使えないわけだし。

あ、一点Zend_Db_Selectを使う確実な利点があったな。limit関係のSQLはDBMSによっていろいろ異なるんだけど、それをZend_Db_Selectを使うことで吸収してくれる模様。それが重要ならば使う価値があるかもね。

*1 +追加条件の場合はorWhereとかorHavingとかの論理演算指定含む

*2 実用レベルでは、内部的に生成されるSQL文を意識しながら使う必要があったり

*3 DBスキーマ側まで規約で縛ったりすれば何とかなるかもしれないけど

Published At2006-03-24 00:00Updated At2006-03-24 00:00

日記
Zend Frameworkをどう使うか その13Edit

「ようやくO/Rマッパーにたどり着きましたよ」のZend_Db_Table。使い方は、

// $db - Zend_Db_Adapterオブジェクト
Zend_Db_Table::setDefaultAdapter($db);
class Foo extends Zend_Db_Table {} // fooテーブルに対応するクラスの宣言
$foo = new Foo(); // $fooテーブルに対応したオブジェクト

って感じ。実行環境(static protected Zend_Db_Table::$_defaultDb)にDB接続情報をセットしておくってやり方は、DB_DataObjectがPEARのstaticPropertyを使っていたやり方と一緒だね。

ただし、上記のようにオプション設定なしでテーブルクラスを宣言した場合は、

  • クラス名 = テーブル名
  • テーブルのプライマリーキーとなるカラム名 = id

となっている必要(規約)がある。その辺を変更したい場合は、

class Table_Foo extends Zend_Db_Table
{
protected $_name = 'foo'; // テーブル名
protected $_primary = 'id'; // プライマリーキーとなるカラム名
}

なんて感じでクラスを定義すればいい*1。実際に使うときにはどう書くんだろうなー。適当なprefixをつけてクラス定義ファイルを自動生成しつつ、必要に応じて内容を修正する感じになるのかなー。まだ具体的な利用イメージは湧かない。

Zend_Db_Tableオブジェクトに対してできる操作は、

  • getAdapter() - Zend_Db_Adapterオブジェクトが取得できるんで、細かい操作が必要な場合はこれ経由で処理。
  • info() - テーブル情報が取得できる。テーブル名、カラム名一覧、プライマリーキーカラム名の三つを連想配列で。
  • insert(array $data) - 連想配列でデータを渡してinsertを実行。やっぱり内部的にはZend_Db_Adapter::insertが呼ばれているね。
  • update(array $data, string $where) - 連想配列でデータを、SQL文でwhere条件を渡してupdateを実行。こっちも内部的にはZend_Db_Adapter::updateが呼ばれている。
  • delete(string $where) - SQL文でwhere条件を渡してdeleteを実行。
  • find(mixed $val) - primary keyに対する検索。$valにscalarを渡すと1行検索(戻り値はZend_Db_Table_Row)、配列を渡すとin検索(戻り値はZend_Db_Table_Rowset)される。
  • fetchAll($where, $order, $count, $offset) - ふつうにselect発効。引数はすべて省略可能。戻り値はZend_Db_Table_Rowset。
  • fetchRow($where, $order) - 1行select。複数行が選択されるような$whereの場合は最初の1行。ただし、Zend_Db_Table_Rowにはnextみたいなメソッドはないから、2行目以降に移動することはできない。
  • fetchNew() - 空のZend_Db_Table_Rowを返す。Zend_Db_Table::insertではなく、Zend_Db_Table_Row::saveを使ってinsertしたいときに使うんだろう。

なんて感じ。クラス構成としては、Zend_Db_Tableはテーブル全体に対する操作を行い、レコード単位の操作は、Zend_Db_Tableのfetch系メソッドで返される、Zend_Db_Table_Row(set)で行うというイメージね。

Zend_Db_Table_Row(set)の話は後回しにして、Zend_Db_Tableの拡張の話をもうちょっと続ける。

最初の方で、テーブル名やプライマリーキーの設定を変更するためのクラス定義の話を書いたけど、それ以外にもクラスを拡張する方法はある。つっても、単にふつうのクラスの拡張の仕方と一緒で、Zend_Db_Tableを継承したクラスで、メソッドを上書き(override)するなり追加するなりすれば、機能拡張できますよ、という話。

たとえば、

class Foo extends Zend_Db_Table
{
function insert($data)
{
if (empty($data['created']) {$data['created'] = time();}
parent::insert($data);
}
}

なんて感じで、insert時に生成時間を自動的に更新したり、

class Foo extends Zend_Db_Table
{
function findByName($name)
{
$db = $this->getAdapter();
$where = $db->quoteInto('name = ?', $name);
return $this->fetchAll($where);
}
}

なんて感じで独自の検索メソッドを追加したりって感じか。でもこの例なんて、DB_DataObjectだったら標準クラスを使って、

$foo = new FooTable(); // DB_DataObjectによるfooテーブルオブジェクト
$foo->name = $name;
$foo->find();

で済んだんだけどね。

*1 他の書き方もあるけど

Published At2006-03-24 00:00Updated At2006-03-24 00:00

日記
Zend Frameworkをどう使うか その14Edit

O/Rマッパー編その2。Zend_Db_Tableのfetch系メソッドで返されるZend_Db_Table_RowおよびZend_Db_Table_Rowsetについて。名前を見れば分かるとおり、Zend_Db_Table_Rowが1行レコードに相当するクラスで、Zend_Db_Table_Rowsetが複数行のレコードを管理するクラスね。

Zend_Db_Table_Rowsetは、単に内部にZend_Db_Table_Rowオブジェクトの配列を抱えているだけで、DBMSレベルでのカーソルとかは使っていない。「100万行返すZend_Db_Table::fetchAllを投げたら、100万個のZend_Db_Table_Rowオブジェクトを作ったるでぇ」という気合いの入った仕様になっている。

Zend_Db_Table_Rowsetのメソッドは、以下のような感じ。

  • current() - カレントのZend_Db_Table_Rowオブジェクトを返す。存在しない場合はfalseが返る。
  • next() - ポインタを次の行に移動し、新しいポインタを返す。
  • rewind() - ポインタを先頭に移動。
  • valid() - 有効なポインタを差しているか。
  • count() - レコード行数。
  • exists() - 有効なレコードを持つか(空のZend_Db_Table_Rowsetかどうか)
  • toArray() - 保持している全行のデータを、配列+連想配列形式(旧世代のPHPらしいデータ形式)に変換して返す。

ただし、ほとんどiteratorインターフェースを実装するためのメソッドね。ふつうのクラスライブラリ的に書けば、

$foo = new Foo(); // Zend_Db_Tableオブジェクト
$rows = $foo->fetchAll(); // 全行取得
while ($rows->valid()) {
$row = $rows->current();
// 各行に対する処理
$rows->next();
}

なんて感じになるけど、iteratorを使えば、

$foo = new Foo(); // Zend_Db_Tableオブジェクト
$rows = $foo->fetchAll(); // 全行取得
foreach ($rows as $row) {
// 各行に対する処理
}

なんて書き方になる。PHPだったらこっちの書き方の方がふつうかな。

そうやって取得した1レコード単位のZend_Db_Table_Rowのメソッドは、以下のような感じ。

  • save() - primary keyがセットされていればupdate、セットされていなければinsertを行う。
  • toArray() - データを連想配列として返す。
  • setFromArray(array $data) - 連想配列から各カラムの値をセットする

なんて感じ。もちろん各カラムの代入と参照は、上記メソッド以外にも、

$row->foo = 1;
$row->bar = 'BAR';
$row->save();

なんて感じで、__set、__getメソッドを経由したプロパティ表現でも書くことができる。

以上でZend FrameworkのO/Rマッパー周りの機能は一通り見ていったわけだけど、きれいな仕様ではあるけれども、かなり機能は少ないなーというのが、全体を通しての印象。

特にO/Rマッパーは1テーブル単位の操作でしか(検索でさえも)使えないって仕様は、結構微妙な気がする。というのはDB_DataObjectの、検索だけなら複数テーブルをまたいだ処理にもデータオブジェクト互換の操作が使える、という仕様に俺が慣れてしまっているからかもしれないけど。具体的には、

$stock = new StockTable(); // 在庫テーブル
$product = new ProductTable(); // 商品テーブル
$product->kind = 'book'; //種類は「本」
$stock->joinAdd($product); // 在庫テーブルと商品テーブルをjoin
$stock->find();
while ($stock->fetch()) {
if ($stock->amount == 0) {
// 在庫がない本の場合の処理
}
}

みたいな書き方がDB_DataObjectでは簡単にできたわけだけど、これをZend_Dbを使って書くと

$select = $db->select();
$select->from('stock', '*');
$select->join('product', 'stock.product = product.id', '*');
$select->where('product.kind = ?', 'book');
$sql = $select->__toString();
$rows = $db->fetchAll($sql);
foreach ($rows as $row) {
if ($row['amount'] == 0) {
// 在庫がない本の場合の処理
}
}

みたいにZend_Db_Adapter+配列による結果セットを使って書くか、あるいは、

$select = $db->select();
$select->from('product', 'id');
$select->where('kind = ?', 'book');
$sql = $select->__toString();
$rows = $db->fetchAll($sql);
$ids = array();
foreach ($rows as $row) {$ids[] = $row['id'];}
class Stock extends Zend_Db_Table {};
$stock = new Stock();
$rows = $stock->fetchAll($db->quoteInto('product in (?)', $ids));
foreach ($rows as $row) {
if ($row->amount == 0) {
// 在庫がない本の場合の処理
}
}

みたいに回りくどくZend_Db_Tableを使って書くことになるんだよね? なんかDB_DataObjectの頃よりもずいぶんわかりにくくなっちゃったなーと思えてしまう。それとも俺が思いついていないだけで、現状のZend_Dbを使ってもっときれいに書く方法があるのかな?

一応補足しておくと、

Zend_Db_Adapterを使って、連想配列で結果を受け取った場合、たとえばamountというカラム名を間違って、

    if ($row['amaunt'] == 0) {

なんて書いたとしても、(E_NOTICEが有効ならばnoticeは出るけど)エラーは出ない。一方、Zend_Db_Table_Rowで結果を得る場合は、

    if ($row->amaunt == 0) {

と書くと、存在しないカラム名を使ったと言うことで、例外が投げられる。もちろん後者の方がコーディングスタイルとしてはいいよね。

Published At2006-03-24 00:00Updated At2006-03-24 00:00