ZendFrameworkのライブラリやドキュメントをコマンドラインからダウンロードするバッチファイル2009年06月10日 04時18分31秒

なんとなく作ってみました

いやー、我ながら毎度毎度くだらんと思うんだけど、Zend Frameworkのアーカイブやらドキュメントやらをコマンドラインでダウンロードするバッチを作ってみたり。ソースも載せてますが、こちらからダウンロードもできます。

元々は、ローカルのsvnリポジトリに、ZFの各リリース版を上書き→コミットしてバージョン間の差分を見てみようというくだらない思いつきが発端なんだけども、アーカイブページからちくちくとリンククリックしたりするのが面倒になってきたもので。いや、ダウンローダ使えばいいんだけどね。

バッチは難しいや

ソースはこんな感じ。

@echo off
set VER=%1
set EXT=%2
set KIND=%3
if "%EXT%"=="" goto setext else goto setkind
:setext
set EXT=zip
:setkind
if "%KIND%"=="doc" goto setman
goto setlib
:setman
set KIND=-manual-ja
set MSG=マニュアル
goto dl
:setlib
set KIND=
set MSG=ライブラリ
:dl
@echo Zend Framework %VER% の %MSG% をダウンロードします
wget http://framework.zend.com/releases/ZendFramework-%VER%/ZendFramework-%VER%%KIND%.%EXT%

ファイル名は「zfdl.bat」とか「zfdl.cmd」とか、適当に。

しかしバッチ書き慣れないせいか、えらく時間かかったうえにかなりグダグダしたソースだなぁ。難しいや。

あ、これ実行するには拙作のwget.js(+ラッパーのバッチファイル)が必要です。ラッパー書くのめんどくさい場合は最終行の

wget http:.....
cscript //nologo wget.js http://....
のように直接JS起動にしても大丈夫ですが。

使い方

引数を1~3つとります。

最初の引数はダウンロード対象のZFのバージョンを「x.x.x」形式で指定します。たとえば、ZF1.0.0を対象とする場合は、

zfdl 1.0.0
のように指定します。

第二引数はオプションで、ダウンロードするファイルの拡張子を指定します。指定可能な拡張子は「.tar.gz」または「.zip」の2種類のみで、省略時、または他の拡張子などを指定した場合は「.zip」になります。先頭にピリオドが必要な点に注意。

第三引数もオプションで、これに「doc」と指定するとドキュメントをダウンロードします。省略時または「doc」以外のパラメータを指定した場合はライブラリのアーカイブをダウンロードします。

コマンドのサンプル

例1:1.8.1のライブラリを.zipでダウンロード

zfdl 1.8.1

例2:1.6.2のライブラリを.tar.gzでダウンロード

zfdl 1.6.2 .tar.gz

例3:1.7.8のドキュメントを.tar.gzでダウンロード

zfdl 1.7.8 .tar.gz doc

例4:1.0.0のドキュメントを.zipでダウンロード

zfdl 1.0.0 "" doc
第二引数は二重引用符が2つ重なっているだけです。

forコマンドで連続ダウンロード

まぁ、そんな需要はまずないと思うのですが、forコマンドで指定バージョンを自動的に連続ダウンロードすることもできます。

まず落としたいバージョンを列挙した、以下のような感じのテキストファイルを用意しておきます。

1.0.0
1.0.1
1.0.2
1.0.3
1.0.4
1行に1つのバージョンを記述する形式です。

で、これをforコマンドのタネにします。たとえば上記のファイルを「ver.txt」として、.zipでマニュアルをダウンロードするには

for /f %v in ('type ver.txt') do @zfdl %v .tar.gz doc
のようにします。

バッチファイルそのものにもう少し手を入れれば、ダウンロード済みのファイルは落とさないようにしておいて、勝手に最新版をダウンロードするようにもできるような気もしますが、まぁそんな必要はないか。

追記

あれ?拡張子の先頭のピリオド、いらないじゃん

