Tags: 技術日記

技術日記
Zend Frameworkクイックスタート モデルとデータベーステーブルの作成Edit

モデル周りの標準的な扱いに関しての説明は、この文書がもっとも詳しい資料になりそうなんで、これも翻訳しておく。 Zend Frameworkクイックスタート モデルとデータベーステーブルの作成の原文はこちら

モデルとデータベーステーブルの作成

作業を始める前に、これから作成されるクラスはどこに配置され、どうやってその場所を見つけられるようにするのかについて、考えておこう。デフォルトのプロジェクトではオートローダーが生成される。そこに通常とは*1異なるクラスに対応するための別のオートローダーを追加することができる。普通は、application/のようなディレクトリツリーの中に、共通のプレフィックス持つようなさまざまなMVCクラスをまとめておきたい。 Zend_Controller_Frontは、独立したミニアプリケーションである“モジュール”という概念を持っている。モジュールは、zfコマンドでapplication/ディレクトリ以下に生成されるディレクトリ構造を真似ており、その中のクラスはすべてモジュール名を使った共通のプレフィックスを持つ。application/自体も"default"モジュールである。その考え方に基づいて、このディレクトリ内のリソースには、"Default"というプレフィックスがついているものとして、オートロードできるように設定しよう。そのために、もう一つブートストラップリソースを作成する。 Zend_Application_Module_Autoloaderは、モジュール内のさまざまなリソースを適切なディレクトリにマッピングする機能や、標準的な名前解決メカニズムを提供する。ブートストラップでZend_Application_Module_Autoloaderリソースを生成することによって、その機能を有効にする。具体的には以下のように書く。
// application/Bootstrap.php
// Bootstrapクラスにこのメソッドを追加する
protected function _initAutoload()
{
$autoloader = new Zend_Application_Module_Autoloader(array(
'namespace' => 'Default_',
'basePath'  => dirname(__FILE__),
));
return $autoloader;
}
最終的には、ブートストラップクラスは以下のようになる。
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initAutoload()
{
$autoloader = new Zend_Application_Module_Autoloader(array(
'namespace' => 'Default',
'basePath'  => dirname(__FILE__),
));
return $autoloader;
}
protected function _initDoctype()
{
$this->bootstrap('view');
$view = $this->getResource('view');
$view->doctype('XHTML1_STRICT');
}
}
さて、ゲストブックとしてどういうものを作るのか考えてみよう。一般的には、「コメント」「投稿日時」、多くの場合は「メールアドレス」などの情報を持つ投稿のリストというのが、シンプルな仕様だろう。その情報をデータベースに保存することを考えると、各投稿には一意のIDも必要になる。投稿を保存したり、投稿を一つ一つ取得したり、あるいはすべての投稿をまとめて取得したりといった機能も欲しい。そのようなシンプルなゲストブックモデルのAPIは、以下のようになるだろう。
// application/models/Guestbook.php
class Default_Model_Guestbook
{
protected $_comment;
protected $_created;
protected $_email;
protected $_id;
public function __set($name, $value);
public function __get($name);
public function setComment($text);
public function getComment();
public function setEmail($email);
public function getEmail();
public function setCreated($ts);
public function getCreated();
public function setId($id);
public function getId();
public function save();
public function find($id);
public function fetchAll();
}
__get()や__set()メソッドは、投稿オブジェクトの各プロパティにアクセスする便利な手段であり、ゲッターやセッターメソッドのプロキシーとなる。これにより、ホワイトリストで指定されたプロパティのみにアクセスを制限することも可能になる。 find()やfetchAll()メソッドは、単一もしくはすべての投稿データを取得する手段となる。 続いて、データベースのセットアップについて考えていこう。 まず、Dbリソースを初期化する必要がある。LayoutやViewリソース同様に、Dbリソース用の設定を行う。application/configs/application.iniファイルの適切な場所に、以下のような行を追加する。
; application/configs/application.ini
; Add these lines to the appropriate sections:
[production]
resources.db.adapter       = "PDO_SQLITE"
resources.db.params.dbname = APPLICATION_PATH "/../data/db/guestbook.db"
[testing : production]
resources.db.params.dbname = APPLICATION_PATH "/../data/db/guestbook-testing.db"
[development : production]
resources.db.params.dbname = APPLICATION_PATH "/../data/db/guestbook-dev.db"
最終的な設定ファイルは以下のようになる。
; application/configs/application.ini
[production]
phpSettings.display_startup_errors = 0
phpSettings.display_errors = 0
bootstrap.path = APPLICATION_PATH "/Bootstrap.php"
bootstrap.class = "Bootstrap"
resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"
resources.layout.layoutPath = APPLICATION_PATH "/layouts/scripts"
resources.view[] =
resources.db.adapter = "PDO_SQLITE"
resources.db.params.dbname = APPLICATION_PATH "/../data/db/guestbook.db"
[staging : production]
[testing : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1
resources.db.params.dbname = APPLICATION_PATH "/../data/db/guestbook-testing.db"
[development : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1
resources.db.params.dbname = APPLICATION_PATH "/../data/db/guestbook-dev.db"
データベースはdata/db/ディレクトリ以下に保存される。ディレクトリを生成し、誰でも書き込み可能なように設定しておこう。UNIXライクなシステムでは以下のようにする。
% mkdir -p data/db; chmod -R a+rwX data
Windowsでは、エクスプローラでディレクトリを作成し、ディレクトリに誰でも書き込めるような権限をセットする。 これでデータベース接続の準備ができた。今回は、application/data/ディレクトリ内に置かれたSQLiteデータベースを利用する。それでは、ゲストブックへの投稿を管理するシンプルなテーブルを設計していこう。
-- scripts/schema.sqlite.sql
--
-- このSQLを使ってデータベーススキーマをロードする
CREATE TABLE guestbook (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
email VARCHAR(32) NOT NULL DEFAULT 'noemail@test.com',
comment TEXT NULL,
created DATETIME NOT NULL
);
CREATE INDEX "id" ON "guestbook" ("id");
また、すぐに使えるようなデータがあったほうがアプリケーションが面白くなるので、いくつかの情報を追加しておこう。
-- scripts/data.sqlite.sql
--
-- 以下のSQL文でデータベースに値を投入する
INSERT INTO guestbook (email, comment, created) VALUES
('ralph.schindler@zend.com',
'Hello! Hope you enjoy this sample zf application!',
DATETIME('NOW'));
INSERT INTO guestbook (email, comment, created) VALUES
('foo@bar.com',
'Baz baz baz, baz baz Baz baz baz - baz baz baz.',
DATETIME('NOW'));
これで、スキーマとサンプルデータの両方がそろった。データベースを構築するためのスクリプトを用意してみよう。普通こういうものは製品には不要なのだが、こういうスクリプトがあると、開発者がデータベース環境をローカルに構築して、そこでアプリケーションを動かすのが楽になる。以下のようなscripts/load.sqlite.phpスクリプトを作成する。
// scripts/load.sqlite.php
/**
* データベースの生成とロードのためのスクリプト
*/
// スクリプト名の後ろにどんなパラメータが渡されても(1や--withdataなど)
// スキーマがロードされた後にデータファイルをロードする。
$withData = false;
if (isset($_SERVER['argv'][1])) {
$withData = true;
} elseif (defined('APPLICATION_LOAD_TESTDATA')) {
$withData = true;
}
// アプリケーションを初期化し、データベースアダプターをブートストラップする
defined('APPLICATION_PATH')
|| define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../application'));
defined('APPLICATION_ENV')
|| define('APPLICATION_ENV', 'development');
require_once 'Zend/Application.php';
$application = new Zend_Application(
APPLICATION_ENV,
APPLICATION_PATH . '/configs/application.ini'
);
$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);
}
// このブロックでスキーマファイルから読み込んだ実際のSQL文を
// 実行する
try {
$schemaSql = file_get_contents(dirname(__FILE__) . '/schema.sqlite.sql');
// 読み込んだSQLを直接DBコネクションに渡す
$dbAdapter->getConnection()->exec($schemaSql);
if ('testing' != APPLICATION_ENV) {
echo PHP_EOL;
echo 'Database Created';
echo PHP_EOL;
}
if ($withData) {
$dataSql = file_get_contents(dirname(__FILE__) . '/data.sqlite.sql');
// 読み込んだSQLを直接DBコネクションに渡す
$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;
}
// 通常このスクリプトはコマンドラインから実行される
return true;
さてこのスクリプトを実行しよう。ターミナルやDOSコマンドラインから以下のように実行する。
% php scripts/load.sqlite.php
以下のような出力結果となるだろう。
path/to/ZendFrameworkQuickstart/scripts$ php load.sqlite.php --withdata
Writing Database Guestbook in (control-c to cancel):
1
Database Created
これでゲストブックアプリケーションのための、完全に動作するデータベースとテーブルの準備ができた。後は、アプリケーションのコードを書いていくだけだ。具体的には、データソース(今回はZend_Db_Tableを利用する)、データソースとドメインモデルを接続するデータマッパーなどの構築だ。最終的には、既存投稿の表示や新規投稿処理などをモデルを介して行うコントローラも作成する。 データソースに接続するためにTable Data Gatewayパターンを使う。Zend_Db_Tableがこの機能を担当する。まずはZend_Db_Tableを継承したテーブルクラスを作成しよう。application/models/DbTableディレクトリを作成し、以下のような内容を持つGuestbook.phpファイルを作成する。
// application/models/DbTable/Guestbook.php
/**
* guestbookテーブル用のDbTableクラス
*/
class Default_Model_DbTable_Guestbook extends Zend_Db_Table_Abstract
{
/** Table name */
protected $_name    = 'guestbook';
}
クラスのプレフィックスはDefault_Model_DbTableだ。オートローダーから与えられるプレフィックス"Default"が最初のセグメントとなり、その後ろにコンポーネントのプレフィックス"Model_DbTable"が続く。後者はmodels/DbTableディレクトリにマッピングされる。 Zend_Db_Tableクラスを継承する場合、テーブル名と、オプションとしてプライマリーキー名(もしも"id"でない場合)が必要となる。*2 続いてData Mapperを作る。Data Mapperはドメインオブジェクトをデータベースにマッピングする。今回の場合、Default_Model_GuestbookモデルをDefault_Model_DbTable_Guestbookデータソースにマッピングする。典型的なデータマッパーのAPIは以下のようになる。
// application/models/GuestbookMapper.php
class Default_Model_GuestbookMapper
{
public function save($model);
public function find($id, $model);
public function fetchAll();
}
これらのメソッドに加え、Table Data Gatewayを設定・取得するためのメソッドも追加する。最終的にapplication/models/GuestbookMapper.phpには以下のようなクラスが置かれる。
// application/models/GuestbookMapper.php
class Default_Model_GuestbookMapper
{
protected $_dbTable;
public function setDbTable($dbTable)
{
if (is_string($dbTable)) {
$dbTable = new $dbTable();
}
if (!$dbTable instanceof Zend_Db_Table_Abstract) {
throw new Exception('Invalid table data gateway provided');
}
$this->_dbTable = $dbTable;
return $this;
}
public function getDbTable()
{
if (null === $this->_dbTable) {
$this->setDbTable('Default_Model_DbTable_Guestbook');
}
return $this->_dbTable;
}
public function save(Default_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, Default_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 Default_Model_Guestbook();
$entry->setId($row->id)
->setEmail($row->email)
->setComment($row->comment)
->setCreated($row->created)
->setMapper($this);
$entries[] = $entry;
}
return $entries;
}
}
データマッパーが用意できたので、モデルクラスをデータマッパーに対応させよう。データマッパーがデータソースを参照し、モデルクラスがデータマッパーを参照するようにすればいい。さらには、コンストラクタやsetOptions()メソッドに配列形式でデータを渡すことによって、モデルに簡単に値をセットできるようにしておく。最終的なapplication/models/Guestbook.phpに置かれるモデルクラスは以下のようになる。
// application/models/Guestbook.php
class Default_Model_Guestbook
{
protected $_comment;
protected $_created;
protected $_email;
protected $_id;
protected $_mapper;
public function __construct(array $options = null)
{
if (is_array($options)) {
$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;
}
public function setMapper($mapper)
{
$this->_mapper = $mapper;
return $this;
}
public function getMapper()
{
if (null === $this->_mapper) {
$this->setMapper(new Default_Model_GuestbookMapper());
}
return $this->_mapper;
}
public function save()
{
$this->getMapper()->save($this);
}
public function find($id)
{
$this->getMapper()->find($id, $this);
return $this;
}
public function fetchAll()
{
return $this->getMapper()->fetchAll();
}
}
最後に、今まで作ってきたクラス群を使って、データベースに保存されている投稿をリスト化するゲストブックコントローラーを作成しよう。*3 新しいコントローラを作成するには、ターミナルもしくはDOSコンソールでプロジェクトディレクトリに移動して、以下のようなコマンドを実行する。
# Unix-like systems:
% zf.sh create controller guestbook
# DOS/Windows:
C:> zf.bat create controller guestbook
このコマンドは、application/controllers/GuestbookController.phpに、indexAction()というアクションをもつGuestbookControllerコントローラーを作成する。また、application/views/scripts/guestbook/というビュースクリプト用のディレクトリとindexアクション用のビュースクリプトも作成される。 "index"アクションをゲストブックのすべての投稿を表示するページ用のアクションとしよう。 それでは、基本的なアプリケーションロジックを実装していこう。indexActionが呼ばれると、すべてのゲストブックへの投稿を表示する。そのコードは以下のようになる。
// application/controllers/GuestbookController.php
class GuestbookController extends Zend_Controller_Action
{
public function indexAction()
{
$guestbook = new Default_Model_Guestbook();
$this->view->entries = $guestbook->fetchAll();
}
}
もちろん対応するビュースクリプトも必要だ。application/views/scripts/guestbook/index.phtmlを以下のように編集しよう。
<!-- application/views/scripts/guestbook/index.phtml -->
<p><a href="<?php echo $this->url(
array(
'controller' => 'guestbook',
'action'     => 'sign'
),
'default',
true) ?>">Sign Our Guestbook</a></p>
Guestbook Entries: <br />
<dl>
<?php foreach ($this->entries as $entry): ?>
<dt><?php echo $this->escape($entry->email) ?></dt>
<dd><?php echo $this->escape($entry->comment) ?></dd>
<?php endforeach ?>
</dl>

