2007-02-03 (土)
■ Mozex 拡張機能のインストール
Firefox で Textarea の編集時に外部エディタを使えるように、 Mozex という拡張機能をインストールしてみた。 インストール後、 Mozex の設定画面を出して Textarea タブの以下の部分を設定。
- Edit textbox in UTF-8 にチェックを入れる
- Text editor: の項目を以下のように設定
C:\Progra~1\TeraPad\TeraPad.exe /cu8n /el %t
最初 Mozex の起動時にエラーが出て動かなかったが、 "Program Files" と書いていたのをこのように Progra~1 と書き直すことで、 動かすことができた。
2007-02-04 (日)
■ geshi.inc.php, highlighter.inc.php プラグイン Ver.0.7 をリリース
geshi.inc.php プラグイン の方の変更点は以下の通り。
- sonots 氏のパッチ を取り込み、設定ファイルの機能を追加
- 行番号の表示機能を一応使えるように
highlighter.inc.php プラグイン の方も、 それに合わせて行番号の表示機能を追加しておいた。
なお、今回の件では sonots さんに色々とお世話になりました。多謝。
2007-02-11 (日)
■ Simplate における変数表記のサポート範囲
Smarty のマニュアルを見ると、 以下のような説明が書かれている。
{$foo} <-- displaying a simple variable (non array/object)
{$foo[4]} <-- display the 5th element of a zero-indexed array
{$foo.bar} <-- display the "bar" key value of an array, similar to PHP $foo['bar']
{$foo.$bar} <-- display variable key value of an array, similar to PHP $foo[$bar]
{$foo->bar} <-- display the object property "bar"
{$foo->bar()} <-- display the return value of object method "bar"
Simplate がこれらの書き方のうち、 どこまでサポートしているのかを確認しておきたかったので、実験してみた。
まず、以下のような PHP のスクリプトを用意。
<?php
require_once 'Smarty/Smarty.class.php';
class tmpClass
{
var $var = 'class_var';
function method($param = '')
{
echo "class_method('$param')";
}
}
$var = 'var';
$array = array('var' => 'array_var');
$class = new tmpClass();
$smarty = new Simplate();
$smarty->assign('var', $var);
$smarty->assign('array', $array);
$smarty->assign('class', $class);
$smarty->display('test.tpl');
?>
テンプレートの test.tpl には、以下のように書いてみる。
{$var}
{$array.var}
{$class->var}
{$class->method()}
{$class->method($var)}
これをコンパイルすると、Smarty も Simplate も以下のような HTML を出力する。
<?php echo $this->_tpl_vars['var']; ?> <?php echo $this->_tpl_vars['array']['var']; ?> <?php echo $this->_tpl_vars['class']->var; ?> <?php echo $this->_tpl_vars['class']->method(); ?> <?php echo $this->_tpl_vars['class']->method($this->_tpl_vars['var']); ?>
結果として、画面には以下のように表示される。
var
array_var
class_var
class_method('')
class_method('var')
ここまではOK。
次に、テンプレートに以下のように書いてみる。
{$array.$var}
{$class->$var}
これをコンパイルした場合の結果は、Smarty と Simplate とで異なる。 Smarty の場合、
<?php echo $this->_tpl_vars['array'][$this->_tpl_vars['var']]; ?>
<?php echo $this->_tpl_vars['class']->{(($_var=$this->_tpl_vars['var']) && substr($_var,0,2)!='__') ? $_var : $this->trigger_error("cannot access property \"$_var\"")}; ?>
Simplate の場合、
<?php echo $this->_tpl_vars["array"]["$var"]; ?> <?php echo $this->_tpl_vars['class']->$var; ?>
つまり、$var というのが global スコープの変数でない限り、 Simplate での表示はおかしなことになる。 試していないが、{$class->$methodName()} のような書き方も恐らくダメだろう。
ということで、通常の使い方ならまず大丈夫だが、 あまり複雑なテンプレートの書き方をすると、 Simplate では使えないことがあるので注意しよう。
2007-02-12 (月)
■ geshi.inc.php, highlighter.inc.php プラグイン Ver.1.0 をリリース
geshi.inc.php プラグイン と highlighter.inc.php プラグイン が ようやく形になったと思うので、バージョン番号も 1.0 に上げて正式公開する。 変更点は以下の通り。
- プラグインの引数でオプションを指定できるように
まだ何とかしたい部分はあるが、とりあえずこれで一段落、ということにしよう。
2007-02-15 (木)
■ PEAR::Pager を継承して使う (2)
以前書いた話 の続き。 前回の書き方でも問題なく動作はするのだが、 PHP 5 の場合に E_STRICT レベルのエラーが大量に発生してしまう。 Jumping か Sliding のどちらかのモードしか使わないという制限付きでよければ、 以下のような書き方をすることで E_STRICT 互換のライブラリのように使うことが出来る。
<?php
ini_set('display_errors', 0);
require_once 'Pager/Sliding.php';
ini_restore('display_errors');
class Pager extends Pager_Sliding
{
public function __construct($options = array())
{
$defaults = array(
'altFirst' => '最初のページ',
'altPrev' => '前ページ',
'altNext' => '次ページ',
'altLast' => '最後のページ',
'altPage' => 'Page',
);
return parent::__construct(array_merge($defaults, $options));
}
}
$numItems = getNumItems();
$options = array(
'totalItems' => $numItems,
);
$pager = new Pager($options);
$links = $pager->getLinks();
echo $links['all'];
?>
この書き方のポイントは以下の2点。
- 直接 Pager_Sliding クラスを継承して使う
- require_once の前後で display_errors ディレクティブの設定を一時的に変更する
Zend Framework 用に書いたコードの一部を抜き出して載せたが、 おそらくこの書き方で汎用的に使えるはず。
2007-02-17 (土)
■ Piece Framework のサンプルカウンタ
Piece Framework を試してみている。 Piece_Flow のアーカイブの中にあったテスト用のファイルを少し手直しして、 ごくごく簡単なサンプルを作ってみた。 カウンタというか、へぇボタンみたいなやつだ。 ただこのサンプルは、必ず同じ state に戻って来るという点があまりよろしくない。 そのせいで「戻る」ボタンを押した時の動作がおかしいが、 あくまでも練習ということで大目に見て欲しい。
- config/flows/Counter.yaml
firstState: DisplayForm
viewState:
- name: DisplayForm
view: Counter
activity:
method: setView
transition:
- event: increase
nextState: done
action:
class: CounterAction
method: increase
- event: reset
nextState: done
action:
class: CounterAction
method: reset
actionState:
- name: done
transition:
- event: succeed
nextState: DisplayForm
initial:
class: CounterAction
method: initialize
- actions/CounterAction.php
<?php
require_once 'Piece/Flow/Action.php';
class CounterAction extends Piece_Flow_Action
{
function initialize()
{
$this->_flow->setAttribute('counter', 1);
}
function increase()
{
$counter = $this->_flow->getAttribute('counter');
$this->_flow->setAttribute('counter', $counter + 1);
return 'succeed';
}
function reset()
{
$this->initialize();
return 'succeed';
}
function setView()
{
$counter = $this->_flow->getAttribute('counter');
$viewElement =& $this->_payload->getViewElement();
$viewElement->setElement('counter', $counter);
}
}
?>
- templates/Counter/Counter.html
<p>Counter: {counter}</p>
<form>
<input type="hidden" name="{__flowExecutionTicketKey}" value="{__flowExecutionTicket}" id="flowExecutionTicket" />
<input type="submit" name="{__eventNameKey}_increase" value="Increase" />
<input type="submit" name="{__eventNameKey}_reset" value="Reset" />
</form>
あとは piece-unity-config.yaml にこのフローを追加するといった作業も必要だが、 とりあえずこれはこれでちゃんと動作する。
さて、ここからが本題。 マニュアルに書かれているアクション継続の説明を読んで、 その時は、ふーん、と思っていたのだが、 しばらく経った後で、書かれていることの意味が頭に染み渡ってきた。 フロー変数を意識する必要が無いだって? もしかして、アクション継続の機能を有効にすればこういう風に書けるのか?
<?php
require_once 'Piece/Flow/Action.php';
class CounterAction extends Piece_Flow_Action
{
var $_counter;
function initialize()
{
$this->_counter = 1;
}
function increase()
{
$this->_counter++;
return 'succeed';
}
function reset()
{
$this->initialize();
return 'succeed';
}
function setView()
{
$viewElement =& $this->_payload->getViewElement();
$viewElement->setElement('counter', $this->_counter);
}
}
?>
試してみたところ、これでちゃんと動いた。こりゃすごいや。
要するに、今までの通常の Web アプリだったら、 セッションに値を保存しておいて、 次の画面でセッションから値を取り出して、 といったことをほぼ手作業でやっていたのに、 そういった処理を何も書かなくて済む。 Piece Framework はステートフル、と書かれていたのがようやく実感できた。
2007-02-20 (火)
■ イベント名を取得する部分の謎の処理
Piece Framework で $_POST (や $_GET) からイベント名を取得する部分の処理は、 以下のようなコードになっている。
class Piece_Unity_Context
{
/**
* Imports an event name from the submit by a submit or a image.
*
* @since Method available since Release 0.9.0
*/
function _importEventNameFromSubmit()
{
$xFound = false;
$yFound = false;
foreach ($this->_request->getParameters() as $key => $value) {
if (preg_match("/^{$this->_eventNameKey}_(.+)$/", $key, $matches)) {
$eventName = $matches[1];
$lastTwoBytes = substr($matches[1], -2);
if ($lastTwoBytes == '_x') {
$xFound = true;
$xEventName = substr($matches[1], 0, -2);
if ($yFound) {
break;
}
} elseif ($lastTwoBytes == '_y') {
$yFound = true;
$yEventName = substr($matches[1], 0, -2);
if ($xFound) {
break;
}
} else {
$this->setEventName($eventName);
return;
}
}
}
if ($xFound || $yFound) {
if ($xFound && $yFound && $xEventName == $yEventName) {
$this->setEventName($xEventName);
} else {
$this->setEventName($eventName);
}
}
}
}
この _x だの _y だのというのは何だろう? 変数名やコメントからすると、どうも x 座標、y 座標っぽい気がするんだが。 いつものように検索してみたところ、以下のページを見つけた。
要約すると、
- <input type="image" name="foo"> のような場合は foo_x, foo_y という変数が渡される
- それぞれ画像のクリックポイントの座標を示している
だから _x, _y を取り除いたものをイベント名としているわけか。納得。
ただ、 http://www.gac.jp/article/index.php?stats=question&category=11&id=19634&command=msg に気になる書き込みを見つけた。
ただIEではtextboxが一つしかないと EnterボタンでSubmitした時に ksearch_xが送られてこないというバグ?があるようです。 (※WindowsXPIE6で確認)
これは、ksearch_y しか送られてこない、という意味なんだろうか? だとしたら foo_x か foo_y のどちらかを見つけた時点で、 それをイベント名とする方がいいのかな?
2007-02-21 (水)
■ Piece_Unity の処理の流れ
Piece Framework の継続サーバの機能を、 他のフレームワーク(というか Zend Framework)で使えるようにできないかと思い、 Piece_Unity の処理の流れを最初から追ってみた。
かなりはしょって書いてしまうが、htdocs/〜.php にアクセスした後は、 以下のような順で処理が行われていく。
- Piece_Unity::dispatch()
- Piece_Unity_Plugin_Root::invoke()
- Piece_Unity_Plugin_Controller::invoke()
ここで Piece_Unity_Plugin_Controller::invoke() は、
class Piece_Unity_Plugin_Controller extends Piece_Unity_Plugin_Common
{
function invoke()
{
if (is_null($this->_context->getView())) {
$dispatcher = &$this->getExtension('dispatcher');
$viewString = $dispatcher->invoke();
$this->_context->setView($viewString);
}
$dispatcherContinuation = &Piece_Unity_Plugin_Factory::factory('Dispatcher_Continuation');
$dispatcherContinuation->publish();
$view = &$this->getExtension('view');
$view->invoke();
}
}
のようになっている。 フローの機能を使う時は dispatcher が Dispatcher_Continuation に設定されているので、 $dispatcher->invoke() の部分は、
- Piece_Unity_Plugin_Dispatcher_Continuation::invoke()
- Piece_Flow_Continuation::invoke()
- ……
のように処理が進んでいく。
あれ? Piece_Flow_Continuation? そんなクラスがあるのか。
ここでひとまず上のコードの部分に戻ってみると、 $dispatcherContinuation->publish() はたいしたことはしていないし、 後はそのまま、
- Piece_Unity_Plugin_View::invoke()
- Piece_Unity_Plugin_Renderer_xxx::invoke()
のように画面 (や JSON) への出力の処理に進んでいく。 だから、Piece_Unity_Plugin_Dispatcher_Continuation クラスが肝のはず。 しかし Piece_Unity_Plugin_Dispatcher_Continuation クラスを眺めてみても、 フローの制御とか、イベントの実行とか、そういった処理がどこにも書かれていない。 考えてみたら、そもそもフローの設定ファイルすら読み込んでいない。
もしかして、Piece_Flow_Continuation クラスが処理の本体で、 Piece_Unity_Plugin_Dispatcher_Continuation クラスはその使用例みたいなもの? Piece_Unity_Plugin_Dispatcher_Continuation 相当の処理さえ書けば、 あとは Piece_Flow の方で全て面倒を見てくれるのか? Piece_Flow のコードをざっと見てみると、どうもそんな気がする。 それならば意外と簡単に実装できるかもしれない。
2007-02-22 (木)
■ Zend Framework 0.8.0 がリリース
されている。 今回のバージョンは変更点がかなり多い。 特に影響が大きそうな点としては、ディレクトリ構造が整理されたため、
- Zend_Controller_RewriteRouter → Zend_Controller_Router_Rewrite
- Zend_Controller_Dispatcher → Zend_Controller_Dispatcher_Standard
のようにいくつかのクラス名が変更されている。 この辺が従来とは非互換になっているので、 気をつけないといけない。
(追記)
Zend_Session クラスも使い方が変更されている。 new Zend_Session(__CLASS__) のようにしていたのを、 new Zend_Session_Namespace(__CLASS__) に修正する必要があった (2007/02/23)。
2007-02-23 (金)
■ __PHP_Incomplete_Class の対処法
Zend Framework で Piece_Flow を使おうプロジェクトのコーディング中。
1回目のアクセスには成功する。 continuation のオブジェクトも作られて、 それがセッションにちゃんと保存されている。 しかしそこから画面遷移すると、以下のようなエラーが出てしまう。
Fatal error: Revulo_Controller_Dispatcher_Flow::invoke() [<a href='function.Revulo-Controller-Dispatcher-Flow-invoke'>function.Revulo-Controller-Dispatcher-Flow-invoke</a>]: The script tried to execute a method or access a property of an incomplete object. Please ensure that the class definition "Piece_Flow_Continuation" of the object you are trying to operate on was loaded _before_ unserialize() gets called or provide a __autoload() function to load the class definition in /home/revulo/flowtest/library/Revulo/Controller/Dispatcher/Flow.php on line 90
セッションに保存したオブジェクトを復元する処理が上手くいっていないようだ。 セッションの中身をのぞいてみると、なにやら妙なことになっている。
Array
(
[continuation] => __PHP_Incomplete_Class Object
(
[__PHP_Incomplete_Class_Name] => Piece_Flow_Continuation
__PHP_Incomplete_Class というキーワードで検索してみると、 unserialize() がどうこうとかいろいろ書かれていたが、 少し試行錯誤してみたところ、 どうも session_start() する前にクラスが定義されていないといけないらしい。 require_once 'Piece/Flow/Continuation.php'; というのを、 session_start() の直後に書いた場合は __PHP_Incomplete_Class のままだが、 session_start() の直前に書くと Incomplete の表示が消えてくれた。
そうして先に進めていったところ、 また同じようなエラーが出たので、セッションの中身を見てみると、 まだ他にも __PHP_Incomplete_Class になっているクラスがあった。
[__PHP_Incomplete_Class_Name] => Zend_Controller_Request_Http [__PHP_Incomplete_Class_Name] => CounterAction
とりあえず以下のように片っ端から強引に追加してやるとエラーは出なくなった。
require_once 'Piece/Flow/Continuation.php'; require_once 'Zend/Controller/Request/Http.php'; require_once '../application/controllers/CounterAction.php';
ただこれだと、 最後のアクションクラスの部分が決めうちになってしまっている。 そこで、手作業で require する代わりに、
function __autoload($class)
{
Zend::loadClass($class);
}
みたいなコードを追加してみたところ、それで動くことを確認できた。
しかし、1ライブラリが、 こうやって __autoload() を定義してしまっていいんだろうか? unserialize 関数のマニュアル を読むと、どうやら __autoload() 関数の代わりに unserialize_callback_func ディレクティブというのが使えるらしい。 ただし、
3) It does not appear to be possible to use a static member fuction of a class (for example, a your object persistence layer) as the unserialize callback function, so this will cause confusion
ということらしく、
ini_set('unserialize_callback_func', 'Zend::loadClass');
みたいな指定は不可。
ini_set('unserialize_callback_func', 'loadClass');
function loadClass($class) {
Zend::loadClass($class);
}
のように関数を用意して指定してやることで動くようになった。
2007-02-24 (土)
■ フロー名の指定方法について
Zend Framework で Piece_Flow を使おう計画の進捗。 まだ絶対に不具合があると思うが、なんだかそこそこ動くようになった。 今はまだフローの名前や定義をコード中に埋め込んで動かしているので、 その辺を自由に指定できるようにしてやらないといけない。 さて、どう実装していこうか?
Piece_Unity はページコントローラ方式なので、 htdocs ディレクトリ内の各ファイルでフロー名を指定しているが、 Zend Framework はフロントコントローラ方式なので、 index.php にフロー名を書いてしまうわけにはいかない。
Zend Framework で指定できるのは、 URL を分解して得られる Controller, Action 名やその他のパラメータ。 パラメータの部分でフロー名を指定するという案も考えられるが、 今やろうとしているのは、 同じ Controller、同じ Action でも条件に応じて別のイベントをやらせよう、 ということなので、 Controller, Action 名の組み合わせをフロー名のように扱う方が、 考え方としてはスマートだろう。
結局考えたのは、フローの定義をする際に、 フロー名と Controller, Action 名との対応関係を書いておくこと。
name: CounterTest controller: counter action: index file: CounterTest.yaml
のようにフローの定義をしておいて、 http://example.com/counter/index のような URL にアクセスしたら、 flowName = CounterTest が指定されたものとしてフロー開始、 という動作にしようと思う。
2007-02-25 (日)
■ Zend Framework で継続サーバ Piece_Flow を使う
ということで、 ここ数日取り組んでいた物が一応形になったと思うので公開してみる。 専用ページを作って、説明も書いておいた。
これを使えば、 Piece Framework のようなステートフルなプログラミングが、 Zend Framework でも一応できるようになる。
エラー処理を書いていなかったり、オプション指定が効かない部分があったり、 はっきり言ってまだアルファ版の状態だが、 少なくとも付属のサンプルは問題なく動作しているので、 興味のある人は試してみて欲しい。
なお、もう少し作りこんでいくつもりだが、 使ってみて不具合(や実装上の勘違い)に気付いたら、 ぜひ教えて下さい。お願いします。
2007-02-26 (月)
■ Revulo-Controller-Dispatcher-Flow クラス Ver.0.2 をリリース
昨日リリースしたばかりの Revulo_Controller_Dispatcher_Flow クラス ですが、ちょっとバージョンアップしておきます。
前バージョンでは、Zend_Controller_Action クラスでなく Piece_Flow_Action クラスを継承して使っていたため、 getRequest() や _redirect() などのメソッドが アクションコントローラ中で使えませんでしたが、 それらが使えるように手を加えました。
Piece_Flow のコード自体をいじる必要があるかと思っていましたが、 Zend_Controller_Action クラスにいくつかメソッドを追加した Revulo_Controller_Action クラスを作り、 ちょっと細工してやることでなんとかなりました。
これで Zend Framework ユーザーにとっても、 それほど違和感なく使えるようになったと思います。
と、ここからはいつもの文体で。
もうしばらくひっそりとやっていくつもりでいたが、 Piece Framework のページ とか PHPプロ!ニュース とか、 何ヶ所かで紹介されたようだ。 フィードバックを期待して公開しているので、紹介されるのは嬉しいが、 いろいろ鋭い指摘が来たりしないかと、ちょっと冷や汗ものだったりする。
ついでに書いてしまうと、実は手元では Piece_Right も Zend Framework に組み込んでみている。 これもそのうち公開できるように整理していきたい。

# ITEMAN [こんにちは。 この件、気になったので調査しました。 結果的には、xもyも送信されないことがわかり、トラブルシューティ..]
# revulo [はじめまして。Piece Framework 使わせて頂いております。 リンク先を拝見しましたが、やはり IE6 固..]