さらに追記

えと、元々wget.jsの作りからして、ダウンロードする内容をいったんすべてメモリに蓄えるので、ドキュメントのダウンロードに使い方絞ったほうが安全かもしれません(dara-jの環境では1.8.1をzip形式でDLできましたが)。

ついでにいうと、圧縮率考えると、zipよりもtar.gzのほうがお勧めです。

KomodoのZFテンプレートが修正されていた件2008年08月04日 12時03分50秒

前にKomodo Edit取り上げた時に「これ、大丈夫なんかいや?」と思っていたZFのプロジェクトテンプレートのアクションコントローラのファイル名、現時点の最新版の4.4.1では修正されてたみたいね。 リリース直後(7/24だか25だか)に何も考えず更新してたんだけど、ここしばらくは既存プロジェクトのメンテナンスばっかりで新しいコントローラ追加してなかったから気づかなかったわ。

さっきちょっとテストアプリ作ってみようと思って新規コントローラ追加したら、ファイル名が普通に大文字から開始されてて、おや?と思って改めてリリースノート見たら、

Zend Framework: MVC project template fixes. (Bug 78464, 78163)
ってあって、Bug 78464を見に行ってみたら
controller files need to be capitalized, eg IndexController, not indexController
って、ちゃんとバグとして報告あがってたのね。よかった、よかった。

Komodo Editが便利だけど微妙な件2008年07月01日 04時36分39秒

ひっさびさ(でもないか、図面の紹介だけど先月書いてたわ)のZFネタ。といってもツールというか、開発環境のお話なんですが。

文句言ってたころから久しく

昨年の終わりごろに、こんな感じでがっかりしていたオープンソースIDE「Open Komodo」、その記事から程なく「Komodo Edit」に統合され、着実にバージョンアップされていた。

んで、春ごろにMOONGIFTで紹介されたころから、まっとうに使えるようになってきていて(確か4.3ごろだったか)、少し触ってみてはいたのだが、先週リリースされた4.4からdara-j内で俄然使える予感がしてきた。

Zend Framework Support: Zend framework autocompletion and a new Zend MVC project template have been added.
そう、Zend Frameworkがサポートされたためだ。

プロジェクトテンプレート、試してみるべ。

個人的にはオレオレライブラリ込みの雛形をsvnで管理して、新しいアプリケーションを作るときにエクスポートして使っているため、最初はプロジェクトテンプレートにはあまり魅力を感じず、オートコンプリートの恩恵(+viキーバインド)のみの利用で、「やっぱproject.vimのがいいやね」とか思っていたのだが、せっかくプロジェクトテンプレートを用意してくれているので試してみることにした。

何はともあれ、Komodo Editを立ち上げてみる(インストール手順は、普通にさくっと入るのでは割愛)。

スタートアップ
(クリックで拡大)
うん、4.4 動かしてるのに4.4の宣伝ってどうなんだろう

で、[File] - [New] から、[New Project From Template...]を選択する。

メニュー選択
(クリックで拡大)
するってぇと、図のようにプロジェクト選択ダイアログが表れる。あるある、「Zend_MVC_Project」!
プロジェクトテンプレート選択
(クリックで拡大)
で、保存先のディレクトリとプロジェクト名を決めて「Open」クリック。

そうすると、メインウィンドウに制御が戻る前に、図のようなプロジェクト設定ダイアログが表示される。

プロジェクト設定
(クリックで拡大)
「Project Location」はそのまま先ほど選択したパスでよいとして、「Zend Framework Location」は、ZFのライブラリのトップ(Zendフォルダがあって、同じ階層にAcl.phpやら、Auth.phpやらが設置されているところ)を指定する。このパスを起点に探索してオートコンプリートに利用するのかな?

で、無事にメインウィンドウに制御が戻ると、プロジェクトツリーが図のようにずらずらと並んでいる。