*1:ディレクトリ配置や命名規則が

*2:Zend_Db_Tableはテーブル定義からプライマリキー名を推測するから、"id"じゃない場合もコードで指定する必要はないんじゃ? ここでの「オプションとして」はそういう意味まで含んでいるんじゃないよね

*3:意味がよくわからないな。bothがどこにかかるのかもよくわかってないし

Published At2009-07-02 09:00Updated At2019-12-30 23:54

技術日記
Zend FrameworkリソースオートローダーEdit

標準的なモデルの扱い方を調べていたら、Zend_Application_Module_Autoloaderなんてものを発見してしまった。Zend_Application配下ではなくZend_Loader配下のドキュメントとして書かれていたせいで見逃していた。というわけで、続いてはそこを読みながら翻訳していく。 Zend Frameworkリソースオートローダーの原文はこちら

30.3. リソースオートローダー

リソースオートローダーは、Zend Framework標準コーディング規約に基づきつつも、クラス名とディレクトリ構造が1対1で対応していないようなネームスペースを使ったライブラリコードを扱うためのものだ。その目的は、ある特定のアプリケーション向けに作られたモデル・フォーム・ACL(アクセス制御リスト)クラスのようなコードを、オートロードできるようにすることだ。 リソースオートローダーは、対象となるネームスペース設定とともに生成され、Zend_Loader_Autoloaderに登録される。これによって、特定のディレクトリとネームスペースを結びつけることが簡単にでき、それをオートロードすることもできる。
30.3.1. リソースオートローダーの使い方
以下のようなディレクトリ構造を想定して欲しい。
path/to/some/directory/
acls/
Site.php
forms/
Login.php
models/
User.php
このディレクトリのコードには、すべて"My_"というネームスペースがつけられている。その中の"acls"ディレクトリに置かれるコンポーネントは"Acl_"というプレフィックスがつけられ、最終的には"My_Acl_Site"というクラス名になる。同様に"forms"ディレクトリは"Form_"に対応し、"My_Form_Login"クラスとなる。"models"ディレクトリではコンポーネント用のネームスペースは使われず、"My_User"クラスとなる。 このようなクラスをオートロードするのに、リソースオートローダーが使える。リソースオートローダーを生成する際に、最低限リソースのベースとなるパスおよびネームスペースの情報を渡す必要がある。つまり、以下のようになる。
$resourceLoader = new Zend_Loader_Autoloader_Resource(array(
'basePath'  => 'path/to/some/directory',
'namespace' => 'My',
));
ベースネームスペースについて
Zend_Loader_Autoloaderでは、ネームスペースの後ろにアンダースコアがつけられていることが期待されている。Zend_Loader_Autoloader_Resourceは、ネームスペース、コンポーネント、クラス名の間がアンダースコアでつなげられるようなコードがオートロードされることを前提としている。だから、リソースオートローダーに登録する際には(ベースネームスペースに)アンダースコアは必要はない。

