モデルとデータベーステーブルの作成 始める前にちょっと考えてみましょう。これらのクラスはどこにあって、それをどのように探すのでしょうか? わたしたちが作成したデフォルトのプロジェクトでは、一つのオートローダーがインスタンス化されます。 そこへ、他のクラスを探せるよう別のオートローダーを付け加えることができます。 MVC の様々なクラスはふつう一つのツリー -- ここでは application/ -- に集まっていて、ほとんどの場合には共通のプレフィックスを持っていてほしいものです。 Zend_Controller_Front は、独立したミニアプリケーション、 すなわち "モジュール" という考え方を採用しています。モジュールは zf ツールが application/ 以下に設定するディレクトリ構造を模倣しており、 その中の全てのクラスはモジュール名を共通のプレフィックスとして持っているものと見なされます。 application/ はそれ自体が一つのモジュール -- "default" または "application" モジュール -- です。以上を踏まえてこのディレクトリ下にあるリソースのオートロードを設定していきたいと思います。 Zend_Application_Module_Autoloader は、あるモジュールの様々なリソースを 適切なディレクトリに対応付けるために必要となる機能を提供し、同時に、名前の付け方の規約も提供します。 このクラスのインスタンスは、デフォルトではブートストラップ・オブジェクトの初期化時に作成され、 アプリケーションのブートストラップでは "Application" というプレフィックスをデフォルトで使用します。 そのため、モデル、フォーム、テーブル・クラスはどれも、プレフィックス "Application_" で始めます。 では、ゲストブックを作っていくことにしましょう。一般的にはコメントタイムスタンプ、それからたまにメールアドレスを持つ単純なリストになります。 それらをデータベースに保存するとしたら、各エントリーのユニークな識別子も欲しいかも知れません。 エントリーを保存したり特定のエントリーを取ってきたり全エントリーを読み出したくなることでしょう。 そうだとすると、簡単なゲストブックモデルの API はこのようになりそうです。 __get()__set() は、 各エントリーのプロパティにアクセスする便利な仕組みと、他のゲッター、セッターのプロキシ提供してくれます。 また、オブジェクト中の許可したプロパティのみアクセス可能にするのにも役立ちます。 find()fetchAll() は単一のエントリー、 全てのエントリーをフェッチする機能を提供し、save() は 一つのエントリーをデータストアに保存する面倒を見ます。 ここでようやく、データベース設定について考え始めることができます。 まず Db リソースを初期化する必要があります。 LayoutView リソースから 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 を使います)と、 そのデータソースをドメインモデルに繋げる役目のデータマッパーを構築することが含まれます。 最後に、既存のエントリーの表示と新規エントリーの処理をモデルに結び付けるコントローラーも作ります。 ここではデータソースへの接続に Table Data Gateway を使います。 Zend_Db_Table がこの機能を提供してくれます。 始めるにあたって Zend_Db_Table ベースのクラスを作りましょう。 レイアウトとデータベースアダプターでやった時と同じように、zf ツールの力を借りることができます。create db-table コマンドを使うのです。 これは最低で 2 つの引数をとります。参照させるクラスと、対応付けるデータベーステーブルの名前です。 Looking at your directory tree, you'll now see that a new directory, application/models/DbTable/, was created, with the file Guestbook.php. If you open that file, you'll see the following contents: Note the class prefix: Application_Model_DbTable. The class prefix for our module, "Application", is the first segment, and then we have the component, "Model_DbTable"; the latter is mapped to the models/DbTable/ directory of the module. All that is truly necessary when extending Zend_Db_Table is to provide a table name and optionally the primary key (if it is not "id"). Now let's create a Data Mapper. A Data Mapper maps a domain object to the database. In our case, it will map our model, Application_Model_Guestbook, to our data source, Application_Model_DbTable_Guestbook. A typical API for a data mapper is as follows: In addition to these methods, we'll add methods for setting and retrieving the Table Data Gateway. To create the initial class, use the zf CLI tool: Now, edit the class Application_Model_GuestbookMapper found in application/models/GuestbookMapper.php to read as follows: _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; } } ]]> Now it's time to create our model class. We'll do so, once again, using the zf create model command: We'll modify this empty PHP class to make it easy to populate the model by passing an array of data either to the constructor or a setOptions() method. The final model class, located in application/models/Guestbook.php, should look like this: 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; } } ]]> Lastly, to connect these elements all together, lets create a guestbook controller that will both list the entries that are currently inside the database. To create a new controller, use the zf create controller command: This will create a new controller, GuestbookController, in application/controllers/GuestbookController.php, with a single action method, indexAction(). It will also create a view script directory for the controller, application/views/scripts/guestbook/, with a view script for the index action. We'll use the "index" action as a landing page to view all guestbook entries. Now, let's flesh out the basic application logic. On a hit to indexAction(), we'll display all guestbook entries. This would look like the following: view->entries = $guestbook->fetchAll(); } } ]]> And, of course, we need a view script to go along with that. Edit application/views/scripts/guestbook/index.phtml to read as follows:

Sign Our Guestbook

Guestbook Entries:
entries as $entry): ?>
escape($entry->email) ?>
escape($entry->comment) ?>
]]>
Checkpoint Now browse to "http://localhost/guestbook". You should see the following in your browser: Using the data loader script The data loader script introduced in this section (scripts/load.sqlite.php) can be used to create the database for each environment you have defined, as well as to load it with sample data. Internally, it utilizes Zend_Console_Getopt, which allows it to provide a number of command line switches. If you pass the "-h" or "--help" switch, it will give you the available options: The "-e" switch allows you to specify the value to use for the constant APPLICATION_ENV -- which in turn allows you to create a SQLite database for each environment you define. Be sure to run the script for the environment you choose for your application when deploying.