現在のZend_Dbはモデルの扱いが面倒でありまだまだ改善の余地がある。(その点symfonyはモデルの扱いについてはPropelやDoctrineといった外部ライブラリに依存することで割り切っている。)ZFもせっかくの疎結合を売りにしているのだからモデル層についてはZend_Dbよりもっとよいものが使えれば、と思っていた。一方他の開発者も同じことを思っているようで、海外ではZFをDoctrineと組み合わせて使う試みがけっこう盛んなようだ。この分野ではこの記事が有名なようで、数カ国後に訳されている。今回はこれを日本語に訳してみた。誤訳・ぬけがあれば指摘してもらいたい。個人的にDoctrineには非常に興味があるのだが、Web上ではZend_Log / Zend_AuthといったモジュールをZend_Dbを使用せずにDoctrineで置き換えるためのプロジェクトもあり面白い。それらは後日改めて紹介していきたい。
Ruben Vermeersch
Retrieved from: http://ruben.savanne.be/articles/integrating-zend-framework-and-doctrine
Zend Framework と Doctrineの統合
Zend Framework とDoctrine. を一緒に使用してプロジェクトをセットアップする方法について説明します。簡単なメッセージボードをステップバイステップで作っていきます。
最初に
この記事の内容は平易なものですが、読者がZendFrameworkとDoctrineについて知識があることを前提にしています。 二つを組み合わせる方法を知る前にまずそれぞれを個別に使ってみることを勧めます。 どちらにも初心者のための良質なドキュメントが整備されています。Zend Framework Quick Start とDoctrine’s My First Projectです。 Akra’s Zend Framework Tutorial も非常に良い手引きです。
Zend Framework は”use-at-wil”(使いたいときに使う)というアーキテクチャに基づいています。 つまり他のフレームワークのように「全部使うか、全く使わないか」というアプローチはとっていません。ZendFrameworkは全体のうち必要な部分だけ使うことができるのです。 「使いたいときに使う」というアーキテクチャであるゆえに、ZendFremeworkが提供するデータベース抽象化レイヤーを使用せずともZendFrameworkアプリケーションを作ることができます。Zend_Dbがダメなテクノロジーだというわけではありませんが、低層のデータベースレイヤーに近い部分をカバーしているだけにとどまっています。 Doctrineを使えば、データベースのことを気にせずにデータをオブジェクトライクに操作することができます。
Zend Framework ではアプリケーション作成の際の自由度が高くなっています。 言い換えるなら、ZendFrameworkではプロジェクトにフレームワークが定める特定の構造を使わなくともよいのです。 今回はここで提供されているデフォルトのプロジェクト構造になるべく沿うようにします。 しかしこれは全くもって好みの問題です。
始めましょう
まず最初に、デフォルトのプロジェクト構造を作成してライブラリをインストールします。 ファイルマネージャを開いて次のようなフォルダー構造を作ってください。 それぞれのフォルダの目的についてはすぐ後で説明します。
基本のフォルダ構造
たくさんのフォルダがありますが、ZendFrameworkを使ったことがあるならばほとんど見覚えがある名前のはずです。 ただしデフォルトの構造と異なる部分があります。:
application/doctrine/: ここにはDoctrineが使用するSQLやYAMLのスキーマ、マイグレーション、データダンプといったものが格納されます。application/models/: Doctrineが自動的に生成するモデルファイルが格納されます。モデルファイルはZendFrameworkの中から簡単に利用することができます。library/: 普通はここにZendFrameworkだけを入れればよいのですが 今回は二つのライブラリが必要になります。つまりZendFrameworkとDoctrineです。 ですので、サブフォルダを二つ作ってそれぞれにインストールすることにします。scripts/Doctrineにはちょっとしたコマンドラインスクリプトが付属しています。ここにそのツールが配置されます。(先ほど紹介したサイトでそのように推奨されています。)
次にZendFrameworkとDoctrineをインストールします。 ウェブサイトからそれぞれの最新版を個別にダウンロードして解凍してください。その中からZendFrameworkの場合はlibraryフォルダの中を、Doctrineの場合はlibフォルダの中を先ほど作ったフォルダに入れてください。 こんな感じになるはずです。
Zend Framework と Doctrine がインストールされた状態
ブートストラップ
Zend Framework Quick Start の内容に従うならば、bootstrap.phpというファイルを作る必要があります。 このファイルを作った上でさらにDoctrineを使うことができるように少々手直しをしましょう。
最初にpublic/index.php とpublic/.htaccess を作ります。 自分の好みのエディタを起動して、次のソースコードをコピーしてファイルを作ってください。(注:Gitを使用してすべてのソースコードを入手できます。詳細は記事の末尾にある付録を見てください。)
public/index.php
<?php require '../application/bootstrap.php';
public/.htaccess
RewriteEngine on
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteRule ^(.*)$ index.php/$1
見てわかるように、このコードは他のZendFrameworkアプリケーションの場合と変わりありません。 一方application/bootstrap.php は多少違っています。 このファイルを2つのファイルに分割します。: application/bootstrap.php とapplication/global.php です。. 前者はクライアントリクエストを担当し、後者は必要なファイルのインクルードを行います。 bootstrapファイルを分割したのはglobal.php はDoctrineのコマンドラインスクリプトからも使う必要があるからです。(これについてはすぐ後で説明します。)
application/global.php
<?php
error_reporting(E_ALL | E_STRICT);
ini_set('display_startup_errors', 1);
ini_set('display_errors', 1);
date_default_timezone_set('Europe/Brussels');
/*
* Setup libraries & autoloaders
*/
set_include_path(dirname(__FILE__).'/../library/zendframework'
. PATH_SEPARATOR . dirname(__FILE__).'/../library/doctrine'
. PATH_SEPARATOR . dirname(__FILE__).'/models'
. PATH_SEPARATOR . dirname(__FILE__).'/models/generated'
. PATH_SEPARATOR . get_include_path());
require 'Zend/Loader.php';
Zend_Loader::registerAutoload('Zend_Loader');
/*
* Set super-global data
*/
Doctrine_Manager::connection("mysql://user:pass@localhost/database");
/*
* Configure Doctrine
*/
Zend_Registry::set('doctrine_config', array(
'data_fixtures_path' => dirname(__FILE__).'/doctrine/data/fixtures',
'models_path' => dirname(__FILE__).'/models',
'migrations_path' => dirname(__FILE__).'/doctrine/migrations',
'sql_path' => dirname(__FILE__).'/doctrine/data/sql',
'yaml_schema_path' => dirname(__FILE__).'/doctrine/schema'
));
application/bootstrap.php
require dirname(__FILE__).'/global.php'; Zend_Controller_Front::run(dirname(__FILE__).'/controllers');
では順を追って詳しく見ていきましょう。
error_reporting(E_ALL | E_STRICT);
ini_set('display_startup_errors', 1);
ini_set('display_errors', 1);
date_default_timezone_set('Europe/Brussels');
適切なエラーハンドリングとタイムゾーンを設定するのはいつでもふさわしいことです。 特に言及することはありません。
/*
* Setup libraries & autoloaders
*/
set_include_path(dirname(__FILE__).'/../library/zendframework'
. PATH_SEPARATOR . dirname(__FILE__).'/../library/doctrine'
. PATH_SEPARATOR . dirname(__FILE__).'/models'
. PATH_SEPARATOR . dirname(__FILE__).'/models/generated'
. PATH_SEPARATOR . get_include_path());
require 'Zend/Loader.php';
Zend_Loader::registerAutoload('Zend_Loader');
Doctrineを使うための設定です。 inlude pathをZendFrameworkとDoctrineをインクルードするように記述します。 Doctrineが生成するモデルファイルを格納するフォルダもインクルードしています。 Doctrineをautoloadの対象にしていないことに注意してください。 include_pathが正しく設定されていれば後はZend Loaderがやってくれます。 注意: 1.8より前のZendFrameworkにはバグがあるために、Doctrin Class Templateを使った際に(軽微な)ワーニングが出力されます。 この問題はバージョン1.8のリリースで解決されることになっています。(subversion内のリポジトリではすでに修正されています。)
/*
* Set super-global data
*/
Doctrine_Manager::connection("mysql://user:pass@localhost/database");
/*
* Configure Doctrine
*/
Zend_Registry::set('doctrine_config', array(
'data_fixtures_path' => dirname(__FILE__).'/doctrine/data/fixtures',
'models_path' => dirname(__FILE__).'/models',
'migrations_path' => dirname(__FILE__).'/doctrine/migrations',
'sql_path' => dirname(__FILE__).'/doctrine/data/sql',
'yaml_schema_path' => dirname(__FILE__).'/doctrine/schema'
));
このコードの最後の部分では二つの機能を実行します。 まずデータベースへの接続を行います。 コードをシンプルにするために接続情報はハードコーディングしてあります。 より現実的なシステムにおいてはZend_Configなどを使うことになるでしょう。方法についてはここでは説明しませんので各自でトライしてみてください。 次の部分ではDoctrineのコマンドラインツールへのパスを設定しています。(このツールは必要なコードとデータベーススキーマすべてを生成します。) 情報を含んだ配列をZend_Registryに登録します。Zend_Registryはオブジェクトをストアするために汎用的に用いられます。オブジェクトをそこへ保管し後から必要になった時に取り出すことができます。
接続情報の部分については各自のシステムに合わせて修正してください。 データベースは空のものを指定する必要があります。 後でそのデータベースに中身を入れます。
application/bootstrap.php には余分なコードを入れないでください。 繰り返しになりますが、このアプリケーションはできるだけシンプルにしてあります。
いよいよDoctrineのコマンドラインツールをセットアップします。
scripts/doctrine-cli
#!/usr/bin/env php
<?php
require dirname(__FILE__).'/../application/global.php';
$cli = new Doctrine_Cli(Zend_Registry::get('doctrine_config'));
$cli->run($_SERVER['argv']);
次のコマンドでスクリプトを実行可能な状態にしてください。
chmod +x scripts/doctrine-cli
これでツールを使えるようになります。
アプリケーションの作成
土台となるコードの準備ができました。ではZendFrameworkとDoctrineを使用した簡単なアプリケーションを作っていきましょう。 書き込みと閲覧を行うとてもシンプルなメッセージボードです。
このアプリケーションはコントローラ一つとビュースクリプトが一つだけという非常に単純な構造です。 次のコードをコピーしてください。
application/views/scripts/index/index.phtmlf
<html> <head> <title>ZF & Doctrine example</title> </head> <body> <h1>Submit a message:</h1> <?=$this->form?> <hr /> <h1>Messages posted:</h1> <!-- TODO: ここにメッセージを表示 --> </body> </html>
application/controllers/IndexController.php
<?php
class IndexController extends Zend_Controller_Action
{
public function indexAction()
{
$form = $this->getForm();
$req = $this->getRequest();
if ($req->getPost() && $form->isValid($req->getPost())) {
// TODO: データベースにメッセージ書き込む
}
$this->view->form = $form;
// TODO: メッセージを全件取得
}
private function getForm()
{
$form = new Zend_Form();
$form->addElement('text', 'name', array(
'label' => 'Your name',
'required' => true
));
$form->addElement('textarea', 'message', array(
'label' => 'Message',
'required' => true,
'rows' => 4
));
$form->addElement('submit', 'send');
return $form;
}
}
?>
コードの中に全部で3つのTODOアイテムがあるのに気づきましたか。ビュースクリプトに1つとコントローラに2つあります。 この部分にDoctrineを呼び出すためのコードを入れます。 そうするためにはまずデータオブジェクトをいくつか定義する必要があります。 このTODOの部分はあとで処理することにして、まずはデータベースのスキーマを作ります。
スキーマの定義
Doctrineではスキーマ定義にシンプルなテキスト構造であるYAMLを使うことができます。 これを使うとDoctrineがPHPスクリプトを自動で生成してくれます。 スキーマ定義は次のようになります。
application/doctrine/schema/schema.yml
---
Message:
columns:
id:
primary: true
autoincrement: true
type: integer(4)
posted:
type: timestamp
name:
type: string(255)
message:
type: string
このアプリケーションで必要になるのはMessageという名前の非常にシンプルなオブジェクト一つでだけです。Messageはユニークid、メッセージが投稿された時のタイムスタンプ、投稿者の名前、メッセージ本文という4つのカラムからなります。
Doctrineのコマンドラインツールを用いてモデルとDBテーブル定義を生成しましょう。 シェルから次のコマンドを実行してください。
$ ./scripts/doctrine-cli generate-models-yaml generate-models-yaml - Generated models successfully from YAML schema $ ./scripts/doctrine-cli generate-sql generate-sql - Generated SQL successfully for models $ ./scripts/doctrine-cli create-tables create-tables - Created tables successfully
正しく実行されれば何もエラーメッセージは表示されません。 もしエラーメッセージが出るようならデータベースへの接続が正しくセットアップされているか確かめてください。
統合する
先ほど残したTODOアイテムを片付けましょう。 一つずついきます。 完成したコードは記事の末尾に載せてありますので参照してください。 まずにメッセージを保存するためのコードです。 この部分を
// TODO: データベースにメッセージを書き込む
次のようにしてください。<?php と?> は省略してあります。
$message = new Message();
$message->fromArray($form->getValues(true));
$message->posted = new Doctrine_Expression('NOW()');
$message->save();
Messageというクラスが使われています。 このクラスはDoctrineによって自動生成されたものです。 application/models/の中にあります。 オートローダが必要なクラスを自動でロードしてくれます。
メッセージを表示するためにそれを取得する必要があります。 まずはコントローラです。
// TODO: メッセージを全件取得
を次のコードで置き換えてください。
messages = Doctrine_Query::create()
->from('Message m')
->orderBy('m.posted DESC')
->execute();
$this->view->messages = $messages;
これも非常にシンプルです。 DQLを使って投稿時間の新しいものから表示されるようにソートしてあります。
あとは取得したメッセージレコードをビュースクリプトに表示させるだけです。
<!-- TODO: ここにメッセージを表示 --> を次のコードで置き換えてください。
<?php foreach ($this->messages as $message): ?>
<h2><?=$message->name?> (<?=$message->posted?>)</h2>
<?=$message->message?>
<?php endforeach; ?>
できました。表示結果はこのようになります。
完成したアプリケーション
このアプリケーションを公開して運用することは避けてください。今回は重要なポイントだけに絞ったので入力値のエスケープなどは省略してあります。 自分で組み込んでみてください。
まとめ
これでZendFrameworkとDoctrineを使ってクリーンでシンプルなアプリケーションを作ることができました。 両フレームワークは同様の設計思想を持っているので非常に美しく統合することが可能なのです。そのためアプリケーション開発はとても楽しいものになります。
意見/コメント/質問があれば(ruben@savanne.be) にメールしてください。あるいは 私のブログにコメントしてください。.
付録: コードの入手について
コードのコピー&ペーストを行いたくなければGitを使ってください。 修正前のTODOアイテムが残っているバージョンと完成したバージョンの両方をオンラインのGitレポジトリに置きました。 以下に入手方法を示します:
- リポジトリのクローンを作る:
git clone git://git.savanne.be/git/chatapp.gitこれで修正前のコードを入手できます。 - 修正後のバージョンが欲しい場合は次のコマンドを実行してください:
git checkout -b finished-version origin/finished-version - 両方のバージョンの間を移動する場合は
git checkout master(修正前のバージョン) 、そしてgit checkout finished-version(修正後のバージョン)を実行してください。
付録: 完成したコード
application/controllers/IndexController.php
<?php
class IndexController extends Zend_Controller_Action
{
public function indexAction()
{
$form = $this->getForm();
$req = $this->getRequest();
if ($req->getPost() && $form->isValid($req->getPost())) {
$message = new Message();
$message->fromArray($form->getValues(true));
$message->posted = new Doctrine_Expression('NOW()');
$message->save();
}
$this->view->form = $form;
$messages = Doctrine_Query::create()
->from('Message m')
->orderBy('m.posted DESC')
->execute();
$this->view->messages = $messages;
}
private function getForm()
{
$form = new Zend_Form();
$form->addElement('text', 'name', array(
'label' => 'Your name',
'required' => true
));
$form->addElement('textarea', 'message', array(
'label' => 'Message',
'required' => true,
'rows' => 4
));
$form->addElement('submit', 'send');
return $form;
}
}
?>
application/views/scripts/index/index.phtml
<html> <head> <title>ZF & Doctrine example</title> </head> <body> <h1>Submit a message:</h1> <?=$this->form?> <hr /> <h1>Messages posted:</h1> <?php foreach ($this->messages as $message): ?> <h2><?=$message->name?> (<?=$message->posted?>)</h2> <?=$message->message?> <?php endforeach; ?> </body> </html>



Pingback: Zend Framework Quick StartにDoctrineを適用してみる - KDF Memo