ベースとなるリソースオートローダーの準備はできたので、続いてはaddResourceType()メソッドを使って、いくつかオートロードするコンポーネントを追加してみよう。このメソッドは3つのパラメータを持つ。内部での参照名となる"リソース種別(type)"。ベースパス配下で実際にリソースが置かれる"サブディレクトリパス"。ベースネームスペースの後ろに追加される"コンポーネントネームスペース"。それでは独自のリソース種別を追加してみよう。

$resourceLoader->addResourceType('acl', 'acls/', 'Acl')
->addResourceType('form', 'forms/', 'Form')
->addResourceType('model', 'models/');
このように書く代わりに、addResourceTypes()を使って配列を渡すこともできる。上記に相当するコードは以下になる。
$resourceLoader->addResourceTypes(array(
'acl' => array(
'path'      => 'acls/',
'namespace' => 'Acl',
),
'form' => array(
'path'      => 'forms/',
'namespace' => 'Form',
),
'model' => array(
'path'      => 'models/',
),
));
最終的に、(ZendLoaderAutoloaderResource)オブジェクトを生成する際のオプションで"resourceTypes"キーとして指定することによって、これらすべての設定を指定してしまうこともできる。具体的には以下のような(オプション配列の)構造となる。
$resourceLoader = new ZendLoaderAutoloaderResource(array(
'basePath'      => 'path/to/some/directory',
'namespace'     => 'My',
'resourceTypes' => array(
'acl' => array(
'path'      => 'acls/',
'namespace' => 'Acl',
),
'form' => array(
'path'      => 'forms/',
'namespace' => 'Form',
),
'model' => array(
'path'      => 'models/',
),
),
));
30.3.2. モジュールリソースオートローダー
Zend Frameworkには、そのMVCアプリケーションで推奨するディレクトリ構造があり、それに対応するマッピング設定を持つZendLoaderAutoloaderResourceクラスの具体的な実装が同梱されている。その具体的な実装であるZendApplicationModuleAutoloaderというローダーは、以下のようなマッピング情報を持っている。
api/         => Api
forms/       => Form
models/      => Model
DbTable/ => ModelDbTable
plugins/     => Plugin
たとえば、"Blog"というプレフィックスを持つモジュールがあり、そこで"BlogFormEntry"クラスが使いたい場合、"forms/"ディレクトリ内に"Entry.php"というファイルが置かれることになる。

ZendApplicationをモジュールブートストラップと一緒に使う場合、ZendApplicationModuleAutoloaderがそれぞれのモジュールごとに生成され、モジュールリソースをオートロードできるようにしてくれる。

30.3.3. リソースオートローダーをオブジェクトファクトリーとして使う場合1
30.3.3. リソースオートローダーリファレンス2

1:$loader->load($resource, $type)の使い方だろうな。オブジェクトを使い回せるファクトリーになるみたいだ

2:新しいクラスはマニュアル内にAPIリファレンスを置いている場合が多いな

Published At2009-07-01 09:00Updated At2019-12-30 23:54

技術日記
[Zend Framework] Zend_Applicationの利用例Edit

Zend_Applicationの利用例の原文。

4.4. 利用例

Bootstrapクラスは最小限のコードとなることが多く、しばしばブートストラップ基底クラスを継承するだけの空のスタブとなる。
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
}
(上記クラスと)対応する設定ファイルは以下。
; APPLICATION_PATH/configs/application.ini
[production]
bootstrap.path = APPLICATION_PATH "/Bootstrap.php"
bootstrap.class = "Bootstrap"
resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"
[development : testing]
[development : production]
しかし、カスタムの初期化コードが必要になることもあるだろう。その場合は二つの選択肢がある。一つは、_initから始まるメソッドとしてブートストラップコードを記述する方法。これらのメソッドはbootstrap()メソッドによって、まるでbootstrap<resource>()というpublicメソッドのように呼ばれ、配列を使った追加オプションを受け取る。 リソースメソッドが返した値は、ブートストラップ内のコンテナに保存される。これはリソース同士が相互連携する場合に便利だ(あるリソースオブジェクトが自分自身を他のリソースオブジェクトに注入(インジェクション)したり)。getResource()メソッドはそれらの(戻り値が保存された)値を取得するために使われる。 以下は、リクエストオブジェクトを(独自に)初期化するためのリソースメソッドの例だ。依存性の追跡機構(フロントコントローラリソースへの依存)、ブートストラップからのリソースオブジェクトの取得、ブートストラップへの値(リソースオブジェクト)の保存を行う例となる。
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initRequest(array $options = array())
{
// フロントコントローラオブジェクトの存在を保証し、それを取得する
$this->bootstrap('FrontController');
$front = $this->getResource('FrontController');
// リクエストオブジェクトの初期化
$request = new Zend_Controller_Request_Http();
$request->setBaseUrl('/foo');
// フロントコントローラにセット(注入)
$front->setRequest($request);
// ブートストラップはコンテナに'request'キーの値として保存する
return $request;
}
}
この例におけるbootstrap()メソッドの('FrontController'を引数にした)呼び出しは、_initRequest()メソッドが実行されるよりも先にフロントコントローラリソースの初期化が行われていることを保証する。あの呼び出し(=$this->bootstrap('FrontController');)がリソース*1もしくはクラス内の他のメソッド*2の呼び出しのトリガーとなる。 他のオプションはリソースプラグインで使われるためのものだ。リソースプラグインは指定された初期化処理を実行するオブジェクトで、初期化方法は以下のタイミングで指定される。
  • Zend_Applicationオブジェクトが生成されるとき*3
  • ブートストラップオブジェクトの初期化処理中*4
  • ブートストラップオブジェクトへのメソッドコールを介して明示的に*5
