Home > Tags > Zend Framework
Zend Framework
EclipseからPhingを使う
- 2009-06-30 (火)
- 技術・開発ツール
phingのタスクををantとしてラッピングしてみた。なんでそんなことをする必要があるのかと言えば、Eclipse(PDT / Zend Studio for Eclipse)からphingを使うためである。そしてなぜphingを使いたいかというと、Zend Frameworkを使用したプロジェクトの環境変数(production環境 or development環境)という情報を.htaccessやiniファイルにハードコーディングするのに違和感があるからである。こういう値はアプリケーション本体の上に位置するレイヤーで設定・コントロールするべきものだ。
phingをEclipseから使うためには外部ツールとしての登録などいくつか方法があるとは思うが、せっかく親戚のantが標準で入っているのだからそれを使わせていただく。phingのタスクをantのタスクでラッピングしてEclipse側からはantとして実行するわけである。ラッピングしているだけなので、Eclipseを使わずにコマンドラインから直接phingを実行することもできる。
事前準備:
- phingをインストールする。(詳細は省略。)
- Zend Studio for Eclipse(6.1系 or 7.0.0beta)を使用している場合、デフォルトではなぜかantがオフになっているのでちょっとした設定が必要。(PDTの場合は不要。)
設定方法:
1. メニューからNew(新規作成) => Other…(その他)を選ぶ。

2. Show All Wizardsにチェックを入れてから、Java => Java Project from Existing Ant Bundleを選びNext。

