Zend Frameworkのソースを読んで思ったこと2007年07月15日 18時26分38秒

まぁ、この辺りはPHPな方々には当たり前チックな話かもしらんが、Zend Frameworkのソースを読んだりしてて感心したのでいくつかメモをば。

メソッドチェーン

Zend_Db_Selectのように、実行前にいくつものパラメータを与える必要があるオブジェクトはパラメータを設定するメソッドの戻り値がオブジェクト自身になっていて、メソッドチェーン(Zendは「流暢なインターフェイス」とか言ってるみたいだが)を利用できる。jQueryっぽく合理的だ。

以下はちょっとしたサンプル。Zend_Db_Selectはfrom()、join()、where()など、SQLの句に相当するメソッドを使ってSELECTクエリを構築するオブジェクト。これを使って、「SELECT id FROM bookmarks WHERE id < 100」を作る場合は、

// $db は接続済みのZend_Db_Adapter
$select = $db->select();

// メソッドチェーンでクエリを構築
$select->from( 'bookmarks' )
	->where( 'id < 100' );

こんな感じ。まぁこの程度は普通に
$db->query( 'SELECT id FROM bookmarks WHERE id < 100' );
でいいんだけども。

アクセッサメソッドが楽しくなった

割とMS系の言語に慣れているので、クラスのフィールドへアクセスするのにアクセッサメソッド(getter/setter)を使うのはちょっと抵抗があった。だってプロパティ表記のがシンプルじゃん。ま、使うしかないんだけどもね。

なんだけども、setterを「流暢なインターフェイス」にすることで、プロパティアクセスよりも使い勝手がよくなるってことをZFが教えてくれた。

例えばZend_Auth_Adapter_DbTable。このクラスはZend_Authによる認証処理のバックエンドにデータベース使用するためのアダプタなのだが、使用するにあたり「認証テーブルのテーブル名」「IDのカラム名」「パスワードのカラム名」を設定してやる必要がある。これをそれぞれ「setTableName(string $tableName)」「setIdentityColumn(string $identityColumn)」「setCredentialColumn(string $credentialColumn)」を使って設定するのだが、これらはメソッドチェーンで使用できる。

// Zend_Auth_Adapter_DbTableはDB接続するので
// Zend_Db_Adapterを必要とする。
// $dbはZend_Db_Adapter。
$auth_adapter = new Zend_Auth_Adapter_DbTable( $db );

// テーブル名、IDカラム、パスワードカラムを順次割り当てる
$auth_adapter->setTableName( 'accounts' )
	->setIdentityColumn( 'login_id' )
	->setCredentialColumn( 'password' );

てな具合。いや、コンストラクタで一気に指定できるんだけどね、本当は。ちょっと他にいい「set~」系のクラスが思いつかなかったもんで。

でも、これって言語に依存しないから、C#なんかでもこういう設計にするのもアリかも。

コンストラクタ引数の処理のアイディア

例えばコンストラクタにいろいろパラメータを設定しなきゃなんない場合に、仮引数を延々とたくさん並べてあると結構うんざりな感じになる(まぁ意味的には厳密なのでそのほうがよいのかも知らんけど)んだけども、prototype.jsのAjax.Requestなんかは第二引数に「options」という無名オブジェクトを受け取る仕様にして「だらだらとした仮引数」を回避している。

ZFのソースを見ていても、いくつかこういったパターンを使用しているのだが、ここでちょっとした工夫をしていた。

どういうことかというと、パラメータリストに相当する連想配列のキーをあらかじめconstで宣言しているのだ。

基本抽象クラスZend_Db_Table_Abstractのソースより一部抜粋。

/**
 * Class for SQL table interface.
 *
 * @category   Zend
 * @package    Zend_Db
 * @subpackage Table
 * @copyright  Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
abstract class Zend_Db_Table_Abstract
{

	const ADAPTER          = 'db';
	const SCHEMA           = 'schema';
	const NAME             = 'name';
	const PRIMARY          = 'primary';
	// 中略
	
	// コンストラクタ
	public function __construct($config = array())
	{
		/**
		 * Allow a scalar argument to be the Adapter object or Registry key.
		 */
		if (!is_array($config)) {
			$config = array(self::ADAPTER => $config);
		}

		foreach ($config as $key => $value) {
			switch ($key) {
				case self::ADAPTER:
					$this->_setAdapter($value);
					break;
				case self::SCHEMA:
					$this->_schema = (string) $value;
					break;
				case self::NAME:
					$this->_name = (string) $value;
					break;
				case self::PRIMARY:
					$this->_primary = (array) $value;
					break;

				// 中略

	            default:
					// ignore unrecognized configuration directive
					break;
			}
		}

		$this->_setup();
	}
	// 中略
}
といった具合に、連想配列のキーと付き合わせるものをあらかじめconstにしておくことで、マッチングミスを軽減しているようなのだ。

当然、呼び出し側でもパラメータを設定するのに

$config = array(
	Zend_Db_Table_Abstract::ADAPTER => $db,
	Zend_Db_Table_Abstract::NAME => 'bookmarks',
	// (略
);
といった具合にconstを使用することができる。こうすることで、
  • キー名をタイプミスを呼び出し時ではなくコンパイル時に発見できる
  • (PDTのように)コード補完機能があるエディタ上でミスなく指定できる
というメリットを作り出している。まぁ、アプローチとしてはすごくまっとうでわかりやすいので、dara-jがアタマわるいだけという話もあるが、結構目からウロコが落ちたり落ちなかったり。JSでも使えそうだな。

まとめ

jQueryですでに実現されているけど、メソッドチェーンは思いのほか気持ちよく使える上、大体の言語で(是非はあると思うが)そのまま応用が効くので個人的には積極的に採用していこうかな、と。

コメント

コメントをどうぞ

※メールアドレスとURLの入力は必須ではありません。 入力されたメールアドレスは記事に反映されず、ブログの管理者のみが参照できます。

※なお、送られたコメントはブログの管理者が確認するまで公開されません。

名前:
メールアドレス:
URL:
コメント:

トラックバック

このエントリのトラックバックURL: http://dara-j.asablo.jp/blog/2007/07/15/1656037/tb

※なお、送られたトラックバックはブログの管理者が確認するまで公開されません。