リソースプラグインはZend_Application_Bootstrap_Resourceインターフェースを実装しており、呼び出し元(ブートストラップオブジェクト)の注入、オプションの設定、init()メソッドを持つように定義されている。たとえばカスタムの"View"ブートストラップリソースは以下のようになる。
class My_Bootstrap_Resource_View
extends Zend_Application_Resource_ResourceAbstract
{
public function init()
{
$view = new Zend_View($this->getOptions());
Zend_Dojo::enableView($view);
$view->doctype('XHTML1_STRICT');
$view->headTitle()->setSeparator(' - ')->append('My Site');
$view->headMeta()->appendHttpEquiv('Content-Type',
'text/html; charset=utf-8');
$view->dojo()->setDjConfigOption('parseOnLoad', true)
->setLocalPath('/js/dojo/dojo.js')
->registerModulePath('../spindle', 'spindle')
->addStylesheetModule('spindle.themes.spindle')
->requireModule('spindle.main')
->disable();
$viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper(
'ViewRenderer'
);
$viewRenderer->setView($view);
return $view;
}
}
ブートストラップでこのリソースを使えるようにするには、リソースの(完全な)クラス名、もしくはプラグインローダー用の検索パスとリソースの短縮名(たとえば"view")の組み合わせを与える必要がある。
$application = new Zend_Application(
APPLICATION_ENV,
array(
'resources' => array(
'My_Bootstrap_Resource_View' => array(), // 完全クラス名もしくは
'view' => array(),                       // 短縮名
'FrontController' => array(
'controllerDirectory' => APPLICATION_PATH . '/controllers',
),
),
// 短縮名を使うにはプラグインのパスを定義しておく
'pluginPaths = array(
'My_Bootstrap_Resource' => 'My/Bootstrap/Resource',
)
)
);
リソースは呼び出し元となるブートストラップにアクセスすることで、他のリソースや初期化処理を呼び出すことができる。
class My_Bootstrap_Resource_Layout
extends Zend_Application_Resource_ResourceAbstract
{
public function init()
{
// ビューが初期化されていることを保証する
$this->getBootstrap()->bootstrap('view');
// ビューオブジェクトを取得する
$view = $this->getBootstrap()->getResource('view');
// ...
}
}
通常の使い方では、アプリケーションオブジェクトを生成し、ブートストラップし、実行する。
$application = new Zend_Application(...);
$application->bootstrap()
->run();
(通常のWebアプリケーション以外の)カスタムスクリプトを作る場合は、特定のリソースだけを初期化する必要があるかもしれない。*6
$application = new Zend_Application(...);
$application->getBootstrap()->bootstrap('db');
$service = new Zend_XmlRpc_Server();
$service->setClass('Foo');  // データベースを利用する
echo $service->handle();
内部メソッドやリソースを呼ぶためにbootstrap()メソッドを使う代わりに、メソッドオーバーロード(__call())を利用することもできる。*7
$application = new Zend_Application(...);
$application->getBootstrap()->bootstrapDb();

*1:=リソースプラグイン?

*2:=リソースメソッド?

*3:Zend_Applicationのコンストラクタの引数として渡されるオプション

*4:ブートストラップのコンストラクタの引数として渡されるZend_Applicationオブジェクトの持つオプション

*5:BootstrapAbstract::setOptions()やBootstrapAbstract::registerPluginResource()

*6:下記では、通常のMVC Webアプリケーションを実行する代わりに、DBリソースの初期化処理だけを利用して、XMLRPC Webサービスを実行している

*7:$boostrap->boostrap('foo')は$bootstrap->boostrapFoo()と書くこともできる。

Published At2009-06-26 09:00Updated At2019-12-30 23:55

技術日記
Zend Frameworkのお勉強Edit

さて、それではそろそろプロトタイプでも作り始めるかと思ったのだが、Zend Frameworkがいつの間にか1.8.5までアップデートされていた。特に1.8以降ではZend_ApplicationやZend_Toolなどが追加されており、ディレクトリ構成や起動ファイル、設定ファイルの書き方の作法が従来から変更(というか、新しく策定)されている。そのため、私の古い知識ではその部分をきちんと調べないと、プロトタイプを書き始めることすらできない。 というわけで、プロトタイプを書き始まる前に、Zend Framework 1.8以降で新しく策定されたZend Frameworkアプリケーションの標準的な書き方というものについて調べていく。まだZend_Applicationのマニュアルの日本語訳が一部しかないみたいなんで、翻訳しながら読み進めていきつつ、適当の訳した文章も参考までにここに載せておく。

Published At2009-06-24 09:00Updated At2019-12-30 23:55

技術日記
Zend_Applicationクイックスタートの翻訳Edit

Zend_Applicationクイックスタートの原文はこちら

4.2. Zend_Applicationクイックスタート

