tumblrメンテ終了...2007年11月02日 03時05分42秒

新しくなったのはいいが、AutoPagerizerもShareOnTumblr.jsも使えんようになっとる... 結構困った...

dashboardもビミョー使いづらくなってるような。friends/followerがごちゃごちゃしてても、前のが良かったような...

ZendFramework入門・その7データベースを扱う・その22007年11月13日 04時51分20秒

モーレツに久しぶりになりましたが、ZendFramework入門、久々の更新です。もしこの記事を楽しみにしてくださってる方がいらしたら、申し訳ありませんでした。なんとか仕事のばたばたは多少収束したので、また週1くらいのペースに戻せたらよいなぁと思っています。

前回のおさらい

前回が9/23と、7週間以上も空いているので、ちょっとおさらいを。

前回よりZend_Db+SQLiteを使って、単純な構造の単一テーブルをにURLとタイトルを保存する簡易ブックマークを題材として取り上げています。

ブックマークを格納するテーブル「bookmarks」の構造は以下のように定義しました。

キー カラム 備考
PK id INTEGER AUTO INCREMENT
  title VACHAR (100)  
IDX1 url VARCHAR (255)  
IDX2 registDate TIMESTAMP  
  comment TEXT  

この「bookmarks」テーブルを、Zend_Db_Adapterで検索、表示するところまでが前回分です。テーブルの作成やファイル構成などは前回の記事を参照してください。

Zend_Db_Tableを使う

さて、ようやく今回の内容に入ります。

前回分では導入ということもあり、Zend_Db_Adapter(正確にはZend_Db_Adapter_Pdo_Sqlite)でデータベースへの接続を開き、そのまま原始的にクエリを発行して連想配列を取得するという、最低限の使い方のみでしたが、今回の題材のように単純なテーブルを対象にする場合は、Zend_Db_Tableを使用して、オブジェクトとして扱ったほうがラクに扱えます。

Zend_Db_Tableは抽象クラスなので、自分で使用する場合はここから派生させたクラスを作成する必要があります。また、Zend_Db_Tableは、基本的にクラスの定義が実際のテーブルとのマッピングになるようなイメージで使用することになります。具体的なコードを示してみましょう。

<?php
require_once 'Zend/Db/Table.php';

class Bookmarks extends Zend_Db_Table {
	protected $_name = 'bookmarks';
	
	protected $_primary = 'id';
	
}
Bookmarks.php
これが、今回題材にしているデータベースの「bookmarks」テーブルを扱うためのクラスで、キーポイントは以下の2つでしょうか。
  • 「$_name」プロテクトフィールドに実際のテーブル名を設定
  • 「$_primary」プロテクトフィールドに対象のテーブルのプライマリキーの名前を設定