ブートストラップ スクリプト
(クリックで拡大)
このテンプレートから起こしてくれたプロジェクトツリー、dara-jが普段採用している、トップのアプリケーションディレクトリを直接Webに公開しているような構成と違い、publicを仮想ディレクトリとして公開する構成になっている。

また、赤みがかったフォルダは実ディレクトリではなく、プロジェクトファイル(.kpfファイル)内に展開される仮想的なディレクトリなので念のため。

プロジェクトツリー

ここからは、プロジェクトツリーの主要なファイルの説明です。

bootstrap.phpとindex.php

先の図で選択している「bootstrap.php」が名前のとおりディスパッチループの起点スクリプト(拡大するとソースが読めます)。これが直接呼び出されるわけじゃなく、構成の意図としては先ほどの説明どおり public/index.php が起動の取っ掛かりなんだけども、index.phpは単にbootstrap.phpをrequireしているだけ(これまた図を拡大してソース嫁)。

index.php
(クリックで拡大)

アクションコントローラとビュースクリプト

で、 application/controllres下にアクションコントローラ、application/views/scripts 下にコントローラに対応するビュースクリプトを配置するのはZFのスタンダードな形態。

IndexControllerは特に説明するようなこともなく、最低限の実装になってます。

(クリックで拡大)
ビュースクリプトは、ちょっとした実装で動作確認できるよう、ちょっとした実装が含まれている模様。$this->formとかあるので、ZF1.5向けのコードかな?
index.phtml
(クリックで拡大)

余談になるが、アクションコントローラのファイル名規約で「camelCase」って大丈夫なんだろうか?Win上でしか確認してないけど、Winはファイルシステムでレターケースは関係ないので動いちゃうだけかもしれないし。ふむ。

※:4.4.1で修正されたので、最新版ではちゃんとCamelCaseになります。(08.08.04)

その他のディレクトリ

他のディレクトリ(modelsとかviews/filtersとか)はプレースホルダとしてディレクトリがあるだけで、気の利いたスクリプトを設置したりしてくれるわけじゃないけど、自分でビュースクリプトヘルパーとか設置する際に迷わずにすむので、まあありがたい。

また、これはよいなと思ったのは、libraryディレクトリ。先のbootstrap.php内でここをインクルードパスに加えているので、アプリケーション固有のライブラリはどんどんここに設置していける。

慣れていれば自分で同じようなことをするのでなんてことない話なんだけども、こういう工夫は余計なことに頭使わずに済むので特にZF触り始めにはありがたいかも(ただ、個人的に「Zend_Loader::registerAutoload()」はどうかと思うが)。

組み込みマクロとスニペット

さて、長い前振りになってしまったが、今回の記事はこの組み込みマクロとスニペットが便利なことを主張するのが目的だったわけだが。ごめん、それ言いすぎ。

先ほど触れた、赤っぽい「Project」フォルダには、3つのプロジェクトテンプレート組み込みマクロと、4つのスニペットが格納されている。これら、特に「New Controller」「New View」マクロは、動作としてはなんてことないがあるとないとでは作業効率が大違いになると思われる。

New Controller マクロ

New Controller マクロ
(クリックで拡大)
名前から推測つくように、このマクロを使って新しいアクションコントローラをプロジェクトに追加できる。

使い方は至極簡単で、Project/New Controller のアイコンをダブルクリックすると下図のようなコントローラ名入力ダイアログが表示される。

コントローラ名入力ダイアログ
ここではエラーコントローラを追加してみているが、名前を入力し「OK」するとこんな感じでちゃんと application/controllers にerrorController.phpが追加される。
ErrorController
(クリックで拡大)
またも余談になるが、ファイル名が「errorController.php」と camelCase になっているのは別にダイアログで「error」と頭小文字で入力したからではない。また別記事で取り上げるかも知れないが、New Controller マクロの実装で、入力した名前をtoLowerCase() しているため、必ずこうなってしまうのだ。重ね重ね、ファイル名規約的に大丈夫なんだろうか
※:4.4.1で修正されたので、最新版ではちゃんとCamelCaseになります。(08.08.04)