Zend_Applicationによる開発をスタートする方法は、プロジェクトの始め方によって二通りある。どちらの場合でも、Bootstrapクラスと関連する設定ファイルを作成することから始まる。 新しいプロジェクトの作成にZend_Toolを使うのならば、このまま続きをどうぞ。すでに作りかけているプロジェクトにZend_Applicationを追加したい場合は、リンク先までスキップを。
4.2.1. Zend_Toolを使う場合
Zend_Toolを使ってプロジェクトスケルトンを生成するのが、もっとも素早いZend_Applicationを使ったアプリケーション開発の始め方だ。これによりBootstrapクラス&ファイルなども生成される。 プロジェクトを生成するには、zfコマンド(*nixシステムでは)を実行する。
% zf create project newproject
Windowsではzf.batコマンドとなる。
C:> zf.bat create project newproject
どちらも以下のようなプロジェクトスケルトンを生成する。
newproject
|-- application
|   |-- Bootstrap.php
|   |-- configs
|   |   `-- application.ini
|   |-- controllers
|   |   |-- ErrorController.php
|   |   `-- IndexController.php
|   |-- models
|   `-- views
|       |-- helpers
|       `-- scripts
|           |-- error
|           |   `-- error.phtml
|           `-- index
|               `-- index.phtml
|-- library
|-- public
|   |-- .htaccess*1
|   `-- index.php
`-- tests
|-- application
|   `-- bootstrap.php
|-- library
|   `-- bootstrap.php
`-- phpunit.xml
上図の中で、起動処理(bootstrap)はnewproject/application/Bootstrap.phpに置かれ、以下のような内容となっている。
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
}
設定ファイルが、newproject/configs/applicaiton.iniとして生成されている点にも注目しよう。以下のような内容となっている。
[production]
phpSettings.display_startup_errors = 0
phpSettings.display_errors = 0
includePaths.library = APPLICATION_PATH "/../library"
bootstrap.path = APPLICATION_PATH "/Bootstrap.php"
bootstrap.class = "Bootstrap"
resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"
[staging : production]
[testing : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1
[development : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1
ここに書かれたすべての設定は、Zend_Applicationおよび起動処理で使われる。 他には、Zend_Applicationの呼び出し(invoke)・実行(dispatch)を行うnewproject/public/index.phpファイルも興味深い。
// アプリケーションディレクトリの定義
defined('APPLICATION_PATH')
|| define('APPLICATION_PATH',
realpath(dirname(__FILE__) . '/../application'));
// アプリケーション環境の定義
defined('APPLICATION_ENV')
|| define('APPLICATION_ENV',
(getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV')
: 'production'));
/** Zend_Application */
require_once 'Zend/Application.php';
// アプリケーションの生成・起動・実行
$application = new Zend_Application(
APPLICATION_ENV,
APPLICATION_PATH . '/configs/application.ini'
);
$application->bootstrap()
->run();
このままクイックスタートを続けるなら、リソースの項までスキップを。
4.2.2. Zend_Applicationを既存のアプリケーションに追加する場合
Zend_Applicationの基本はとてもシンプルだ。
  • Bootstrapクラスが書かれたapplication/Bootstrap.phpファイルを作成する。
  • Zend_Applicationに必要とされる最低限の設定が書かれた設定ファイル、application/configs/application.iniを作成する。
  • public/index.phpをZend_Applicationを利用するように書き換える。
最初に、Bootstrapクラスを作成しよう。以下のような内容のapplication/Bootstrap.phpファイルを作成する。
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
}
続いて、設定ファイルを用意する。このチュートリアルでは、INI形式の設定ファイルを利用するが、XMLやPHP形式の設定ファイルを利用してもかまわない。以下のような内容を持つ、application/configs/application.iniを作成しよう。
[production]
phpSettings.display_startup_errors = 0
phpSettings.display_errors = 0
includePaths.library = APPLICATION_PATH "/../library"*2
bootstrap.path = APPLICATION_PATH "/Bootstrap.php"
bootstrap.class = "Bootstrap"
resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"
[staging : production]
[testing : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1
[development : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1
今度は窓口となるスクリプト、public/index.phpを修正しよう。もしそのようなファイルが存在しない場合は作成し、以下のような内容に書き換えよう。*3
// アプリケーションディレクトリの定義
defined('APPLICATION_PATH')
|| define('APPLICATION_PATH',
realpath(dirname(__FILE__) . '/../application'));
// アプリケーション環境の定義
defined('APPLICATION_ENV')
|| define('APPLICATION_ENV',
(getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV')
: 'production'));
// 通常は、あなたのlibrary/ディレクトリもinclude_pathに追加したいだろう。
// 特にそこにZFがインストールされている場合などは。
set_include_path(implode(PATH_SEPARATOR, array(
dirname(dirname(__FILE__)) . '/library',
get_include_path(),
)));
/** Zend_Application */
require_once 'Zend/Application.php';
// アプリケーションの生成・起動・実行
$application = new Zend_Application(
APPLICATION_ENV,
APPLICATION_PATH . '/configs/application.ini'
);
$application->bootstrap()
->run();
アプリケーション環境定数が、環境変数"APPLICATION_ENV"から取得されていることに注目して欲しい。Webサーバーの環境変数として設定する方法を推奨している。Apacheではバーチャルホスト設定もしくは.htaccessで設定できる。public/.htaccess*4に以下のように記述することで、推奨設定を実現できる。
SetEnv APPLICATION_ENV development
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ index.php [NC,L]
mod_rewriteに関して 上記のリライトルールは、バーチャルホストのドキュメントルート以下に置かれたすべてのファイルへのアクセスを許可する。公開されたくないファイルがある場合は、よりアクセスを制限するルールが必要だ。ApacheのWebサイトでmod_rewriteについてより詳しく調べるといいだろう。

これであなたはZend_Applicationのすべての恩恵を受ける準備が整った。*5

4.2.3. リソースの追加と作成

ここまで指示通りに進めてきたなら、あなたの起動(bootstrap)クラスはフロントコントローラを使って動作するようになっているだろう。しかし、たいていの場合もうちょっと設定をいじる必要が出てくるはずだ。

このセクションでは、あなたのアプリケーションに二つのリソースを追加していく。まずレイアウトのセットアップを行い、それからビューオブジェクトのカスタマイズを行う。

"layout"リソースは、ZendApplicationが提供する標準リソースの一つだ。これを使うには、ZendLayoutインスタンスの設定を行うための設定値を指定しておく必要がある。

これを使うためには、設定ファイルを更新するだけでいい。

[production]
phpSettings.displaystartuperrors = 0
phpSettings.displayerrors = 0
bootstrap.path = APPLICATIONPATH "/Bootstrap.php"
bootstrap.class = "Bootstrap"
resources.frontController.controllerDirectory = APPLICATIONPATH "/controllers"
; 以下の行を追加
resources.layout.layout = "layout"
resources.layout.layoutPath = APPLICATIONPATH "/layouts/scripts"
[staging : production]
[testing : production]
phpSettings.displaystartuperrors = 1
phpSettings.displayerrors = 1
[development : production]
phpSettings.displaystartuperrors = 1
phpSettings.displayerrors = 1
もしまだ、application/layouts/scriptsディレクトリとその中のlayout.phtmlファイルを作っていないなら、それらを用意しよう。最初のレイアウトとしては、以下のような内容がいいだろう(そして、これは次に説明するビューリソースと連動する)。
<?php echo $this->doctype() ?>
<html>
<head>
<?php echo $this->headTitle() ?>
<?php echo $this->headLink() ?>
<?php echo $this->headStyle() ?>
<?php echo $this->headScript() ?>
</head>
<body>
<?php echo $this->layout()->content ?>
</body>
</html>
これでレイアウト機能が動くようになった。

続いて、ビューのカスタマイズ機能を追加しよう。ビューを初期化する際に、HTMLのドキュメントタイプ情報やHTMLのHEAD部分で利用するタイトルの初期値をセットしたい。これは、Bootstrapクラスにメソッドを追加することで実現できる。

class Bootstrap extends ZendApplicationBootstrapBootstrap
{
protected function _initView()
{
// ビューの初期化
$view = new ZendView();
$view->doctype('XHTML1STRICT');
$view->headTitle('My First Zend Framework Application');
// ViewRendererに追加
$viewRenderer = ZendControllerActionHelperBroker::getStaticHelper(
'ViewRenderer'
);
$viewRenderer->setView($view);
// returnすることでbootstrapに保存される
return $view;
}
}
このメソッドは、アプリケーションを起動すると自動的に実行され、アプリケーションが必要なときにはビューが確実に初期化されていることが保証される。
4.2.4. ZendApplicationを使った次のステップ
ここまでの説明で、ZendApplicationの利用を開始し、アプリケーションを起動させることができるようになっただろう。ここからはリソースメソッドや最高の再利用性のためのリソースプラグインクラスの作成を始めていく。続きを読んで、より詳しく学ぼう。

1:原文では.htaccessが書かれていないけど、あった方がいいだろう

2:「includePaths.library = APPLICATION_PATH "/../library"」みたいな記述って可能だったんだ。INIファイル内で定数が展開されるってのは知ってたけど、定数と文字列を連結したりも出来るのね。

3:「; otherwise」の接続がよくわからなかったんで適当

4:ここだけ本文では.htaccesってsが一個抜けてるな

*5:たぶんyou're all set toって言い回しがあるんだろうけど、よくわからないから雰囲気で適当に

Published At2009-06-24 09:00Updated At2019-12-30 23:55

技術日記
[Zend Framework] Zend_Application動作原理の翻訳Edit

Zend_Application動作原理の原文はこちら。

4.3. 動作原理

MVCアプリケーションを設定して動かすためには、データベース、ビューやビューヘルパー、レイアウトなどの設定や、プラグイン、アクションヘルパーの登録などなど、多くの機能を利用するためのコードがたくさん必要になる。 加えて、テストやcronタスク、サービススクリプトなどを実行するために、同じコードを再利用したいこともよくあるだろう。*1そういう処理は、ブートストラップ(起動準備処理*2)に書いてしまうのが簡単なやり方だが、環境依存の初期化処理が必要となることもあるだろう。たとえば、cronタスクにMVCはいらないし、サービススクリプトにはDBレイヤーだけあればいい。 Zend_Applicationでは、OOPによりブートストラップをカプセル化して再利用可能にすることで、そのような要望に応えようとしている。 Zend_Applicationは以下の3つの領域をサポートする。
  • Zend_Application: PHP環境設定の読み込み、インクルードパスおよび(クラスの)オートロードへの対応、対応するブートストラップのインスタンス化。
  • Zend_Application_Bootstrap: ブートストラップへのインターフェース。Zend_Application_Bootstrap_Bootstrapが、依存性チェックや必要に応じたリソースの読み込みなど、ブートストラップで必要となる機能群を提供する。
  • Zend_Application_Resource: ブートストラップによって必要に応じてロードされるリソースや、いくつかのデフォルトのリソースなど、標準的なリソースへのインターフェースを提供する。
開発者はアプリケーションのために、Zend_Application_Bootstrap_Bootstrapクラスを継承するか、あるいはZend_Application_Bootstrap_Bootstrapperインターフェースを(最低限)実装したブートストラップクラスを作成する。その呼び出し部(=public/index.php)ではZend_Applicationが読み込まれ、以下のパラメータを使ってインスタンス化される。
  • 現状の環境(定義名)
  • ブートストラップオプション群
ブートストラップオプション群は、ブートストラップクラスが書かれたファイルへのパスを含み、その他オプションとして以下のような要素を持つ。
  • 追加するインクルードパス
  • オートローダーに追加登録するネームスペース
  • 初期化に利用するphp.ini設定
  • ブートストラップクラスのクラス名(未指定ならば"Bootstrap")
  • ペアで使われるリソースのパスとプレフィックス*3
  • 利用するリソース(クラス名もしくは短縮名)
  • 追加でロードする設定ファイルのパス
  • その他の設定値
設定値はPHPの配列、Zend_Configオブジェクト、もしくは設定ファイルへのパスで表現する。
4.3.1. ブートストラップ
Zend_Applicationの二つ目の役割は、アプリケーションのブートストラップ(起動準備処理)を行うことだ。ブートストラップはZend_Application_Bootstrap_Bootstrapperインターフェースに以下のようなAPIとして定義されており、それらを最低限実装する必要がある。
interface Zend_Application_Bootstrap_Bootstrapper
{
public function __construct($application);
public function setOptions(array $options);
public function getApplication();
public function getEnvironment();
public function getClassResources();
public function getClassResourceNames();
public function bootstrap($resource = null);
public function run();
}
このAPIは、環境(定義名)と設定オプションをアプリケーションオブジェクトから受け取り、ブートストラップ時に用意する必要があるリソース群を知らせ、そしてブートストラップとアプリケーションの実行を行うためのものだ。 これらのインターフェースをスクラッチで、あるいはZend_Application_Bootstrap_BoostrapAbstractを継承して、あるいはZend_Application_Bootstrap_Bootstrapを利用して実装することができる。 これ以外にも、あなたが慣れなければならないことはたくさんある。
4.3.1.1. リソースメソッド
Zend_Application_Bootstrap_BootstrapAbstractの実装では、クラスにリソースメソッドというものを定義するようになっている。_initから始まるprotectedメソッドは、すべてリソースメソッドとして扱われる。 一つのリソースメソッドを実行するには、bootstrap()メソッドにそのリソース名を渡して呼ぶ。リソース名とは、メソッド名から_initを取り除いたものだ。 いくつかのリソースメソッドを実行したいならリソース名の配列を渡せばいい。すべてのリソースメソッドを実行する場合は引数なしで呼ぶ。 以下のブートストラップクラスを見てみよう。
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initFoo()
{
// ...
}
protected function _initBar()
{
// ...
}
protected function _initBaz()
{
// ...
}
}
_initFoo()メソッドを実行したい場合は、以下のようにする。
$bootstrap->bootstrap('foo');
_initFoo()と_initBar()メソッドを実行したい場合は、以下のようになる。
$bootstrap->bootstrap(array('foo', 'bar'));
すべてのリソースメソッドを実行する場合は、引数なしでbootstrap()を呼ぶ。
$bootstrap->bootstrap();
4.3.1.2. リソースプラグインを利用したブートストラップ
よりブートストラップの再利用性を高くしたい人のために、リソースプラグインという仕組みも用意されている。これは(コードではなく)設定を使ってシンプルにリソースを利用できるようにする。後で作り方について説明するが、ここではまず使い方についてのみ説明する。 もしブートストラップでリソースプラグインを使いたいならば、Zend_Application_Bootstrap_ResourceBootstrapperインターフェースを追加実装する必要がある。このインターフェースはリソースプラグインの配置や登録、読み込みを行う。
interface Zend_Application_Bootstrap_ResourceBootstrapper
{
public function registerPluginResource($resource, $options = null);
public function unregisterPluginResource($resource);
public function hasPluginResource($resource);
public function getPluginResource($resource);
public function getPluginResources();
public function getPluginResourceNames();
public function setPluginLoader(Zend_Loader_PluginLoader_Interface $loader);
public function getPluginLoader();
}
リソースプラグインが提供する基本機能は、アプリケーションをまたがって再利用可能なリソースの初期化機構だ。これにより、ブートストラップを比較的クリーンに保つことができる。ブートストラップコードに手を加えず、新しいリソースを導入することができるのだ。 Zend_Application_Bootstrap_BootstrapAbstract(とそれを継承したZend_Application_Bootstrap_Bootstrap)にはこのインターフェースも実装されており、リソースプラグインを利用できるようになっている。 リソースプラグインを利用するには、アプリケーションオブジェクトやブートストラップのオプションとして、その設定を行う必要がある。これらは設定ファイルやオプションパラメータとして渡される。設定はリソース名のキーを持つ値のペアとなる。リソース名はクラスプレフィックスの後に続く部分となる。たとえばZend Frameworkに同梱されるリソースは、"Zend_Application_Resource_"というクラスプレフィックスを持ち、その後にリソース名が続くことになる。例を挙げると、
$application = new Zend_Application(APPLICATION_ENV, array(
'resources' => array(
'FrontController' => array(
'controllerDirectory' => APPLICATION_PATH . '/controllers',
),
),
));
これは、(controllerDirecotry)オプションが指定された"FrontController"リソースが使われることを示している。 独自のリソースプラグインを作る、もしくはサードパーティ製のリソースプラグインを使う場合、ブートストラップにそれらがどこにあるかを教えておく必要がある。ブートストラップは内部でZend_Loader_PluginLoaderを使うので、クラスプレフィックスと検索パスのペアが必要となる。 たとえば、APPLICATION_PATH/resources/に独自のリソースプラグインがあるとして、そのクラスプレフィックスがMy_Resourceだとすると、アプリケーションオブジェクトに以下のような情報を渡すことになる。
$application = new Zend_Application(APPLICATION_ENV, array(
'pluginPaths' => array(
'My_Resource' => APPLICATION_PATH . '/resources/',
),
'resources' => array(
'FrontController' => array(
'controllerDirectory' => APPLICATION_PATH . '/controllers',
),
),
));
これで指定したディレクトリのリソースを使えるようになる。 リソースメソッドのように、bootstrap()メソッドを使ってリソースプラグインを実行できる。リソースメソッド同様に、一つのリソースプラグイン、複数のプラグイン(配列を使って)、すべてのプラグインを指定できる。加えていうと、リソースメソッドと混在して実行することも可能だ。
// 一つ実行
$bootstrap->bootstrap('FrontController');
// 複数実行
$bootstrap->bootstrap(array('FrontController', 'Foo'));
// すべてのプラグインとメソッドを実行
$bootstrap->bootstrap();
4.3.1.3. リソースレジストリ
たいていのリソースメソッドやプラグインは、オブジェクトの初期化を行う。そして多くの場合、そのオブジェクトはアプリケーションの他のところでも利用される。その場合、オブジェクトにはどうやってアクセスすればいいだろうか? Zend_Application_Bootstrap_BootstrapAbstractは、それらのオブジェクトを保存するためのローカルなレジストリを用意しており、(リソースメソッドやプラグインで)リソースオブジェクトをreturnするだけで保存が行われる。 柔軟性を高くするために、レジストリは内部では"container"として参照される。オブジェクトだけがそこに保存される。リソースはリソース名にちなんだプロパティとして登録される。デフォルトでは、Zend_Registryインスタンスが利用されるが、他のオブジェクトを使うこともできる。setContainer()、getContainer()メソッドを使ってコンテナを操作できる。getResource($resource)でコンテナからリソースを取得し、hasResource($resource)でリソースが登録済みかどうかを調べることができる。 以下の例は、基本的なビューリソースだと思って欲しい
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initView()
{
$view = new Zend_View();
// more initialization...
return $view;
}
}
登録されているか確認したり、リソースを取得したりするのは、以下のようなコードとなる。
// has/getResource()の二つを使う
if ($bootstrap->hasResource('view')) {
$view = $bootstrap->getResource('view');
}
// コンテナを通して
$container = $bootstrap->getContainer();
if (isset($container->view)) {
$view = $container->view;
}
レジストリやコンテナがグローバルではないことに気をつけて欲しい。つまり、リソース(オブジェクト)を取得するにはブートストラップにアクセスする必要があるのだ。Zend_Application_Bootstrap_Bootstrapはそのための方法を用意している。run()が実行されている間、ブートストラップは自分自身をフロントコントローラの"bootstrap"パラメータとして登録している。それを使えば、ルーターやディスパッチャー、プラグインやアクションコントローラからリソースオブジェクトにアクセスできるようになる。 たとえばアクションコントローラの中からビューリソースにアクセスしたい場合は、以下のようになる。
class FooController extends Zend_Controller_Action
{
public function init()
{
$bootstrap = $this->getInvokeArg('bootstrap');
$view = $bootstrap->getResource('view');
// ...
}
}
4.3.1.4. 依存性の追跡
リソースメソッドやプラグインを実行するだけでなく、それらが必ず1度だけ実行されることを保証する必要がある。アプリケーションをブートストラップするたびに、(リソースの初期化処理が)複数回実行されてしまうのではオーバーヘッドを招くことになるからだ。 また、いくつかのリソースは他のリソースに依存している可能性がある。この二つの問題を解決するために、Zend_Application_Bootstrap_BootstrapAbstractは簡単で効果的な依存性の追跡機構を備えている。 前記の通り、すべてのリソース(メソッドもしくはプラグイン)は、bootstrap($resource)メソッドの$resource指定によって、一つのリソース、もしくは複数のリソース(配列)、もしくはすべてのリソース(引数なし)がブートストラップされるようになっている。 あるリソースが他のリソースに依存する場合、依存先のリソースがすでにブートストラップ済みであることを保証するために、依存元のリソース(メソッドやプラグイン)コードの中で(依存先のリソースをブートストラップする)bootstrap()メソッドを呼んでおくべきだ。その後でもしも同じリソースがブートストラップされたとしても、その呼び出しは無視されることになる。 リソースメソッド内でのそのような(依存するリソースの)呼び出しを行う場合、以下のように書く。
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initRequest()
{
// フロントコントローラが初期化済みであることを保証する
$this->bootstrap('FrontController');
// ブートストラップレジストリからフロントコントローラを取得する
$front = $this->getResource('FrontController');
$request = new Zend_Controller_Request_Http();
$request->setBaseUrl('/foo');
$front->setRequest($request);
// リクエストオブジェクトがブートストラップレジストリに保存されるようにする
return $request;
}
}