3. するとダイアログボックスが「Ant Developmentを有効にするか?」と聞いてくるのでOKを押す。新規作成そのものはキャンセル。これでAntが使えるようになる。
処理対象ファイル
application.php.distというファイルに環境関連の設定記述があるものとする。このファイルに対してphingで処理を行う。
application.php.dist
<?php
$paths = array(
get_include_path(),
'../library'
);
set_include_path(implode(PATH_SEPARATOR, $paths));
defined('APPLICATION_PATH')
or define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../application'));
defined('APPLICATION_ENV')
or define('APPLICATION_ENV', '@ENVIRONMENT@');
以下略
タスクの定義
1. phingの定義ファイルbuild.xml)を作成
build.xml
<?xml version="1.0" encoding="UTF-8"?>
<project name="testphing" default="setappenv" >
<property name="basedir" value="../" />
<target name="setappenv">
<property file="ant.properties" />
<copy file="${basedir}/application/application.php.dist"
tofile="${basedir}/application/application.php" overwrite="true">
<filterchain>
<replacetokens begintoken="@" endtoken="@">
<token key="ENVIRONMENT" value="${environment}" />
</replacetokens>
</filterchain>
</copy>
</target>
</project>
2. プロパティファイルを設定
ant.properties
phing=/Path/to/phing environment=development
3. ラッパーであるantファイル(ant-build.xml)を作成
ant-build.xml
<?xml version="1.0" encoding="UTF-8"?>
<project name="phing-wrapper" default="setappenv">
<target name="setappenv" description="Set application environment from property file">
<property file="ant.properties" />
<exec dir="" executable="${phing}">
<arg line="setappenv" />
</exec>
</target>
</project>
4. ant-build.xmlをAnt Editorで開き、antビューかoutlineビューにてタスクを選択し、 Run as => Ant buildと実行。
phingを実行するとapplication.php.distの中の@ENVIRONMENT@の部分が”development”に置換され、新たにapplication.phpとして作成される。アプリケーションはpublic/index.phpなどでapplication.phpをインクルードして以後の処理を行うことになる。
まとめ
たかが一個の変数を設定するためにphingを使うのはいかにも大げさだが、タスクを追加設定していけばテンポラリディレクトリの掃除/データのリロード / ユニットテスト実行 / ドキュメント生成 / 配布用アーカイブ作成 といった定型作業を自動で実行できるようになるので正確で迅速なリリースの助けになるだろう。
参考サイト
Zend Framework 1.8: Building MVC Web Applications 。
How to enable the Ant plugin in Zend Studio for Eclipse – Blog of Max Horvath
PHPUnit3で始めるユニットテスト:第5回 PHPUnitの便利な機能とPhingとの連携|gihyo.jp … 技術評論社
- Comments: 0
- Trackbacks: 0
Zend Framework のViewにPHPTALを使う
- 2009-06-24 (水)
- 技術・開発ツール
(少なくとも日本においては)PHPのテンプレートエンジンと言えばSmartyが知名度・普及度共に圧倒的だが、Smartyで定義されたテンプレートファイルはプレビューしたときにデザインが崩れるという欠点がある。これはSmartyを知らないデザイナーさんとの協業をする場合に大きな問題になるし、自分でデザインをするとしてもロジック側が完成しないと出来上がりのイメージを目で見ることができないのがつらい。
ところで最近のPHP製フレームワークはViewとして素のPHPファイルを使うのがモダンスタイルということになっている。確かにテンプレートエンジンを使わずとも記述を工夫すればテンプレート風に書くことはできるしなによりその方が速度の面で有利だ。 そこで目を付けたのがPHPTALだ。日本語のマニュアルも整備されている。 以下のエントリを見ると PHPテンプレートベンチマーク —- PHPTALに注目 – なんたらノート 第二期 速度的にはSmartyとそん色ない。 なによりも大きな特徴はこのエントリが述べているように
* タグ構造を維持したまま、属性に制御コードを書く(デザイナフレンドリ)
* 元ページのDOMノードをラップするマクロを定義できる
という二点だ。2番目の特徴はさておき最初の部分だけでもPHPTALを使う価値が十分にあると思う。
PHPTALとZend Frameworkとの組み合わせについては次の情報が有力と思われる。 TAAT — Zend Framework Tutorial Step 2
※著者はポーランド人のようである。Zend Framework関連の海外情報を見ているとポーランド人が発信した情報が多いのに気づく。
今回PHPTALの調査を兼ねてこのソースをダウンロードして動かしてみた。なお、著者の説明によると
${structure helper:layout().content}
のような記述でビューに変数を書けるとあるのだが、これではPHPTALの美点が失われてしまうのでいただけない。PHPTAL本来の記述を試してみることにする。 コントローラ側で
$this->view->name = 'name';
$this->view->defaulttext = 'hoge@example.com';
$this->view->attributes = array('id'=>'anotherID','size'=>22);
と書きビュー側で
<li tal:content="structure helper:formText(name, defaulttext, attributes)">この部分は置き換えられる</li>
としたところ出力されるHTML が
<li><input type="text" name="name" id="anotherID" value="hoge@example.com" size="22" /></li>
だったのでこのPHPTAL式記述で問題ないようだ。 なお、PHPTALの仕様として
tal:content=”$value”
と書いた場合に$valueは第2引数にENT_QUOTESを、第3引数に文字エンコーディング(デフォルト値はUTF8)を伴ったhtmlspecialchars関数で自動的にエスケープされる。セキュリティの観点から見てこれらのパラメータは指定する必要があるので、PHPTALのこの仕様は正しい。(参照:htmlspecialchars/htmlentitiesの正しい使い方 )もしエスケープさせたくない場合は
tal:content=”structure $value”
と”structure”を付加すれば良い。
さてまずレイアウトを定義したファイルだが
application/modules/default/views/layouts/main.tpl.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>Main layout</title>
</head>
<body id="index">
<div id="page-container">
<h1 id="title">Main layout</h1>
<?php echo $this->layout()->content ?> ⇐1
${structure helper:layout().content} ⇐ 2
<span tal:content="structure helper:layout().content">contents</span> ⇐ 3
</div>
</body>
</html>
1. がZF標準の書き方、2.が変数の中身だけを書く方法、3.がPHPTALの美点を生かした書き方である。3つのうちどれを使っても全く同じHTMLを吐き出す。(ZFSyntax.phpというプレフィルターがあるので1.のように書いてもちゃんとPHPTALが理解できるように内部で書き換えてくれるのだ。)ここではもちろん3.を採用する。 レイアウトの中に含めるビューは
application/modules/default/views/templates/index/index.tpl.html
<div >
<ul>
<li tal:content="structure helper:formatCurrency(price1)">price1(JPY)</li>
<li tal:content="price2">price(with no helper)</li>
<li tal:content="structure helper:formatCurrency(price2, '€')">price2(EURO)</li>
<li tal:content="structure helper:formText(name, defaulttext, array('size'=>35, 'id'=>'myID'))">this is email</li>
<li tal:content="structure helper:formText(name, defaulttext, attributes)">this is email</li>
<li tal:content="name">this is email</li>
</ul>
</div>
コントローラではテスト用に意地悪な値をセットする。
application/modules/default/controllers/IndexControllers.php
<?php
class IndexController extends Zend_Controller_Action
{
public function indexAction ()
{
$this->view->price1 = 'ABC';
$this->view->price2 = '12800';
$this->view->name = "'&<>";
$this->view->defaulttext = "hoge@example.com'&<>";
$this->view->attributes = array('size'=>22, 'id'=>'"anotherID"');
}
}
通貨表示用のフォーマットを行うビューヘルパーを作る。
application/modules/default/views/helpers/FormatCurrency.php
<?php
class My_View_Helper_FormatCurrency extends Zend_View_Helper_Abstract {
public function formatCurrency($value, $symbol='¥')
{
$output = $value;
$value = trim($value);
if (is_numeric($value)) {
if ($value >= 0) {
$output = $symbol . number_format($value, 2);
} else {
$output = '-' . $symbol . number_format(abs($value), 2);
}
} else {
$output = '###値が無効です###';
}
return $output;
}
}
表示結果は
となる。はきだされたHTMLソースを見ると
Main layout
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<title>Main layout</title>
</head>
<body id="index">
<div id="page-container">
<h1 id="title">Main layout</h1>
<span><div>
<ul>
<li>###値が無効です###</li>
<li>12800</li>
<li>€12,800.00</li>
<li><input type="text" name="'&<>" id="myID" value="hoge@example.com'&<>" size="35" /></li>
<li><input type="text" name="'&<>" id=""anotherID"" value="hoge@example.com'&<>" size="22" /></li>
<li>'&<></li>
</ul>
</div></span>
</div>
</body>
</html>
である。formTextに入れた値のシングルクォートがエスケープされていない。
調べてみると
<li tal:content="name">this is email</li>
というPHPTALの記述はコンパイルされて
<li><?php echo phptal_escape($ctx->name, ENT_QUOTES, 'UTF-8') ?></li>
と展開され、
<li tal:content="structure helper:formText(name, defaulttext, attributes)">this is email</li>
は
<li><?php echo $ctx->this->formText($ctx->name, $ctx->defaulttext, $ctx->attributes) ?></li>
となっていた。formTextはZF純正のビューヘルパーで、エスケープ処理には内部的にhtmlspecialcharsを使っているのだが、第2と第3パラメータは明示的に指定する必要があるのである。そこで
library/My/PHPTAL/Escape.php
<?php
class My_PHPTAL_Escape {
public static function safeEscape ($var) {
return htmlspecialchars($var, ENT_QUOTES, mb_internal_encoding());
}
}
というクラスを作り、ZFでのエスケープ処理の際は常にこれを呼ぶようにする。
library/My/Controller/Plugin/ViewSetup.php
~~~
$view = new My_PHPTAL_ZFView();
//set 2nd and 3rd parameters to escape method
$view->setEscape(array('My_PHPTAL_Escape', 'safeEscape')); // <== Added
~~~
$phptal = new PHPTAL;
$view->setEngine($phptal);
$phptal->setPhpCodeDestination($root . '/temp')
->setEncoding(mb_internal_encoding()) // <== Added
->setForceReparse(true)
->setPreFilter(new My_PHPTAL_Filter_ZFSyntax());
改めて出力してみると
<li>###値が無効です###</li>
<li>12800</li>
<li>€12,800.00</li>
<li><input type="text" name="'&<>" id="myID" value="hoge@example.com'&<>" size="35" /></li>
<li><input type="text" name="'&<>" id=""anotherID"" value="hoge@example.com'&<>" size="22" /></li>
<li>'&<></li>
となりinput中のシングルクォートもエスケープされるようになった。 ということでPHPTALの機能のごく一部を触っただけだがこれはなかなかいけそうな雰囲気である。PHPアプリケーション構築でテンプレートエンジンを利用してデザイン部分を分離する必要がある場合、PHPTALはSmartyに代わる有力な候補になるのではないだろうか。
- Comments: 4
- Trackbacks: 0
Zend Framework Quick StartにDoctrineを適用してみる – その2
- 2009-06-14 (日)
- 技術・開発ツール
前回のエントリのコードはDoctrine関連の記述が煩雑だった。せっかくZend_Applicationが出てきたのだからそれを利用したスマートな記述ができるのではと思っていたら、すでに先人がいた。
Doctrine ORM and Zend Framework « Danceric
多忙のため更新が滞っている元ネタの作者に代わりZF1.8を適用した形で書き直したというエントリである。内容はコードを見てもらえばわかるとして、doctrine-cliの中の
$application->bootstrap('doctrine')
は
$application->getBootstrap()->bootstrap('doctrine');
の間違いだと思う。(前者だとBootstrapの中の_init*メソッドがすべて実行されてしまうので。)
それから同じくdoctrine-cliの中で
define('APPLICATION_ENV', 'development');
とあるが、この値は外部に出してindex.phpとdoctrine-cliとで共用すべきだろう。preparation.phpの中にまとめてあるのでそれをインクルードするようにした。
doctrine-cli
#!/usr/bin/env php
<?php
/**
* Doctrine CLI script
*/
//Preparation
require_once '../library/preparation.php';
//Doctrine Bootstrap
$application->getBootstrap()->bootstrap('doctrine');
$cli = new Doctrine_Cli($application->getOption('doctrine'));
$cli->run($_SERVER['argv']);
- Comments: 0
- Trackbacks: 0
Zend Framework Quick StartにDoctrineを適用してみる
- 2009-06-12 (金)
- 技術・開発ツール
前回のエントリに基づき、ZF本家のクイックスタートにあるチュートリアルのコードをDoctrineを使って書き直してみた。Zend_Dbを使う場合に一番面倒なモデルのコードの記述が簡単なYAMLファイルを一つ書くだけで済み、コントローラの修正も最低限で済んだ。
新旧の概要を比較を見てみると
オリジナルのディレクトリ構造
Doctrine適用後のディレクトリ構造
- モデルの生成をDoctrinに任せた結果、チュートリアル中の”Create a Model and Database Table”に関わる部分は不要になる。つまりオリジナルの”models/”以下のコードはまるまるDoctrineが生成するコードで置き換わる。
- MySQLを使用するのでdata/に入っていたSQLiteのテーブルを削除。代わりにDoctrineを使用する際に必要になる4つのディレクトリを置く。schema.sqlはDoctrineが生成したものである。
- library/の配下にDoctrine本体(1.1.1)と自分でカスタマイズしたプログラムを入れる。本来はプラグインとして定義すればスマートなのだろうが今回は省略。また環境設定関連の情報はWebアプリとDoctrineのコマンドラインツールの両方から参照される必要があるので、preparation.phpとして外に出しここに配置。
- scripts/の配下はDoctrineのコマンドラインツールを動かすためのdoctrine-cli.phpに置き換える。
- あらかじめZendFrameworkにパスが通っていることが前提。もちろんlibrary/の下にZFを置いてもいい。自分が使用したバージョンは1.8.2。
- 余談だが、library/の下に複数のライブラリを置く場合にどうやって配置するかという問題を感じた。正攻法で行けば各ライブラリごとにサブディレクトリを切ってその中に配置することで明示的に分けるべきだろう。ただしそうなると新たにライブラリを追加するたびにinclude_pathのメンテも行わなければならなくなり面倒だ。一方フラットに配置したとするとlibrary/にさえパスが通っていればあとはAutoloader任せでOKということになる。ただしファイル名の衝突が生じたときにやっかいなことになるだろう。
public/index.phpとscripts/doctrine-cli.phpの両方から呼び出されるpreparation.phpの内容はこんな感じだ。
preparation.php
<?php
// Define path to application directory
defined('APPLICATION_PATH')
|| define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../application'));
// Define application environment
defined('APPLICATION_ENV')
|| define('APPLICATION_ENV', (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : 'production'));
// Set include_path
set_include_path(implode(PATH_SEPARATOR, array(
realpath(APPLICATION_PATH . '/../library'),
realpath(APPLICATION_PATH . '/../library/doctrine'),
realpath(APPLICATION_PATH . '/models'),
realpath(APPLICATION_PATH . '/models/generated'),
get_include_path()
)));
require_once 'Zend/Loader/Autoloader.php';
$autoloader = Zend_Loader_Autoloader::getInstance();
$autoloader->setFallbackAutoloader(true);
// Create application
$application = new Zend_Application(
APPLICATION_ENV,
APPLICATION_PATH . '/configs/application.ini'
);
$application_ini = new Zend_Config_Ini(APPLICATION_PATH . '/configs/application.ini', null);
Zend_Registry::set('application_ini', $application_ini);
$connection = My_Db_Doctrine::connect($application_ini );
Zend_Registry::set('doctrine_config', array(
'data_fixtures_path' => APPLICATION_PATH . '/../data/fixtures/',
'migrations_path' => APPLICATION_PATH . '/../data/migrations/',
'sql_path' => APPLICATION_PATH . '/../data/sql/',
'yaml_schema_path' => APPLICATION_PATH . '/../data/schema/',
'models_path' => APPLICATION_PATH . '/models/'
));
Doctrineとの接続を行うクラスであるMy_Db_Doctrineも定義する
library/My/Db/Doctrine.php
<?php
class My_Db_Doctrine
{
public static function connect($application_ini){
$data = $application_ini->con_database;
$dsn = sprintf(
'%s://%s:%s@%s/%s',
strtolower($data->type),
$data->username,
$data->password,
$data->host,
$data->dbname
);
$db = Doctrine_Manager::connection($dsn);
return $db;
}
}
今回モデル関連で作成する必要のある唯一のファイルがschema.ymlである。
schema.yml
---
Guestbook:
tableName: guestbook
columns:
id:
primary: true
autoincrement: true
type: integer(10)
email:
type: string(32)
notnull: true
email: true
comment: string(255)
created: timestamp
オリジナルのスキーマにはemailフィールドにデフォルト値を定義しているが今回は省略。また初期データの投入のしかけもスキップした。
今回はSQLiteではなくMySQLを使用するのでapplication.iniファイルに以下のセクションを追記する。
[con_database] name = "db" type = "mysql" dbname = "zfdev" host = "localhost" username = "zfdev" password = "zfdev" charset = "utf8"
scripts/doctrine-cli.phpは実行スクリプトではなくただのphpファイルにした。こうすればWindowsからも使えるはずだ。
doctrine-cli.php
<?php
//Preparation
require_once '../library/preparation.php';
$cli = new Doctrine_Cli(Zend_Registry::get('doctrine_config'));
if (isset($_GET['cmd'])) {
$_SERVER['argv'] = array('doctrine-cli.php', $_GET['cmd']);
}
$cli->run($_SERVER['argv']);
scripts/の中から
php doctrine-cli.php
と実行するとDoctrineのオプション一覧が出てくる。
正しくコマンドラインツールが動いているのが確認できたので今度は
php doctrine-cli.php build-all
を実行。DoctrineがMySQL内にテーブルを作り、同時にapplication/modelsの配下にモデルが生成される。DDL文は別途
php doctrine-cli.php generate-sql
とすることでdata/sqlの配下に生成されるがアプリの動作そのものには必須ではない。
コントローラについて修正が必要なのはapplication/controllers/GuestbookController.phpだけだ。
application/controllers/GuestbookController.php
<?php
class GuestbookController extends Zend_Controller_Action
{
public function init()
{
}
public function indexAction()
{
$guestbook = Doctrine_Query::create()
-> from('Guestbook g')
-> orderBy('g.created DESC')
-> execute();
$this->view->entries = $guestbook;
}
public function signAction()
{
$request = $this->getRequest();
$form = new Default_Form_Guestbook();
if ($this->getRequest()->isPost()) {
if ($form->isValid($request->getPost())) {
$guestbook = new Guestbook();
$guestbook->fromArray($form->getValues(true));
$guestbook->created = new Doctrine_Expression('now()');
$guestbook->save();
return $this->_helper->redirector('index');
}
}
$this->view->form = $form;
}
}
元のコードは、indexAction()が
public function indexAction()
{
$guestbook = new Default_Model_Guestbook();
$this->view->entries = $guestbook->fetchAll();
}
で、signAction()が
public function signAction()
{
$request = $this->getRequest();
$form = new Default_Form_Guestbook();
if ($this->getRequest()->isPost()) {
if ($form->isValid($request->getPost())) {
$model = new Default_Model_Guestbook($form->getValues());
$model->save();
return $this->_helper->redirector('index');
}
}
である。非常に単純なアプリケーションとはいえ拍子抜けするほど簡単に変更できた。(当然ながらビューはまったく変更していない。)
Doctrineを使うと参照整合性制約やモデル同士の一対一/一対多/多対多といった関係、さらにモデル同士の継承でさえも簡単に定義できてしまうという。 もちろんDoctrineという一種のフレームワークを覚えなければならないからその分のコストはかかるしDoctrine独自の制約といった難し さもあるのだろうが、それらを補ってあまりある恩恵が得られると思う。これはトライする価値ありだ。
- Comments: 0
- Trackbacks: 1
Zend FrameworkとDoctrineを組み合わせる
- 2009-06-09 (火)
- 技術・開発ツール
現在の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>
- Comments: 0
- Trackbacks: 1
Home > Tags > Zend Framework