New View マクロ

こちらも名前どおり、新しいビュースクリプトをプロジェクトに追加するマクロ。

New View マクロ
(クリックで拡大)
起動すると、こんな感じで、コントローラ名とアクション名を入力するダイアログが表示されるので、必要なものを入力してOKすれば views/scripts/[コントローラ]/[アクション].phtml が追加される。
ビュースクリプト追加ダイアログ
余談ばっかりで恐縮だがこのダイアログ、コントローラ名がコンボっぽいのだが別にプロジェクト内のコントローラを列挙してくれるわけではない。まぁ既定のダイアログコンポーネントを使っているからこういうインターフェイスなんだろうけど、「おお、コントローラも列挙してくれるんか」って期待しちゃうのが普通だと思うのだが。

で、無事追加されましたと。

error.phtml
(クリックで拡大)

その他(つか、スニペット)

「Project」下にある「Abbreviations」以下にはZF向けのコードスニペットが4つぶら下がっている。

コードスニペット
(クリックで拡大)
まあ、これらもまた名前で想像つくだろうけど、bootstrap/index.phpやアクションコントローラ、ビュースクリプトのコードテンプレートが定義されていて、ダブルクリックすると現在のカーソル位置にコードを展開してくれる。

ただ、BootstrapStub/IndexStubは当然として、ZendController/ZendViewスニペットも単独ではまず起動することはないと思われる。というのも、これらのスニペットはテンプレート変数をマクロ内で置換して使うように設定されており、単独起動しても変数が展開されるわけではないからだ。

で、どのあたりが「微妙」よ?

ここまで長々と、マクロが便利だ何だと書いてきたわけだが、このエントリのタイトルは「Komodo Editが便利だけど微妙な件」。当然微妙に思っている部分があるわけだ。

アクションコントローラのファイル名が微妙

もうすでに書いたけど、IndexControllerクラスを定義するファイル名が「indexController.php」といった具合に、ファイル名規約的に微妙な出力をしてくれる。まぁ探した限りではこれでまずいって記述を見つけたわけではないんだけども、落ち着かん。

※:4.4.1で修正されたので、最新版ではちゃんとCamelCaseになります。(08.08.04)

「ZendView」スニペットが微妙

いや、当然こういうスニペットはありがたいんだけど、「ZendView」スニペットに定義されているDOCTYPE宣言、なぜか2文字目にバックスラッシュが残っていて、これじゃぜんぜん「XHTML 1.0 Strict」じゃないんじゃないかと。 直せばいいだけの話なんだけど、微妙。

ディレクトリ構成が微妙

うーんと、これは個人の好みやスキルの問題もあるんだけど、dara-jが普段使っている環境はポート8080設定の単独EXE起動のApacheだもんで、こういうディレクトリ構成で「public」のみを仮想ディレクトリとして公開した場合、うまいことrewriteできずに「Bad Request」になってしまうんだよね。リライトの定義がうまければ回避できるんだろうけどさ。

また、普通は自分でルータ書いたりしないので、ハナっから.htaccessがついてこないってのもなんとなく微妙。ビュースクリプト側で全部GETパラメータ指定しろと?それともZF1.5の世界ではmod_rewriteなしが標準の実行環境なのか?

まぁ、なんだかんだいっても便利よね

と、くだらないことでちくちくと文句を言ってみたが、やっぱりマクロでコントローラ/ビュースクリプトを追加できるのは大変便利(もちろんオートコンプリートもね)。

デフォルトで提供されるプロジェクトテンプレートが肌に合わないだけで、このマクロは充分利用できるので、今度はそのあたりを記事にしてみようかしら。ちゃんと書くかしら。

コントローラ関連のシーケンス図など2008年06月03日 02時56分26秒

久々のZFネタっす。といっても自分でなんか書いたりするわけじゃなく、よそ様で紹介されていた資料の紹介。すまぬ。