4.3.2. リソースプラグイン

前記の通り、再利用性の高いブートストラップリソースを作成し、あなたのコードから関係が薄い(リソース初期化の)コードを取り除きたいのならば、リソースプラグインを利用するといいだろう。Zend Frameworkはいくつもの標準的なリソースプラグインを同梱しているが、その意図は、開発者は独自に必要となるような初期化コードのみを書くべきだと考えるからだ。 リソースプラグインはZend_Application_Resource_Resourceインターフェースを実装すればいい。より簡単にはZend_Application_Resource_ResourceAbstractクラスを継承すればいい。基本インターフェースはシンプルだ。
interface Zend_Application_Resource_Resource
{
public function __construct($options = null);
public function setBootstrap(
Zend_Application_Bootstrap_Bootstrapper $bootstrap
);
public function getBootstrap();
public function setOptions(array $options);
public function getOptions();
public function init();
}
このインターフェースには、リソースはコンストラクタでオプションを受け取る、オプションの設定や取得が可能、ブートストラップオブジェクトの設定や取得が可能、そして初期化を実行するメソッドが定義されている。 次のサンプルは、アプリケーションで利用される一般的なビューの初期化処理を想定している。ドキュメントタイプ、CSS、JavaScriptを扱い、オプションから得た基本ドキュメントタイトルを渡す。そのような機能を持つリソースは以下のようになる。
class My_Resource_View extends Zend_Application_Resource_ResourceAbstract
{
protected $_view;
public function init()
{
// ビューを返し、レジストリに保存する
return $this->getView();
}
public function getView()
{
if (null === $this->_view) {
$options = $this->getOptions();
$title   = '';
if (array_key_exists('title', $options)) {
$title = $options['title'];
unset($options['title']);
}
$view = new Zend_View($options);
$view->doctype('XHTML1_STRICT');
$view->headTitle($title);
$view->headLink()->appendStylesheet('/css/site.css');
$view->headScript()->appendfile('/js/analytics.js');
$viewRenderer =
Zend_Controller_Action_HelperBroker::getStaticHelper(
'ViewRenderer'
);
$viewRenderer->setView($view);
$this->_view = $view;
}
return $this->_view;
}
}
このリソースプラグインのプレフィックスパスを登録すると、アプリケーションから使えるようになる。プラグインローダーを利用することにより、デフォルトの"View"リソースプラグインが上書きされ、独自のプラグインが代わりに利用されるようになる。

