モデルとデータベーステーブルの作成 始める前にちょっと考えてみましょう。これらのクラスはどこにあって、それをどのように探すのでしょうか? わたしたちが作成したデフォルトのプロジェクトでは、一つのオートローダーがインスタンス化されます。 そこへ、他のクラスを探せるよう別のオートローダーを付け加えることができます。 MVC の様々なクラスはふつう一つのツリー -- ここでは application/ -- に集まっていて、ほとんどの場合には共通のプレフィックスを持っていてほしいものです。 Zend_Controller_Front は、独立したミニアプリケーション、 すなわち "モジュール" という考え方を採用しています。モジュールは zf ツールが application/ 以下に設定するディレクトリ構造を模倣しており、 その中の全てのクラスはモジュール名を共通のプレフィックスとして持っているものと見なされます。 application/ はそれ自体が一つのモジュール -- "default" または "application" モジュール -- です。以上を踏まえてこのディレクトリ下にあるリソースのオートロードを設定していきたいと思います。 Zend_Application_Module_Autoloader は、あるモジュールの様々なリソースを 適切なディレクトリに対応付けるために必要となる機能を提供し、同時に、名前の付け方の規約も提供します。 このクラスのインスタンスは、デフォルトではブートストラップ・オブジェクトの初期化時に作成され、 アプリケーションのブートストラップでは "Application" というプレフィックスをデフォルトで使用します。 そのため、モデル、フォーム、テーブル・クラスはどれも、プレフィックス "Application_" で始めます。 では、ゲストブックを作っていくことにしましょう。一般的にはコメントタイムスタンプ、それからたまにメールアドレスを持つ単純なリストになります。 それらをデータベースに保存するとしたら、各エントリーのユニークな識別子も欲しいかも知れません。 エントリーを保存したり特定のエントリーを取ってきたり全エントリーを読み出したくなることでしょう。 そうだとすると、簡単なゲストブックモデルの API はこのようになりそうです。 __get()__set() は、 各エントリーのプロパティにアクセスする便利な仕組みと、他のゲッター、セッターのプロキシを提供してくれます。 また、オブジェクト中の許可したプロパティのみアクセス可能にするのにも役立ちます。 find()fetchAll() は単一のエントリーや 全てのエントリーをフェッチする機能を提供し、save() は 一つのエントリーをデータストアに保存する面倒を見ます。 ここでようやく、データベース設定について考え始めることができます。 まず Db リソースを初期化する必要があります。 Layout リソースと View リソースから Db リソースの設定を準備できます。これには zf configure db-adapter コマンドが使えます。 'adapter=PDO_SQLITE&dbname=APPLICATION_PATH "/../data/db/guestbook.db"' \ > production A db configuration for the production has been written to the application config file. % zf configure db-adapter \ > 'adapter=PDO_SQLITE&dbname=APPLICATION_PATH "/../data/db/guestbook-testing.db"' \ > testing A db configuration for the production has been written to the application config file. % zf configure db-adapter \ > 'adapter=PDO_SQLITE&dbname=APPLICATION_PATH "/../data/db/guestbook-dev.db"' \ > development A db configuration for the production has been written to the application config file. ]]> ここで application/configs/application.ini ファイルの相当する部分に 以下の行が追加されているのが見付かるので、編集します。 設定ファイルが最終的に以下のようになるようにしてください。 データベースは data/db/ に保存されることに注意しましょう。 ディレクトリを作って全ユーザーに書き込み権限を与えます。ユニックスライクなシステムでは、 次のようにすれば設定できます。 Windows では、エクスプローラでディレクトリを作り、 全ユーザーがそのディレクトリに書き込めるようアクセス権を設定する必要があります。 この段階でデータベース接続が行えます。今の例では application/data/ ディレクトリ内にある Sqlite データベースへの接続です。では、ゲストブックのエントリーを入れる簡単なテーブルを設計しましょう。 それから、素晴らしい仕事ができるように、数行、アプリケーションを面白くするする情報を作りましょう。 これでスキーマができ、データもいくらか定義できました。それでは一緒にスクリプトを書いてこのデータベースの構築を実行しましょう。 普通は、プロダクション環境でこういったことは必要ありませんが、このスクリプトがあれば開発者が必要なデータベースを手元で構築して、アプリケーションの作業に全力投球するのを助けてくれるでしょう。 以下の内容で、scripts/load.sqlite.php としてスクリプトを作ってください。 'Load database with sample data', 'env|e-s' => 'Application environment for which to create database (defaults to development)', 'help|h' => 'Help -- usage message', )); try { $getopt->parse(); } catch (Zend_Console_Getopt_Exception $e) { // オプションが不正な場合に使用法を表示 echo $e->getUsageMessage(); return false; } // ヘルプが要求された場合に使用法を表示 if ($getopt->getOption('h')) { echo $getopt->getUsageMessage(); return true; } // CLI オプションの有無に応じて値を初期化 $withData = $getopt->getOption('w'); $env = $getopt->getOption('e'); defined('APPLICATION_ENV') || define('APPLICATION_ENV', (null === $env) ? 'development' : $env); // Zend_Application の初期化 $application = new Zend_Application( APPLICATION_ENV, APPLICATION_PATH . '/configs/application.ini' ); // DB リソースの初期化と読み込み $bootstrap = $application->getBootstrap(); $bootstrap->bootstrap('db'); $dbAdapter = $bootstrap->getResource('db'); // やっていることをユーザーに通知 // (実際にここでデータベースを作る) if ('testing' != APPLICATION_ENV) { echo 'Writing Database Guestbook in (control-c to cancel): ' . PHP_EOL; for ($x = 5; $x > 0; $x--) { echo $x . "\r"; sleep(1); } } // データベースファイルが既にないかチェック $options = $bootstrap->getOption('resources'); $dbFile = $options['db']['params']['dbname']; if (file_exists($dbFile)) { unlink($dbFile); } // このブロックでスキーマファイルから読み込んだ実際のステートメントを実行 try { $schemaSql = file_get_contents(dirname(__FILE__) . '/schema.sqlite.sql'); // use the connection directly to load sql in batches $dbAdapter->getConnection()->exec($schemaSql); chmod($dbFile, 0666); if ('testing' != APPLICATION_ENV) { echo PHP_EOL; echo 'Database Created'; echo PHP_EOL; } if ($withData) { $dataSql = file_get_contents(dirname(__FILE__) . '/data.sqlite.sql'); // use the connection directly to load sql in batches $dbAdapter->getConnection()->exec($dataSql); if ('testing' != APPLICATION_ENV) { echo 'Data Loaded.'; echo PHP_EOL; } } } catch (Exception $e) { echo 'AN ERROR HAS OCCURED:' . PHP_EOL; echo $e->getMessage() . PHP_EOL; return false; } // 大抵の場合、このスクリプトはコマンドラインから走らせて true を返す ]]> このスクリプトを実行しましょう。ターミナルか DOS のコマンドラインから以下を実行してください。 以下のような出力を目にすると思います。 これでゲストブックアプリケーションのためにきちんと動くデータベースとテーブルができました。 次のステップはアプリケーションのコードを作成することです。 これにはデータソース(ここでは Zend_Db_Table を使います)と、 そのデータソースをドメインモデルに繋げる役目のデータマッパーを構築することが含まれます。 最後に、既存のエントリーの表示と新規エントリーの処理をモデルに結び付けるコントローラーも作ります。 ここではデータソースへの接続にテーブルデータゲートウェイ を使います。 Zend_Db_Table がこの機能を提供してくれます。 始めるにあたって Zend_Db_Table ベースのクラスを作りましょう。 レイアウトとデータベースアダプタでやった時と同じように、zf ツールの力を借りることができます。create db-table コマンドを使うのです。 これは最低で 2 つの引数をとります。参照させるクラスと、対応付けるデータベーステーブルの名前です。 ディレクトリツリーを見てみると、新規ディレクトリ application/models/DbTable/ が作られてファイル Guestbook.php が作られているのが分かります。 ファイルを開くと以下の内容になっています。 クラスのプレフィックス Application_Model_DbTable に注目しましょう。 最初の部分がモジュールのクラスプレフィックス "Application" で、その後にコンポーネント "Model_DbTable" がきます。後者はモジュールのディレクトリ models/DbTable/ に対応付けられています。 Zend_Db_Table を拡張する際に必要なのはテーブル名と場合により主キーを ("id" でなければ)与えることだけです。 では データマッパー を作成しましょう。データマッパー はドメインオブジェクトをデータベースに対応付けます。 ここではモデル Application_Model_Guestbook をデータソース Application_Model_DbTable_Guestbook に対応付けることになります。 データマッパーの典型的な API は次のようになるでしょう。 こうしたメソッドの他に、テーブルデータゲートウェイを設定してそこからデータを取り出すメソッドを追加します。 クラスの最初の形を作成するのに CLI の zf ツールを使います。 次に application/models/GuestbookMapper.php にあるクラス Application_Model_GuestbookMapper を編集して下記の通りにします。 _dbTable = $dbTable; return $this; } public function getDbTable() { if (null === $this->_dbTable) { $this->setDbTable('Application_Model_DbTable_Guestbook'); } return $this->_dbTable; } public function save(Application_Model_Guestbook $guestbook) { $data = array( 'email' => $guestbook->getEmail(), 'comment' => $guestbook->getComment(), 'created' => date('Y-m-d H:i:s'), ); if (null === ($id = $guestbook->getId())) { unset($data['id']); $this->getDbTable()->insert($data); } else { $this->getDbTable()->update($data, array('id = ?' => $id)); } } public function find($id, Application_Model_Guestbook $guestbook) { $result = $this->getDbTable()->find($id); if (0 == count($result)) { return; } $row = $result->current(); $guestbook->setId($row->id) ->setEmail($row->email) ->setComment($row->comment) ->setCreated($row->created); } public function fetchAll() { $resultSet = $this->getDbTable()->fetchAll(); $entries = array(); foreach ($resultSet as $row) { $entry = new Application_Model_Guestbook(); $entry->setId($row->id) ->setEmail($row->email) ->setComment($row->comment) ->setCreated($row->created); $entries[] = $entry; } return $entries; } } ]]> これでモデルクラスが作れます。ここでもコマンド zf create model を使います。 この空の PHP クラスを修正して、コンストラクタでも setOptions() メソッドでも、データの配列からモデルを生成するのを簡単にします。application/models/Guestbook.php 中の最終的なモデルクラスはこのようになるはずです。 setOptions($options); } } public function __set($name, $value) { $method = 'set' . $name; if (('mapper' == $name) || !method_exists($this, $method)) { throw new Exception('Invalid guestbook property'); } $this->$method($value); } public function __get($name) { $method = 'get' . $name; if (('mapper' == $name) || !method_exists($this, $method)) { throw new Exception('Invalid guestbook property'); } return $this->$method(); } public function setOptions(array $options) { $methods = get_class_methods($this); foreach ($options as $key => $value) { $method = 'set' . ucfirst($key); if (in_array($method, $methods)) { $this->$method($value); } } return $this; } public function setComment($text) { $this->_comment = (string) $text; return $this; } public function getComment() { return $this->_comment; } public function setEmail($email) { $this->_email = (string) $email; return $this; } public function getEmail() { return $this->_email; } public function setCreated($ts) { $this->_created = $ts; return $this; } public function getCreated() { return $this->_created; } public function setId($id) { $this->_id = (int) $id; return $this; } public function getId() { return $this->_id; } } ]]> 最後に、以上の要素を全て一つに繋げます。データベース内の既存のエントリーを一覧表示する ゲストブック用のコントローラを作りましょう。 新しいコントローラを作るには zf create controller コマンドを使います。 このコマンドによって application/controllers/GuestbookController.php の中に GuestbookController が作られ、そこには一つのアクションメソッド indexAction() が出来ています。また、このコントローラーの ビュースクリプト用ディレクトリ application/views/scripts/guestbook/ とインデックスアクション用のビュースクリプトも作成されます。 ゲスブックの全エントリーを表示する入り口用のページとして "index" アクションを使います。 では、基本的なアプリケーションロジックを一息に作ってしまいましょう。indexAction() へやって来るとゲストブックの全エントリーを表示します。これは次のようになります。 view->entries = $guestbook->fetchAll(); } } ]]> それからもちろんこれに使うビュースクリプトが必要です。 application/views/scripts/guestbook/index.phtml を以下のように編集します。

Sign Our Guestbook

Guestbook Entries:
entries as $entry): ?>
escape($entry->email) ?>
escape($entry->comment) ?>
]]>
チェックポイント ここで "http://localhost/guestbook" にアクセスしてみましょう。 ブラウザには次のように表示されるはずです。 データローダースクリプトの使用 この節で導入したデータローダースクリプト(scripts/load.sqlite.php)は 定義した環境のそれぞれでデータベースを作りサンプルデータを読み込むのに使用できます。 内部では、多くのコマンドラインスイッチを提供できるようにしてくれる Zend_Console_Getopt を利用しています。"-h" または "--help" スイッチを渡すと使用可能なオプションを提示します。 "-e" スイッチを使うと APPLICATION_ENV 定数に使用する値を指定できます。 -- 定義した各環境で順に SQLite データベースを作れるようになるのです。 デプロイ時に、アプリケーション用に選んだ環境で確実にこのスクリプトを走らせるようにしてください。