キーポイントといっても、実際のコード部分そのままなのですが(^^;、実際にコードはたったこれだけです。

また、dara-jは試していませんが、クラス名がテーブル名と厳密に一致する場合は$_nameを省略しても良しなにマッピングしてくれるようです。

自動インクリメントやシーケンス

MySQLやSQLiteはプライマリキーに自動インクリメントを設定できますが、Zend_Db_Tableもこの機能を利用するように設計されています。自動インクリメントに対応するにはプロテクトフィールド「$_sequence」にtrueを設定します。

といっても、$_sequenceはデフォルト値がtrueなため、特に指定しないとプライマリキーを自動インクリメント列と見なされるため、自動インクリメント機能を利用する場合は上記のように特に$_sequenceを設定する必要がありません。

また、OracleやPostgreSQLなどは、自動的にプライマリキーに値を割り当てるのにシーケンスを利用できますが、$_sequenceはこの機能にも対応しています。使い方は簡単で、$_sequenceにシーケンス名を割り当てるだけです。例えばここで扱っているbookmarksのid用にシーケンス「seq_bookmark_id」を定義している場合は

protected $_sequnce = 'seq_bookmark_id';
のように定義するだけです。

使用するDBMSが自動インクリメントやシーケンスに対応していない場合や、アプリケーションからプライマリキー値を設定するような設計にする場合は、明示的に$_sequenceにfalseを設定し、データ挿入時に明示的に値を設定する必要がありますので注意してください。

アプリケーションの仕様

さて、このBookmarksクラスを使用して、実際にデータを追加・更新・削除できる機能を追加していきましょう。

やり方はいろいろあるかと思いますが、今回は単純な設計ですので、以下のような仕様にします。

  • コントローラはIndexControllerのみ使用
  • indexActionで全データをリスト表示
  • addActionで新しいデータを作成
  • editActionで既存データを変更
  • deleteActionで既存データを削除
このほかに、add/editActionは実際にはデータを編集するフォーム表示になるので、そこからPOSTして実際にデータをDBへ反映させる「saveAction」をIndexControllerに実装します。

また、addActionとeditActionは初期データの有無を除けばだいたい同じになるので、addActionとeditActionでビュースクリプトを共用することにします。

IndexController

いきなりコードです。

<?php
require_once 'Zend/Controller/Action.php';

// Zend_Dbをrequre
require_once 'Zend/Db.php';

// Bookmarks.phpをrequire
require_once './application/models/Bookmarks.php';

// IndexController
class IndexController extends Zend_Controller_Action {
	// Zend_Db_Adapter
	private $_db;
	
	// initメソッド。初期化処理用のフックメソッド
	// アクセスレベルが「public」な点に注意
	public function init() {
		// リクエストオブジェクト(Zend_Controller_Request_Abstract)を取得
		$request = $this->getRequest();
		
		// ベースURLを取得
		$baseUrl = getApplicationUrl( $request );
		
		// ビューへ割り当てる
		$this->view->assign( 'baseUrl', $baseUrl );
		
		// Zend_Db_Adapterを生成
		$this->_db = Zend_Db::factory(
			// Zend_Db_Adapter_Pdo_Sqlite クラスを使用する
			"Pdo_Sqlite",
			
			// 生成パラメータ。SQLiteは接続先を示す'dbname'のみでOK
			array(
				'dbname' => 'zdb1.db'
			)
		);
		
	}
	
	// indexアクション
	public function indexAction() {
		// SELECTステートメントを実行
		$result = $this->_db->fetchAll( 'SELECT * FROM bookmarks ORDER BY registDate DESC' );
		
		// 実行結果をビューへ割り当てる
		$this->view->assign( 'rows', $result );
	}
	
	// editアクション
	// 既存ブックマークの編集を行う
	public function editAction() {
		// Bookmarksを生成
		$bookmarks = new Bookmarks( array( 'db' => $this->_db ) );
		
		// リクエストからパラメータ'id'を取得
		// (リクエスト中に含まれない場合は-1とする)
		$id = $this->getRequest()->getParam( 'id', -1 );
		
		$this->view->assign( 'bookmark', $bookmarks->find( $id )->current() );
		
		// edit.phtmlをaddActionと共用するので、呼び出し元を明示しておく
		$this->view->assign( 'mode', 'edit' );
	}
	
	// addアクション
	// ブックマークの新規追加を行う
	public function addAction() {
		$this->view->assign( 'mode', 'add' );
		
		// ビュースクリプトはeditActionと同じ'edit.phtml'
		$this->_helper->viewRenderer( 'edit' );
	}
	
	// saveアクション
	// editAction/addActionから呼び出され、編集されたデータをDBへ保存する
	public function saveAction() {
		$bookmarks = new Bookmarks( array( 'db' => $this->_db ) );
		
		$req = $this->getRequest();
		
		$mode = $req->getParam( 'mode', 'add' );
		
		$row = $mode == 'edit' ?
			$bookmarks->find( $req->getParam( 'id', -1 ) )->current() :
			$bookmarks->fetchNew();
		
		// データ追加時はregistDateに現在日時を設定
		if( $mode == 'add' ) {
			$row->registDate = date('Y-m-d H:i:s');
		}
		// id と registDate 以外のカラム名でループ処理
		foreach( array( 'title', 'url', 'comment' ) as $key ) {
			$row->{$key} = $req->getParam( $key );
		}
		
		// saveメソッドでDBへ反映
		$row->save();
		
		// index/indexへリダイレクト
		$this->_redirect( 'index/index' );
	}
	
	// deleteアクション
	// 指定ブックマークを削除する
	public function deleteAction() {
		$bookmarks = new Bookmarks( array( 'db' => $this->_db ) );
		$row = $bookmarks->find( $this->getRequest()->getParam('id', -1) )->current();
		
		if( $row != null ) {
			$row->delete();
		}
		
		$this->_redirect( 'index/index' );
	}
}
IndexController.php
要点はだいたいコメントに書いてありますが、以下に簡単な解説を示します。

定義部分

先ほど作成したBookmarksクラスを定義する「Bookmarks.php」をrequireしています。配置はパスが通ればどこでもいいんですが(ってわけでもないでしょうけど)、application下にmodelsを切って、そこに配置することにしました。このあたりは別にZend Frameworkが何かしてくれるわけではないので、他の場所でも問題ありません。

また、Zend_Db_Adapterを格納する変数を前回はindexAction内のローカル変数にしていましたが、今回は各アクションメソッドから共通で利用するため、プライベートフィールド「$_db」に格納することにしました。

コンストラクタ

大筋は前回と変わりませんが、$_db(=Zend_Db_Adapter)の初期化をここで行っています。

indexAction

Zend_Db_Adapterの初期化コードが__constructに移動した以外は前回と変わりません。

editAction

さて、ここからが今回追加分のコードになります。まずは既存データの編集アクション「editAction」です。

まず、Bookmarksクラスを初期化していますが、コンストラクタパラメータとしてZend_Db_Adapterを渡しています。Zend_Db_Tableのコンストラクタパラメータは連想配列を渡す仕様になっており、ここではキー「db」にZend_Db_Adapterを設定して使用しています。その他のキーについてはAPIドキュメントとZend_Db_Table自身のソースコードが一番参考になると思います。

次に、Zend_Controller_Request::getParam()で編集するブックマークのidを受け取っています。第二引数に「-1」を指定しているのは、リクエストに「id」が含まれていなかった場合はデフォルト値として「-1」を使用するためです。こうすることでリクエストのnullチェックなどをしないでもデフォルトの動作を決めることができるので、dara-jは比較的よく使うパターンです。

Zend_Db_Table::find()

その次にいきなりZend_Viewへ「bookmark」をassignしていますが、割り当てる値を取得しているのが、Zend_Db_Table::find()メソッドになります。

Zend_Db_Table::find()は、プライマリキーを指定して一致するデータをZend_Db_Table_Rowsetで取得するためのメソッドです。

Zend_Db_Table_Rowset

Zend_Db_Table_Rowsetはその名の通り「行セット」を表すクラスです。これ自身はたいした機能を提供しておらず、問い合わせに対して実際の行データであるZend_Db_Table_Rowを0行以上返すために使用されます。 Zend_Db_Table_RowsetはIteratorインターフェイスを実装しているため、foreach構文でループ処理が可能です。また、Countableインターフェイスも実装しているため、count()メソッドで、結果の行数を取得することもできます。

ちょっとわき道にそれますが、findメソッドがZend_Db_Table_Rowsetを返す理由を説明しておきます。

先ほど、findメソッドは「プライマリキーを指定して一致するデータを取得する」と説明しましたが、疑問に思われた方もいらっしゃるかも知れません。テーブル内でユニークなプライマリキーで検索をするのに、なぜZend_Db_Table_Rowを直接返さず、不要とも思われる複数行対応のRowsetを返すのでしょうか?と。

答えは非常に単純で、「find()の引数に複数のプライマリキーをarrayで渡すことができ、複数行を取得できる」からです。また、foreachが可能であることから、引数を単体で指定しようがarrayを渡そうが、取得できた行に対してループ処理を行えば、余計なnullチェックもしなくて済むでしょう。戻りが0行なら、foreach内のブロックが実行されないだけですので。

さて、本題に戻ります。ビューへassignしているのは取得したZend_Db_Table_Rowsetのcurrent()メソッドの戻り値です。

current()メソッドは名前から想像がつくでしょうが、Rowset中の「現在行」を取得するためのメソッドで、戻り型はZend_Db_Table_Rowになります。

ここでのコードはfindに単独の値を指定しているため、戻りのRowsetは0行または1行のRowを含んでいるはずです。もし指定したプライマリキーに一致するデータがない場合、current()メソッドはnullを返します。 Zend_Db_Table_Rowについては後ほど説明します。

そして、editActionの最後です。キー「mode」に値「edit」を設定しているのは、先ほど触れたとおりaddActionとビュースクリプトを共有するため、どちらのアクションメソッドからビューを実行しているかの識別のために設定しています。

addAction

こちらはきわめて単純で、editActionの最後の部分と同じようにキー「mode」をZend_Viewへassignしています。これはaddActionを示すために「add」という値を割り当てています。

そしてここで終わると「add.phtml」を探しにいってしまうので、ViewRendererアクションヘルパーを呼び出して「edit.phtml」でレンダリングするように指示しています。ViewRendererアクションヘルパーについては「7.8.4. 組み込みのアクションヘルパー」のViewRendererの部分を参照してください。

saveAction

先にビュースクリプトのコードの後に説明したほうがいいかとも思ったのですが、ちょっと解説が長くなっているのでソースから遠くならないうちに先に説明します。

まず、このアクションメソッド内で扱っている、Zend_Db_Table_Rowの簡単な説明から。

このクラスは名前の通り、データベースの行オブジェクトを表現するクラスで、テーブルのカラム名に一致するプロパティを実装しています(実際にはマジックメソッドですが)。そして、そのプロパティの読み書きを行うことでデータを操作します。

例えばカラム「comment」に値を設定するには、

// $tableがBookmarksのインスタンス、$commentがコメントデータの文字列とします
$row = $table->find( $id )->current();
$row->comment = $comment;

// save()メソッドでデータベースへ書き込みを行います
$row->save();
といった具合に使用します。

さて、メソッド全体の説明です。このアクションメソッドはeditAction、addActionがレンダリングしたフォーム(=edit.phtml)のPOST先になります。

edit.phtmlのフォームは、テーブルのカラム名に一致するフィールドを定義しておきます(後ほどコードを掲載しますが)。

Bookmarksを初期化し、getRequest()->getParam('mode')でモードを取得するところまでは問題ないと思います。その次の

$row = $mode == 'edit' ?...
の部分は、editActionとaddActionでZend_Db_Table_Rowの取得方法を変えています。

$modeが'edit'(すなわちeditActionから遷移してきた)場合はeditActionと同様にfind()メソッドで既存のデータを取得してますが、addActionからの遷移の場合はfetchNew()で新しい空の行オブジェクトを取得しています。

その直後の「$row->registDate」は先ほど述べたとおり、「registData」列に日付データをセットしています。

その次のforeachループはちょっとわかりづらいかも知れませんが、値を設定したいプロパティ名(=フォームフィールド名にも一致)をループ処理して、値を割り当てています。

そしてsave()メソッドでデータベースへ更新を反映させた後にindex/indexへリダイレクトしています。

deleteAction

ようやく最後のアクションメソッドです。データを取得するところまでは問題ないでしょう。

取得したZend_Db_Table_Rowのdelete()メソッドでデータを削除しています。delete()後はsave()は不要ですのでお間違えないように。

削除が完了したら、index/indexへリダイレクトします。

ビュースクリプト

ビュースクリプトのソースを以下に示します。ここでは特に解説しませんが、特に難しいことはしていないので、皆さんで確認してみてください。

<html>
<head>
	<title>zdb1</title>
	<!-- base要素の出力。末尾の「/」に注意 -->
	<base href="<?php echo $this->baseUrl; ?>/"></base>
</head>
<body>
	<h3>
		bookmarksの内容
	</h3>
	<hr>
	
	<!-- 新規追加へのリンク -->
	<a href="index/add">新規追加</a>
	<!-- テーブルで表示 -->
	<table border="1" cellpadding="0" cellspacing="0">
<?php
foreach( $this->rows as $i => $row ) {
	if( $i == 0 ) {
		// 最初の行のみ
?>
		<tr>
<?php
		// ヘッダ行出力
		foreach( $row as $key => $value ) {
			echo '<th>' . $this->escape( $key ) . '</th>';
		}
		echo '<th>操作</th>';
?>
		</tr>
<?php
		// if 終わり
	}
?>
	<tr>
<?php
	// 行出力
	foreach( $row as $key => $value ) {
		echo '<td>' . $this->escape( $value ) . '</td>';
	}
	echo "<td><a href=\"index/edit/id/{$row['id']}\">編集</a><br><a href=\"index/delete/id/{$row['id']}\">削除</a></td>";
?>
	</tr>
<?php
	// foreach終わり
}
?>
	</table>
</body>
</html>
index.phtml

<?php
$title = $this->mode == 'edit' ?
	"id {$this->bookmark->id} の編集" : "新規作成";
?>
<html>
<head>
	<title><?php echo $this->escape( $title ); ?></title>
	<!-- base要素の出力。末尾の「/」に注意 -->
	<base href="<?php echo $this->baseUrl; ?>/"></base>
</head>
<body>
<h3><?php echo $this->escape( $title ); ?></h3>
<hr>
<form action="index/save" method="post">
<input type="hidden" name="mode" value="<?php echo $this->mode; ?>">
<input type="hidden" name="id" value="<?php echo $this->bookmark->id; ?>">
<dl>
<?php foreach( array( 'id', 'title', 'url', 'registDate', 'comment' ) as $key ) { ?>
	<dt><?php echo $this->escape( $key ); ?></dt>
	<dd><?php
	if( $key != 'id' && $key != 'registDate' ) {
		echo "<input type=\"text\" name=\"{$key}\" value=\"" .
			$this->escape( $this->bookmark->{$key} ) . "\">";
	} else {
		echo $this->escape( $this->bookmark->{$key} );
	}
	?></dd>
<?php } ?>
</dl>
<hr>
<button type="submit">保存</button><button type="reset">リセット</button>
<a href="index/index">戻る</a>
</form>
</body>
edit.phtml

ファイル構成

今回のアプリケーションのファイル構成を示します。

  • htdocs/
    • zdb1/
      • application/
        • controllers/
          • IndexController.php
        • models/
          • Bookmarks.php
        • views/
          • index/
            • edit.phtml
            • index.phtml
      • index.php
      • .htaccess
      • zdb1.db
      • create_table.sql
今回のファイル一式は、こちらからダウンロードできます。

あとがき

えー、本当に久しぶりの更新になってしまいましたが、いかがでしたでしょうか?コードそのものはたいした量ではないのですが、説明することが結構増えてきたので、そろそろ全コードを掲載するのがちょっとしんどくなってきました。

次回はZend_Db関連をもう少し突っ込んでみようかと思います。Zend_Db_Tableのリレーション機能か、Zend_Select・Zend_Statementか、といったあたりでしょうか。思いつきで変更するかもしれませんが、その場合はご容赦ください。(^^;

それでは、次回はなるべく早く更新したいと思いますので、よろしくお願いいたします。

Googleリーダーが微妙にアレだった件2007年11月13日 23時32分18秒

普段フィードはUSBメモリに放り込んだGreatNewsで読んでいるのだが、今日はたまたま家に忘れてしまったため、しょーことなしにGoogleリーダーを使ってみた。

キーボード操作に慣れると、なるほど快適なのだが、なぜかtumblrのフィードをなかなか受信してくれない。

このところtumblrがしょっちゅうコケるのでその影響かとも思ったがさにあらず、フィードのURLを直接叩くとちゃんと更新された情報を取得できるのでなぜかGoogleリーダーが読み込んでくれないということらしい。

ダメじゃん

なかなか気持ちよく使えていただけに残念だが、まだ当面GreatNewsが手放せないか。

Inkscapeでアクア風ボタンを作ってみる2007年11月14日 04時07分22秒

いきなり試みてみたり

ちょっと前に常山日記さんとこに掲載されていたorange-A-go-go!さんのこちらの記事。Photoshopでアクア風ボタンを作成する手順を説明してくれている。

これみて、なぜかInkscapeでできそう、と思ったのでやってみた。ま、ボタンつーか珠つーか。

ちなみに手持ちのバージョンは0.45。

手順

正円を描く

Inkscape起動したら、ツールバーから「円/弧を作成」を選択(またはF5)して、Ctrlキーを押しながら適当な正円を描画。
(クリックで拡大)

グラかける

描画した円のコンテキストメニューから[フィル/ストローク]をクリックして「フィル/ストローク」ダイアログを呼び出す。
(クリックで拡大)

そんで、「フィル」タブで線形グラデーションを選択して[編集]。「ドキュメントが選択されてない」だの、「オブジェクトが選択されてない」だのナメたことを抜かしてくる場合があるので、そういうときはとりあえずメインウィンドウや先ほどの円オブジェクトを適当にクリックしてるとそのうち認識するのでがんばる。
(クリックで拡大)

で、黒→オレンジ→黄色ぐらいの感じでグラデーションをかける。どの色もアルファ値を「255」にして不透明にすることを忘れないように。
(クリックで拡大)

グラ設定できたら、ついでに「ストロークの塗り」タブでストロークを「塗りなし」に設定する。するとこんな感じになる。
(クリックで拡大)

グラの方向を変える

Inkscapeでは、線形グラデーションの方向がデフォルトでは水平方向だが、元記事のように垂直グラにしたいので、ツールバーの「パスのノードまたはコントロールハンドルを編集」を選択(するか、F2)してグラの方向を変更する。
(クリックで拡大)

レイヤー効果なんてないので工夫してみる

元記事ではこの後
□レイヤーパレットにある『レイヤー1』をダブルクリックして、レイヤースタイルを変更します。
□変更するスタイルは、『光彩(内側)』です。
なんてことをしているんだけど、Inkscapeにはそんな機能がないので、ちょっと工夫。

見た感じは外周側が暗く、中心に向かって透明のグラデーションぽくなってるので、そんなようなオブジェクトを重ねてみることにする。

円オブジェクトを選択した状態で[編集] - [複製]で同じサイズの円オブジェクトを複製し、またもコンテキストメニューから[フィル/ストローク]を実行、「フィル」タブで「放射グラデーション」を選択する。

このままグラ編集をすると、最初の円オブジェクトのグラデーションを構成する色も編集されてしまうので、[コピー]をクリックしてグラデーションを複製しなきゃならない点に注意。

複製したグラデーションを編集して、外周部分を不透明の黒、中心を透明の白に変更する。中心は透明の白(FFFFFF00)の色フェーズを2~3個使ったほうが外周の暗い部分が狭くできるのでよい(ここではFFFFFF00を2つ使っている)。
(クリックで拡大)

で、作成したグラ円オブジェクトを元の円にぴったり重ねる。ただ、このままじゃグラ円がちょっと強いかもしれないので、そんな場合は「フィル/ストローク」で「マスター透明度」を調整してみるとよいかと。
(クリックで拡大)
ここでは「マスター透明度」を67%に設定してみている。

ハイライトを入れる

元記事と同じように、円の上部に楕円を重ねて、これに白→透明のグラをかける。透明部分の重なり具合を見て、グラデーションの範囲を適当に調整してみるとよいかも。
(クリックで拡大)

で、これまた元記事に準じてハイライト楕円を複製、[オブジェクト] - [垂直に反転]した後、円の下部にハイライトと上下対象になるような位置に配置する。
下部に配置したクローン楕円を選択したまま、「フィル/ストローク」で「ぼかし」とマスター透明度を適当に調整。この図ではぼかし:11.0、マスター透明度:70ってな感じ。
(クリックで拡大)

完成

で、ビットマップエクスポートすると、こんな感じになりました。PNG出力ね。

Inkscapeで作成したSVGはこちら。NYSLでご自由にお使いください。使い道ないだろうけど。

色みやグラデーションのかけ方、ぼかし方なんかはあんまり考えないで行ったけど、そこそこうまくいったのでは。ハイライトに多少色みをつけたり、放射グラを黒ではなくほかの色にしてみたりするとも少しきれいになるかな。

Zend_DateでHTTP日付を出力2007年11月15日 22時02分13秒

Zend_Dateのコネタでやす。

HTTP日付って?

昨日今日と、ちょっとIEのタコっぷりにやられてた都合で(あとで書くかも)、PHPから Last-Modified をレスポンスヘッダに出力してやる必要があったので、ちょっと調べてみた。

検索して引っかかったこちらのサイトの記述によると、

Last-Modified = "Last-Modified" ":" HTTP-date
ってな定義らしい。んで、この「HTTP-date」は、
リソースの最終更新時刻を記述するためのフィールドです。ここに指定する時刻は、HTTP 日付を使用しなければなりません。
だそうな。サンプルを見ると、
Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT
てな出力になる。

んで、さらにここのサイトさんはHTTP日付についても掲載していてくれて、それによると、

最初のフォーマットはインターネット標準としてより好まれ、RFC 1123 (RFC 822 の改定) にて定義される固定長サブセットを表す。
ってな話らしい。要するにRFC 822か、RFC 1123の形式で出力できればよさげ。

Zend_Dateでいけるかにゃー

ZFのマニュアル「9.6 日付関数全般の定数」をつらつら眺めると、「表 9.14. 日付フォーマットに関する定数 (タイムゾーンを含むフォーマット)」に、

  • Zend_Date::RFC_822
  • Zend_Date::RFC_1123
  • なんて、そのままズバリの定数があるので、これをZend_Date::get()に渡せばよかろうと。

    <php
    require_once 'Zend/Date.php';
    
    $date = new Zend_Date('2007/11/15');
    echo $date->get( Zend_Date::RFC_1123 );
    
    とやったら、出力は
    Thu, 15 Nov 2007 00:00:00 +0900
    
    
    となった。これでいいのかと思いきや、
    全ての HTTP 日付/時刻スタンプは、例外を除いてグリニッジ標準時刻 (GMT) で表されなければならない。
    なんてあるじゃないですか!タイムゾーンを設定せねば。

    Zend_Dateにタイムゾーンを設定してみる

    Zend_Dateには「getTimeZone()」やら「setTimeZone()」なんてメソッドがあるので、これ使えばいいかと。

    ただ、マニュアルに載ってるサンプルコードは特定のタイムゾーンに設定するものばかりで、明示的にGMTを設定するコードが載ってない。はて、どうしたもんかと30秒くらい悩んだ挙句、ダメもとで文字列'GMT'と渡してみることに。

    $date->setTimeZone( 'GMT' );
    echo $date->get( Zend_Date::TIMEZONE_NAME );
    
    // -> 'GMT'
    
    
    あら、ビンゴ。んじゃさっきのZend_Date::RFC_1123と組み合わせてみるか。
    $date->setTimeZone( 'GMT' );
    echo $date->get( Zend_Date::RFC_1123 );
    
    // -> 'Wed, 14 Nov 2007 15:00:00 +0000'
    
    
    って、「+0000」ってなんやねん。

    なんとか「GMT」と出力したい

    このままほっとこうかなーとも思ったが、クライアントが意図どおり解釈してくれるかちと不安になったので、なんとか「+0000」を「GMT」に変えて出力したい。

    もちろん、細かく書式指定子を与えれば欲しい出力が得られるのはわかってるんだけど、なるたけ不精したいじゃない。

    なんとかならんかとまた「 9.6 日付関数全般の定数」をつらつら眺めたら、「表 9.13. タイムゾーンに関する定数」におあつらえ向きの「Zend_Date::GMT_DIFF」なんてのが載ってたので、どんな出力になるのか試してみた。

    echo $date->get( Zend_Date::GMT_DIFF );
    
    // -> '+0000'
    
    
    おし、思ったとおり「+0000」の出力が得られた!これを単純に'GMT'に置き換えればいいワケだ。多分。

    そんなこんなで結論

    置換は特に問題ないだろうと思うので、シンプルにstr_replace()を使うことにして、最終的には、こんな感じになった。

    <php
    require_once 'Zend/Date.php';
    
    $date = new Zend_Date('2007/11/15');
    
    // タイムゾーン設定
    $date->setTimeZone( 'GMT' );
    
    echo str_replace(
    	$date->get( Zend_Date::GMT_DIFF ),      // search => '+0000'
    	$date->get( Zend_Date::TIMEZONE_NAME ), // replace => 'GMT'
    	$date->get( Zend_Date::RFC_1123 )
    );
    
    // -> 'Wed, 14 Nov 2007 15:00:00 GMT'
    
    
    ...でも、ちと面倒くさいなーー。このくらいのフォーマット用定数、用意しておいてよ > Zend_Date

    余談

    前にZend_Date取り上げた時のエントリのタイトル、「Zend_Dete」って...orz