*1:サービススクリプトって、定期実行するわけではないコマンドライン系の処理のことか? たとえば初期セットアップスクリプトとか。日本語でもサービススクリプトっていうんだろうか? cronの方ならバッチが一番近い感じなんだけど。

*2:ブートストラップは日本語にせずに、そのままカタカナで書いてしまうことにした

*3:Bootstrapで使われているpluginpaths設定のことかなー。だったら「リソースのプレフィックスとパスのペア」って感じになる?

Published At2009-06-24 09:00Updated At2019-12-30 23:55

技術日記
[設計] URLの設計Edit

DB設計によって内部的なサービスのイメージがだいぶ固まってきたので、今度はURL設計を行うことで、外部から見たサービスのイメージを固めていく。
  • / - サイトトップページ。
  • /login - ログイン。
  • /logout - ログアウト。
  • /about - サイトの説明。
  • /search - 検索ページ。
  • /config - ユーザー設定変更。
  • /register - ユーザー登録。
  • /home/ - ログインしたユーザーのホームページ。
  • /uris/ - URI統計ページ。新着、注目など。
  • /uri/[uri_string]/ - [uri_string]というURLに関するページ。
  • /asins/ - ASIN統計ページ。同上。
  • /asin/[asin]/ - [asin]というASINに関するページ。
  • /location/[longitude]/[latitude]/ - [longitude]、[latitude]という位置に関するページ。
  • /tags/ - タグ統計ページ。同上。
  • /tag/[tagname]/ - [tagname]というタグに関するページ。
  • /dj/[personality_name]/ - [personality_name]のトップページ。
  • /dj/[personality_name]/date/[yyyy]/[mm]/[dd]/ - [personality_name]の[yyyy][mm][dd]の更新。
  • /dj/[personality_name]/talk/[talk_id]/ - [personality_name]の[talk_id]発言。