すばらしい図面が紹介されてた

PHPSPOTさんで紹介されてたZF-users.jpというZendFrameworkユーザ向けのハブサイトさんで、Zend_Controller/Zend_View関連の図面が紹介されてました。

このページで「シーケンス図 (http://www.kitpages.fr/zf_helper_plugin.php)」と「Zend_Controller と Zend_View 図でまとめ」という2つの図面が紹介されていて、どちらも非常に有用なのですが、特に「シーケンス図」がお勧め。個人的に。

ソース追っかけるときのお供に

この図、ブラウザからリクエストが発生して、Zend Frameworkのコントローラ群が処理を行ってブラウザにレスポンスを返すまでの流れをあらわしていて、これを片手にZend/Controller/Front.phpから流れを追っかけてくとコントローラ周辺の処理がよく理解できると思います。

ソース読めばまぁわかるんだけどもちと迷いそうなところだけ補足しておきます。

プラグインブローカー

まずカーキ色のボックス「Plugins」を呼び出しているところは、実際は Zend_Controller_Plugin_Broker (フロントコントローラ内の「_plugins」)に対する呼び出しです。ブローカーはこれらのメソッドを呼ばれると、自身に登録されている各プラグインクラスの同名メソッドを順次呼び出す仕組みになっています。この機構を利用すれば、例えばdispatchLoopStartupをフックして、特定条件下では必ずログインフォームへナビゲートさせるような認証プラグインなんかを実装することができます(フックメソッド中にリクエストオブジェクトのコントローラ名/アクション名を上書きしたりもできるので)。

ディスパッチループ

「12: preDispatch」~「28: postDispatch」まで(背景がカーキになっている部分)は「ディスパッチループ」で、特定の条件下、例えばアクションコントローラで _forward() した場合は再びpreDispatchへ処理が移るような流れになっています。

具体的な処理についてはソースを見てみてください。Zend_Controller_Action::_forward でリクエストオブジェクト(Zend_Controller_Request_Abstract)に対して setActrionNameメソッドで次に実行するアクションを登録し、 setDispatchedメソッドにfalseを渡しているのがわかると思います。この「setDispatched(false)」が、ディスパッチループを繰り返すという決定を下している部分になります。

その他の部分

その他の部分は特に解説はいらないと思います。「Helpers」(=アクションヘルパー)の部分は考え方としてはプラグインブローカー/プラグインと同様に捉えられると思いますので。

まとめ

なんて具合に人様のコンテンツの尻馬に乗ってお茶を濁しましたが、本当はいずれこういうシーケンス図を書くつもりだったんですよ、いや、まじで。(つか、リファレンスマニュアルにこういうの載せてくれればいいのに)

今回紹介されていた2つの図面を見るとコントローラ(とビュー)の基本クラスの関連はぐっと把握しやすくなり、ソースを追っかけた場合の混乱も軽減されると思うので、ぜひ印刷するなりで手元においておいて、さらにZFのソースを読んでみることをお勧めします。

PHPのバージョンでZend_Jsonの動作が違っていた件2008年04月06日 04時05分27秒

Zend_Json::decode()って便利!と思っていたが

Zend_Json::decode()が割と使える。UNICODEエスケープされた文字(\uXXXX形式ね)をデコードできるからだ。

<?php
require_once 'Zend/Json.php';

$s = '"\u65e5\u672c\u8a9e"';
echo Zend_Json::decode( $s );

とかってすると、「日本語」と出力が得られる(内部エンコードがutf-8じゃないとあかんみたいだが)。

だもんで、クライアント側でJSでescape()したマルチバイト文字を、preg_replace_callbackを絡めて'%uXXXX'を'\uXXXX'に変換した上でダブルクォートで囲ってZend_Json::decode()に渡すようにして復元したりしていた。

が、Zend_Jsonの動きをたいして気にしていなかったため、ちみっとハマった。

PHPのバージョンの違いで、なんかヘン。

ある環境では上記のようなデコード処理がまったく問題なく動いていたのだが、他の環境で動かしたとたんに'\uなんて不正なエスケープだ!'とエラーがでるようになった。Zend Frameworkのバージョンはどちらも「1.0.0」を使っているのに。

文字コードの関連も、実行環境に依存しないように必ずdefault_charsetとmbstring.internal_encoding、mbstring.http_outputをコード中で指定してutf-8にあわせてあるし、違いといえばPHPのバージョン。

ためしに、

<?php
require_once 'Zend/Json.php';

$s = '日本語';
echo Zend_Json::encode( $s );

なんてのをやってみたところ、正常に動作する環境は
"\u65e5\u672c\u8a9e"
とUNICODEエスケープで出力されたが、うまく動かない環境のほうでは
"日本語"
と、まんまで出力されている。はて。

ソースを覗いてみたら

エンコード部分で動作に違いがでたので、Zend/Json/Encoder.php(Zend_Json_Encoder)のソースを見てみた。該当するのは _encodeString プロテクトメソッドか。

/**
 * JSON encode a string value by escaping characters as necessary
 *
 * @param $value string
 * @return string
 */
protected function _encodeString(&$string)
{
    // Escape these characters with a backslash:
    // " \ / \n \r \t \b \f
    $search  = array('\\', "\n", "\t", "\r", "\b", "\f", '"');
    $replace = array('\\\\', '\\n', '\\t', '\\r', '\\b', '\\f', '\"');
    $string  = str_replace($search, $replace, $string);

    // Escape certain ASCII characters:
    // 0x08 => \b
    // 0x0c => \f
    $string = str_replace(array(chr(0x08), chr(0x0C)), array('\b', '\f'), $string);

    return '"' . $string . '"';
}

(Zend/Json/Encoder.php より抜粋)
はて、マルチバイト文字の扱いとか特にやってる風ではないな。どないなっとんねん。

じゃあ、実際にコードから叩いているZend_Jsonのほうを見てみるか。

/**
 * Encode the mixed $valueToEncode into the JSON format
 *
 * Encodes using ext/json's json_encode() if available.
 *
 * NOTE: Object should not contain cycles; the JSON format
 * does not allow object reference.
 *
 * NOTE: Only public variables will be encoded
 *
 * @param mixed $valueToEncode
 * @param boolean $cycleCheck Optional; whether or not to check for object recursion; off by default
 * @return string JSON encoded object
 */
public static function encode($valueToEncode, $cycleCheck = false)
{
    if (function_exists('json_encode') && self::$useBuiltinEncoderDecoder !== true) {
        return json_encode($valueToEncode);
    }

    require_once 'Zend/Json/Encoder.php';
    return Zend_Json_Encoder::encode($valueToEncode, $cycleCheck);
}

(Zend/Json.php より抜粋)
ほあ!?json_encode()??

はぁ、PHP5.2.0からだったのねん

同様にZend_Json::decode()部分もjson_decode()が存在していたらそっちを利用するようになっていた。調べてみるとこの2つの関数は、JSON関数として、PHP5.2.0からは標準でインストールされるようになったPECL拡張モジュールで提供されている関数だったと。

先ほどの2つの環境、うまく動かないほうは5.1.6、正常なほうは5.2.5だもんで、なるほどこの通りになるのか。

先ほどのjson拡張モジュール自体はPHP4.3.0以降に適合するので、それをインストールすれば同様の動作になるけど、Zend_Json関連を使うときは一応PHPのバージョンを気にしておいたほうがよいかも。

オマケ

前半部分でescape()した文字のデコード目的で使用、ってな話を書いたけど、「escape()って必ずUNICODEエスケープなのか?」ってのに自信がなくなったので調べてみたら、こんな一覧表が見つかった。

なるほど現在普通に使われるようなブラウザならたいていUNICODEエスケープとみて間違いないかな(MacユーザでiCab使ってる人いたらごめんなさい)。