あまり機能を増やしすぎても、あまり使われないわりにはサービスが重くなるだけなので、この程度の機能があれば十分だろうか。実際に作り始めると、もう少しページは増えてしまうだろう。 また、上記URL群は情報表示のそれぞれの切り口での起点ページと位置づけ、実際にはそこに表示された要素からユーザーアクションなどに応じて、(Ajaxなどを使って)必要な情報が後付で追記されていく構成を考えている。 ただ、最初からその辺まで含めて設計を行うのは難しいので、まずはプロトタイプ的に上記URL構成のシンプルなWebアプリケーションを開発し、それを実際に使いながらユーザーインターフェースを洗練させていくというアプローチを取っていくつもりだ。

Published At2009-06-23 09:00Updated At2019-12-30 23:55

技術日記
DB設計ベースでのサービス設計Edit

私の場合、DBのテーブル設計を考えながらアプリケーションの機能をまとめていくので、まずはどういうサービスにしたいのかを考えながら、ざっくりとしたテーブル設計を書いてみた。 f:id:netjockey:20090622172526p:image 利用者はパーソナリティ(personalities)テーブルで管理される。NetJockeyというのは、twitterライクなマイクロブログの語り手をラジオのDJ(パーソナリティ)、その言葉の連なりをDJのトークに見立てて名付けたネーミングだ。だからパーソナリティ(personalities)がトーク(talks)するという形でのテーブル設計となっている。 トークに含まれるURL(talks_uris)、ASIN(talks_asins)、タグ(talks_tags)、位置情報(talks_locations)などを抽出することで、同じ話題に関するトークをまとめることができる。またURL(uris)に関しては、URLのFQDN(uris.hostname)に別途インデックスを用意することによって、URL完全一致だけでなく、「あるドメイン上のURLに関する話題」のような範囲を広げた形で話題を追うことも可能にする。位置情報に関してもGIS関連機能を使って「○○近くの話題」などを追いやすくする。 NetJockey内部でのやりとりに関しては、twitterの「@~」に相当するパーソナリティ対パーソナリティ(talks_personalities)のほかに、発言同士の関連性(talks_talks)も管理する。特定の発言に対する反応を明示する仕組みだ。表記法としては2chライクに「>>発言番号」などで表してもいいかもしれない。 話題をフォローする方法については、発言者(following_personalities)のほかに、URL(following_uris)、ASIN(following_asins)、タグ(following_tags)、トーク(following_talks)、位置(following_locations)などさまざまな方法を用意している。ただ、これをユーザーが手動でいちいち登録するというのでは使い勝手が良くないように思うので、基本的には自分のトーク中に含まれるURL、ASIN、タグ、位置情報、発言者、トークなどを自動的にフォロー対象に追加していく(古いものは自動消去?)といった形にすることを考えている。これならば自然な形でこれらの機能を利用することができるだろう。もちろん明示的に特定のフォロー対象を追加・削除することも可能にする。 フォローの反対に、見たくない情報を見なくて済むようにする方法としては、人間単位でのブロック(blocked_personalities)を用意している。個人レベルでのブロック機能については、私はあまり重きを置いていないのだが、スパム対策としてはこの手の機能は必要だろう。また、システム全体としてのブロック(public_blocked)も用意しておく。こちらに登録されたパーソナリティは、いわゆるスパム系パーソナリティとして通常の表示対象から除去することができるようにする。その逆に、システムからの通知などを強制的に表示するためのシステム全体としてのフォロー(public_following)と言うものも考えているが、実際に利用するかどうか微妙なところだ。 認証(authentications)に関しては、1470.netと同様に外部認証系APIを主に利用することを考えている。はてな認証API、typekey、OpenID、携帯端末IDあたりが主な認証手段となる。また、APIなどで利用するためのサービスローカルなパスワードも一応保持しておくことを考えている。 本当はもう一つ、ディレクトリ(directories)という話題をまとめる単位を用意することも考えていた。ディレクトリとは、2chにおける板的なもの。あるいは、NetNewsにおけるニュースグループ的なもの。つまり、システム側である程度話題の分類指針を示しておき、利用者がそこから自分の好きなディレクトリを選んで発言することによって、半強制的に話題をある程度分類してしまう、というアプローチだ。このディレクトリ自体をWikiとしてユーザーが自分たちで編集できるようにしておくことで、世の中の話題のカテゴライズがリアルタイムで視覚化できるのではないか、などとも妄想した。 これはこれで非常に面白そうにも思えるのだが、それがなくてもすでに話題の分類のための手段は十分に用意されているし、ディレクトリ+Wikiという仕組みはそれ自体がある程度以上に複雑すぎて、まともに立ち上げるのはすごく困難そうに思えた。そこでこの機能は割愛することに決めた。ただし、メインのサービスが十分うまく立ち上がった後に余力があれば、このアイディアは復活させるのも面白いように思っている。

Published At2009-06-22 09:00Updated At2019-12-30 23:55

技術日記
Zend_Db_Select::where()メソッドの$typeEdit

trunkでは、

    * @param string   $cond  The WHERE condition.
* @param string   $value OPTIONAL A single value to quote into the condition.
* @param constant $type  OPTIONAL The type of the given value
* @return Zend_Db_Select This Zend_Db_Select object.
*/
public function where($cond, $value = null, $type = null)
{
if ((func_num_args() > 3) or (($type !== null) and ($type !== 0) and ($type !== 1) and ($type !== 2))) {
$value = func_get_args();
array_shift($value);
$type = null;
}
$this->_parts[self::WHERE][] = $this->_where($cond, $value, $type, true);
return $this;
}

なんて感じになっているんだけど、この「(($type !== null) and ($type !== 0) and ($type !== 1) and ($type !== 2))」ってのはどこから現れたんだ?

where()メソッドでは、

    * @param constant $type  OPTIONAL The type of the given value

になっているけど、ここから呼ばれている_where()メソッドでは、

    * @param string   $type   optional

になっているし、実際PDO_MYSQLを使ったZend_Db_Tableのinfo()では、DATA_TYPEはstringで返ってくる("varchar"とか"int"とか)。

0、1、2という数値から、Zend_Db::INT_TYPE = 0、Zend_Db::BIGINT_TYPE = 1、Zend_Db::FLOAT_TYPE = 2あたりが怪しそうな気がするけど、この値ってZend_Db_Adapter_Abstract内で$_numericDataTypesとして定義されて以降、まったく使われていないし、意味的にも$typeが数値ではなかった場合に$valueを配列扱いするっていう意味がわからない。

これのせいで、trunkにしたらZend_Db_Tableのリレーション周り(findParentRow()とか)が動かなくなっちゃったんだよなー(Zend_Db_Table_Row_Abstract::findParentRow()内でZend_Db_Table_Abstract::info()で取得したDATA_TYPEをそのまま渡しているため)。自前で直そうにも、コードの意味がわからなくて直せないし。

3/13追記

昨日更新されたrevision 8783で、上記の怪しげな条件文はばっさり削除された。なんだったんだ……。

Published At2008-03-12 00:00Updated At2019-12-30 23:56

技術日記
Zend_Controller_Dispatcher_Standardのtrunk 247行目Edit

           if (!$this->getParam('useDefaultControllerAlways') && !empty($controller)) {

           if (!$this->getParam('useDefaultControllerAlways') && empty($controller)) {

のような気がするんだけど、いい花粉が飛んでいて頭がぼーっとするせいで、いまいち自分の判断が信用できない。ここは、useDefaultControllerAlwaysが無効な場合は、コントローラ名が解決できない→例外を発生させる処理だよな。

Published At2008-03-11 00:00Updated At2019-12-30 23:56