タイトル/名前 | 更新者 | 更新日 |
---|---|---|
日記/2007/07/12/Xhwlayメモ | msakamoto-sf | 2008-12-23 00:04:00 |
日記/2007/07/07/Xhwlayメモ | msakamoto-sf | 2008-12-23 00:00:17 |
日記/2007/07/01/Xhwlayメモ | msakamoto-sf | 2008-12-22 23:56:37 |
日記/2007/06/07/Xhwlayメモ | msakamoto-sf | 2008-12-22 23:51:05 |
PHP/Tips/エラーレベル | msakamoto-sf | 2008-12-22 23:45:44 |
PHP/Tips/20051016/外部定義された変数を関数内でunset()するときの罠 | msakamoto-sf | 2008-12-22 23:37:21 |
PHP/Tips/20060304/OSの判定とSmartyのdate_formatの罠 | msakamoto-sf | 2008-12-22 23:33:57 |
PHP/Tips/20050714/角括弧と波括弧の裏技 | msakamoto-sf | 2008-12-22 23:16:47 |
日記/2008/12/22/やってる事、昔から変わってないかも | msakamoto-sf | 2008-12-22 00:28:58 |
PHP/IniConfig_Variable | msakamoto-sf | 2008-12-21 23:52:14 |
とりあえず、getInstanceでインスタンス毎に分離できる形にして Xhwlay_Hook, TestCaseまで一通り終了。以降はいよいよ心臓部であるRunnerを作り直す。恐らくAbstractは消えて、Xhwlay_Runner_Baseみたくリネームされるのではないか?Invokerをとりあえず実装しちゃう。これによりAbstractな箇所が多分、無くなるはずだから。
あと重要な意志決定をいくつか。
実際の使い方を考慮すると、pushする順とはすなわち、設定ファイルなどに記述する順となる場合が圧倒的多数だろう。
例えばyamlファイルに以下のように記述したhookを実行するような実装をdeveloperが作ったとして、実行順序がこの逆になるような仕様が果たして分かりやすく使いやすいと言えるだろうか?直観的で誤解を招かないと言えるだろうか?
- hooks: - user_hook: - hook1 - hook2 - hook3
こうした場合、もっとも自然な実行順序は設定順、すなわち先にpushした順であるのは殆どの人が同意するはずである。
もう一つの消極的理由としては、実行順序を将来的に変更するようになった場合、後方互換性を確保する為、恐らくattributeに順序を設定するのでは無かろうか?そうなると、恐らくinvoke()やその周りのインターフェイスを変える必要はなくなり、単にsetAttributeすれば良いだけになるだろう。
となると、現行のテストケースがそのまま使える。
変更箇所はおそらくinvoke()の内部だけに留められる筈であろう。であるならば、本当に必要になったその時に実装を考えても、今のところは困らないと考えた。
他に考慮したケースは、システム側でpushしておいたHookに、developerコードが追加でpushしたい場合。
class hoge_fw_runner { function init() { $h =& Xhwlay_Hook('init'); $h->pushCallback(array(&$this, "start_session")); $h->pushCallback(array(&$this, "restore_user_context")); } function run() { $h =& Xhwlay_Hook('init'); $h->invoke(); ... } }
上記のようなクラスを用い、例えば次のように実行する場合。
<?php require_once('your_framework_files'); /* * エントリポイント用の各種設定コード・処理 */ hoge_fw_runner::run(); ?>
この場合、run()メソッドを呼ぶ前に "init"HOOKにdeveloper-hookをpushできるか?させるべきか?を考える。
安直なプログラマーであれば、run()する前に自分で"ini"HOOKにpushしてしまうかも知れない。
$h =& Xhwlay_Hook("init"); $h->pushCallback(...); hoge_fw_runner::run();
しかしこれでは、hoge_fw_runner::init()中の設定と干渉する。
もしinvoke順を、First-Push, Last-Calledにしておく、あるいは選択できるのであればこれに対応できる。
しかし、果たしてそうするべきだろうか?init()の中では実際に、First-Push, First-Callを期待して、先にstart_sessionが呼ばれるように調整してあるというのに!!
$h->pushCallback(array(&$this, "start_session")); $h->pushCallback(array(&$this, "restore_user_context"));
こうした場合、一番王道な方法はhoge_fw_runner自体を派生し、init()をオーバーライドするべきだろう。
<?php require_once('your_framework_files'); class custom_fw_runner extends hoge_fw_runner { function init() { hoge_fw_runner::init(); // PHP5なら、parent::init() で済む? $h =& Xhwlay_Hook("init"); $h->pushCallback('your_own_hooks'); } /* * エントリポイント用の各種設定コード・処理 */ custom_fw_runner::run(); ?>
どうしても"start_session"の前にオリジナルのHOOKを入れたいのであれば、親クラスのinit()を呼ぶ前に入れればよい。
function init() { $h =& Xhwlay_Hook("init"); $h->pushCallback('your_own_hooks_1'); hoge_fw_runner::init(); // PHP5なら、parent::init() で済む? $h->pushCallback('your_own_hooks_2'); }
このような考慮の結果、特にinvoke順は First-Push, FirstCallでも問題ないとの結論に達した。
まずコードと仕様が複雑になる。escape()と組み合わされた場合も考慮すると、いささかややこしい。
で、その複雑なコードとややこしさが、果たして実際の実装要求と等価か?と考えると、不安。
つまり、現状まだそこまで使うようなシーンが思い浮かばないのだ。「できたらいいな」ではあるが、しかしその実体は「理論的に可能な遊び」に近い。従って、まだ本気で取り上げるべきでもない。
一応その下準備として、invoke()中では一旦hook-callbackのstack配列をコピーし、そのコピーでもってinvokeチェインを回している。
$copy_hook_stacks = $this->_callbacks; foreach ( $copy_hook_stacks as $callback) { ... }
みたいな感じ。これにより、万一HOOK中でpush/popCallbackが行われても影響(被害?)を押さえられる。回しているのはコピーされたHOOKだからだ。
もしも本気で初期の構想で使いたい場合は、HOOK中からオリジナルのHOOKを呼び、escape()するのが良いかも知れない。
これは構想のミス。当初はオリジナルのパッケージで挙げられたErrorStackを検出しようとしたが、Xhwlay_ErrorStackではパッケージ名でエラーを取得できなかった・・・のを、忘れてた。つまり、Xhwlay_ErrorStackでは不可能。
なので、escape()に一本化することにした。まあ、Invoking中のHOOK-Chainを止めるのはdeveloperの明確な意志に基づく、ということで。
と言うことで午前半休にして、Xhwlay_Runnerに取りかかる。
とりあえず、RendererにstdClassでaci, pageName, bookmarkContainerId割り振っていたけど、いろいろ考えて、止める。
代わりにXhwlay_Varに
XHWLAY_VAR_NAMESPACE_VIEW
を追加して、これにセットするようRunnerを修正。それに伴い、AbstractRendererと、唯一getRequest()使っていたIncludeを修正し、SimpleTestまで終了。
あと、Rendererでsetup()やterminate()はHOOK実行に実装。また、setup()後にaci/pageName/BookmarkContainerId取ってるところを、ホワイトリストで文字種チェックをかけるよう、 _cleanup_outparam()というメソッドを追加してそれを通すよう修正。
また、これらのアウトパラムの最大文字数をRunnerに定義し、_cleanup_outparamでは "/ ... /m"で複数行対応でチェックするよう実装。
ただし_cleanup_outparamは未テスト。
invokePage, Barrier系は実装する旨TODOで書き付けておく。
他、AbstractRunnerは Xhwlay_Runnerにリネームし、ディレクトリを一つ上に上げた。Testも追随。&br;
但し、まだ古い方は削除するのちょっと待ち。
そんな感じ。
全部staticなのが、ちょっと、ね・・・。一番いただけないと気づいたのは、HOOKポイントの中からHook操作を行う場面を想定した場合。今の形だと、Hook操作用のstaticメソッドは全て、HOOK名を指定する。
→Hookポイント自身が、どこで使われるか把握していないといけない。
・・・う~~ん・・・これは、さすがに、ペケ。HookポイントはどのHOOKに設定されるか分からないのが基本。まあ確かに、HOOKの性質からどのPOINTにHOOKされるのかは限定されるのは確かだけど、名前までHOOKポイントの中で知っていなければならないのは、硬直が過ぎる。
・・・ああ・・・HOOK名をHOOKのI/Fに渡しても、良いのか。いや、駄目かも。
ただ、Xhwlay_HookはXhwlay_Varとは大分性質が異なる。かなり能動的なクラスであり、インスタンス化して使用するとしても不自然ではない。
getInstanceの内部でFactoryContainer化することは十分に可能だし。
Xhwlay_Runnerレベルで使用するのと、その上のApplication layerで使用するのを分離するのも、インスタンスを分けることでより、間違いを減らすことが可能だ。
つまりXhwlay_Hookにおいては、インスタンス化し、FactoryContainerに対応させることで、インスタンスを分離することでHOOKのnamespace or domain を分離することが可能になる。
ぶっちゃけたところ、Xhwlay_Varはメソッド数も大したこと無いので、各メソッドの冒頭で Xhwlway_Var::getInstance()(protected) を呼ぶのはさほどの苦ではなかった。また、かなり単純なクラスで、使用用途も決まっていて、拡張するような代物でもないため、クラスの拡張を考慮する必要は無かった。
けど、HOOKとなるとねえ・・・。Application Layerで独自に拡張したい場合も十分あるだろうし・・・。という訳。まあ、実行中のHOOKを覚えておくHOOK Stackはこれで無用にはなる。あと、callbackやresultの扱いも、[$hook]が削り取られる分シンプルで安全になるし。
あと、すんごい迷っているのがresultの取り扱い。破壊的操作にするのか(pop)、非破壊的(=接触)操作にするのか(copy on writeによるコピー渡し)、迷ってる。あと、HOOKポイントがnullを渡したときの取り扱いがチョービミョー。値を返さないHOOKが混じっていたことによるnullなのか、有意であるnullなのか。それにより、resultスタックにpushするのか、しないのかがチョービミョー。どうしよう?
・・・という風に考えていくと、やっぱり"attribute"は必要かもしれない。現状すでに「有効・無効」を指定する Available というbooleanの概念はあるが、他にもHookそれ自体の属性がこの件を含めてもう2、3個出てくるかもしれない。
少なくとも、次のメソッドはアリだと思う。
で、attributeとして
って感じだろうなあ・・・。
どうでも良いけど、これ、そのままPageActorやBarrierのチェインに使えない?・・・いや、これらメインのチェイン自体はアイデアとしては不要としてお蔵入りしてるんだけどさ。まあ、多分使わない。また制御周りでややこしくなるから。っつーか、Hookの中でHOOKチェインを操作したりresultを操作したりするのって、よほどでない限り、ないだろう。
Argumentもなんだか無用な気がしてきたな・・・。HOOKにはもう、Hookのインスタンス一つのみ渡すようにしよう。
Argumentという名称自体は、別に悪くないし、かなり的確だと思える。HOOKの引数データ、という意味で端的にあらわしている。・・まあ、argumentに array_unshiftで先頭に &$this を突っ込んでcall_user_func_array すりゃいいだけか。
うん。Argumentはやっぱり有用。
どうでもいいけど、今定数系って"XHWLAY_(クラス名のlarge case)_XXXX"になってるけど、"XV_"とか"XH_"とか"XES_"(Xhwlay_ErrorStack"とか"XR_"とかにできないだろうか?タイプがめんどいし、80桁超えやすいんだけど。あとで一括置換しておくか。
→やっぱ、定数名の衝突が怖いのでやめ。
あと、Xhwlay系のインスタンスを保持するときは、$xh とか $xr とかみたいな短縮がかっこいいかもしれない。と思った。HookのSimpleTestで使ってみよう。あとsimpletestのpartial mock、あれ、すげーな。
あと、pop/pushの実装をarray_shiftとarray_unshift使ってたけど。これ、素直にarray_pushとarray_popでもかわらねえよなあ?
いや、array_shiftとarray_pushという組み合わせは完全にまちがってるよ?けど、これ、先頭を出したり引っ込めたりするか、お尻を出したり引っ込めたりするかの違いだから。
別にどっちだって良いし、であるならば、後で変に邪推される必要の無い、array_push/popで良いんじゃね?
filterPageNameやfilterViewNameのHookは・・・ダミー入れておくしか、ないなあ。
Runner自体にstaticなデフォルトHOOK、作ってそれ入れておくか。invokePageActorやinvokeBarrierも同様かな。
・・・あれ?Runner、Abstract必要なくなる?
英語だと後者になるようですが、元々とある漫画の「ブーレイ騎士団」という名前が頭をかすめた時に思いついた名前ですので、前者を推したいところです。
・・・が、2008年12月現在は作者自身が後者の"レイ"にアクセントを置いた方で読んでます。
Piece Framework を強く意識し、ステートフルな画面遷移を行う為の、シンプルなコントローラです。フレームワークではありません。Piece Frameworkを意識していますが、ステート保持のコンセプトはFSM(Finite State Machine)ではなく、ページの流れを一つの「本」(Book)と捉え、ユーザーに現在表示するべきページを「栞」(Bookmark)として保持するような仕掛けにしています。従ってActiveStateやViewStateなどの区分けは存在しません。従来通りの「ページ - アクション」マッピングの思想を引き継いでいます。一応、Guardに相当する「Barrier」(バリア)を設定することが可能です。ページ遷移を特定条件でブロックするときなどに使います(*1)。
例によりValidatorやORM、さらにロギングなどの連携は一切手を出していません。好きなように作り込んで下さい。
PokoXの上位版の予定でしたが、互換性は無くなります。当初はPokoX 2.0 を予定していましたが、後述の理由により名前を変更し、ゼロから作り直しました。
"pokox"で検索すると、かなりの数のハンドルネームが引っかかってしまいました。このご時世、本当に「ユニーク」な名前を考案するのは至難の業のようです。新しい名称は、とりあえず 2007/04 - 06 現在、Google上では他に存在しないようです。
フローエンジンのコアとなる、Piece-Flowと StageHand_FSM のソースを読んでも、何をやっているのかよく分からなかったというのが原因です。発端がそこで、「FSMワケわかんねー!」となり、「じゃあ、自分で作ってしまえ」と例により暴走が始まりました。ソースちゃんと読めよ、と言われても、暴走し出すと傲慢な性格がむき出しになりますので、何を言われても大して心に・・・とめないように、努力はしてます。
後付の理由ですが、ステートフルなページ遷移を行うフレームワークの仲間を増やしたいな、と。フレームワークじゃなくてライブラリですが。
2007-07-01 現在、目下コーディング & 単体テスト中で、機能をシェイプアップ中です。sf.netにプロジェクトは登録できましたが、まだCVSには入れていません。
この後の予定ですが、とりあえず一通り終わったら1.0.0-RCとしてCVSに入れて、リリースファイルに登録したら、そのままYakiBikiに移る予定です。8月位になるかな・・・。
YakiBikiでの実装によるフィードバックを入れて、とりあえず1.0.0としてリリースする予定です。ドキュメントもYakiBikiで書きたいところですし。
非常に刺激的でした。また、帰り道幾つか思いついた設計変更を。ここから下は作者以外の人は読み飛ばしちゃって下さい。
よくよく考えたら、PageActorがreturn nullすればお仕舞いなので、二重にrender()の起動チェックをおこなう必要は無いと結論づけました。後述のXhwlay_Varsの導入とも関連しますが、なるべくXhwlay_AbstractRunnerインスタンスの持ち回りを避けたいというのもあり、もともとPageActorのエントリポイントに、skipRender()させたい為に Runnerインスタンスを渡していましたが、まあ、これで不要になるかなと。
今までは pageName や aci, bookmarkContainerId などを stdClass に押し込んでましたが、これも、Runnerインスタンスの持ち回りの遠因になっていましたのでやめます。 Xhwlay周りで値を持ち回したい場合は、Xhwlay_Vars を用いましょう、と。
Xhwlay_Vars::isset($key, $domain = '*') Xhwlay_Vars::unset($key, $domain = '*') Xhwlay_Vars::get($key, $default = null, $domain = '*') Xhwlay_Vars::set($key, $val, $domain = '*') define('XHWLAY_VARS_DOMAIN_DEFAULT', '*')
あまり複雑な設定値を持たせたいわけではなく、単にスカラー値やサイズの小さい配列を入れる予定ですので、これで十分かと。refにするかは、ビミョー。
今後は、pageNameやaciなどは
Xhwlay_Vars::get(XHWLAY_VARS_PAGE_KEY); // pageName Xhwlay_Vars::get(XHWLAY_VARS_ACI_KEY); // ACI Xhwlay_Vars::get(XHWLAY_VARS_BCID_KEY); // bookmarkContainerId
みたいな感じにアクセスするようになります。def値も切っておかないと・・・。
また、XHWLAY_VARS_PAGE_KEYで取得できるのはあくまでも"filtered"された値になります。BCIDも同様。リクエストされた「生の」値を取得するには、
Xhwlay_Vars::get(XHWLAY_VARS_PAGE_KEY, '', XHWLAY_VARS_DOMAIN_REQUEST); Xhwlay_Vars::get(XHWLAY_VARS_BCID_KEY, '', XHWLAY_VARS_DOMAIN_REQUEST);
とするようにすべきかもしれません。
書いておいてなんですが、Xhwlayでは基本的にクラスは単数形を使う方針ですので、Xhwlay_Var, XHWLAY_VAR_*となる可能性大。
AbstractRunnerのコアレベルでは、Bookmarkのdropは行いますが Bookmark Container の drop はしていません。しかし、Bookmark Container ID が第二のセッションIDとなり得る以上、きちんとdropをしないとBCIDに対する固定値攻撃などが発生しかねません。
次の「総HOOK化」とも関連しますが、Bookmark の drop の後に、Bookmarkの数が0になっていれば Bookmark Container も破棄するようなHOOKをデフォルトで仕掛けておくべきでしょう。それに関連して、Bookmarkの数をcountするIFをBookmark Containerに追加しておくべきでした。
・・・総、とまでは行かなくても、現段階でAbstractRunnerのimplement/extend先での実装を想定している setup(), terminate()などは、
push($hookpoint, $callback); pop($hookpoint);
的にすべきかもしれません。Xhwlay_Hookの登場か!?・・・それも、アリか・・・。
総HOOK化とも関連します。また、開発者をどこまで野放しにするかのバランス感覚の話になります。当初は、PageActorの開発だけに注目して欲しくて、その外の世界にはあまり触らせないようにしていました。が、Hook化の事も考えると、どうしてもRunnerに触りたい、少なくともBookmark ContainerやConfig, Rendererに直に触りたいがその場合はまずRunnerにアクセスしなければならない。そうした時に、引数で一々Runnerの参照を渡すのも、かなり面倒です。
実はPageActorやBarrierActorのインターフェイスにもまだ迷いがあって、Xhwlay_Vars の導入を決めたのはこれら開発者用のIFがPokoXの時ほどお気楽・お手軽に決定できていない、というのがあります。であるならば、とりあえず情報にはアクセスできるようにしておき、徒に書き換えたときに何が発生するのかは保証外として単体テストもそこまでは通しませんよ・・・と線引きをしてもいいかな、というなんだか情けない妥協の結果が、Runner::getInstance()の導入経緯となります。
function execute(&$runner, $pageName, &$bookmark, $params)
これが、今考えているPageActorのIFです。&$bookmark, $params は変える気はありません。どう考えても、今後設計が変わっても、必須のものであろうからです。しかし$pageName, $runnerは何の為にあるのか?と問われると、必須である理由はあまりなく単に「外部情報としてこの二つがあれば事足りるだろう」と考えていたからです。
しかし、外部情報として何が必要かは、たった一人の開発者が全てを予想できるものでは無いでしょう。であれば、もう、拘らずに全解放してしまっても良いのではないか。
もちろん Runner::setXXYY 系は軒並み、final & protected となり、規律としては外部から呼び出すことはできなくなります。get のみが public となります。
・・・と書いてみたら、なんだか随分激しい仕様変更のような気もしてきました。特に「総Hook化」でのpop/pushは使い方次第ではコアのフローを完全に書き換えることができる悪魔のような仕様になりそうです。Hookの中でHookを書き換え、実行順序を変えたり、コアでデフォルトで設定されていたHookをPOPで破棄し、代わりに独自Hookを詰め直す・・・ということも可能。
うわぁ。
PieceではStateFull/StateLessを、Flow自体ではなくUnityの方のYAMLで切り替えられるようになっていたのに触発されて。
一応Story自体にBookmarkモードを埋め込むことを「デフォルトの」使い方としますが、Runner側でも "requireBookmark" というHOOKポイントを用意しようかなと思います。
Runnerは現状、以下のコードでstory自体のBookmarkモードを検査しています。
if ( $this->_config->needsBookmark(...) ) {
これが以下のようになります。本番はもうちょっとifブロックをまとめ直すかも・・・。
Xhwlay_Hook::invoke("requireBookmark"); $overWrited = Xhwlay_Var::get('requreBookmark', null); // 未設定であれば上書きは無かったとする。 if ( !is_null($overWrited) ) { if ( Xhwlay_Var::get('requiredBookmark') ) { // 上書きでBookmark ON } else { // 上書きでBookmark OFF } } else { if ( $this->_config->needsBookmark(...) ) { // 通常のStory/Page重視のBookmark ON ...
以上より、登録しておくべきHookは以下のようになります。処理はサンプルです。
class User_Hooks { function overwrite_story_bookmark() { $aci = Xhwlay_Var::get(XHWLAY_VAR_ACI_KEY); if ( $aci == 'anonymous' ) { Xhwlay_Var::set("requireBookmark", false); // ACI = 'anonymous'なら強制的にBookmarkOFF return; } $page = Xhwlay_Var::get(XHWLAY_VAR_PAGE_KEY); switch("$page.$aci") { case "page1.admin" : Xhwlay_Var::set("requireBookmark", true); // Bookmarkを強制的にON break; } } } Xhwlay_Hook::push(array('User_Hooks', 'overwrite_story_bookmark'));
・・・え・・・?何気なく書いちゃったけど、ACIとページ名によってHOOK側で強制的にBookmarkのON/OFFを切り替えられるのか?・・・ま、いっか。
※PokoXとして妄想していた時分の記録
発端は、「で、そもそも何でFSM入れるんだっけ?」だ。
理由は一つ。限定的遷移を行わせたい。つまり、CSRF対策だ。
で、昨夜、ソースをゼロベースで手を出し始めたが、フロー定義のyamlファイルのスキーマの解説を書いているうちに、「・・・なんでここまでする必要があるんだ・・・。」という気になってきたのだ。
CSRF対策?限定的遷移?そもそも、FSMを用いるメリットは何だ?なぜわざわざFSMを導入する?
理由は一つ、限定的遷移。つまり、ある実行ポイントから次の実行ポイントに移るときに、その遷移が、正規のものであることを保障できる・・・手法として、FSMがある意味、「そのまんま」当てはまったからだ。
ちょっとまて、と。別に・・・遷移を設定ファイルに記述されたとおりに保障する仕掛けさえあれば、FSMを使わずともよくね?
結局、「現在の実行ポイント」をstoreし、identifyする機構を設け、そのうえで、ある実行ポイントに遷移するとき、「現在の実行ポイントからの遷移先として正規か?」をチェックし、チェックOKなら遷移、チェックNGならデフォルトの実行ポイントに強制リダイレクト。というのがもともとの、筋のはずだ。
これを応用することにより、CSRFを防げ、しかもDouble-POSTも防止できる。
・・・別に、FSM使わなくても良いじゃん。
しかも、だ。うれしいことに、PokoXはもともと「ストーリー」という概念で実行ポイントを定義している。
ま、ちょっと定義している概念階層は上下しちゃってるんだけど。
じゃあ・・・PokoXのストーリーに、そう、「ロック・イン」(固定する、箱に入れる、閉じ込める、出られなくする)できるようにすれば良いのでは?
(lock in よりはlock onかなあ・・・。連結する、自動追尾する、みたいな表現。)
PokoX自体は、何度か使ったけど結局「モジュール・チェイン」は使わない。(まあ、モジュール・ディスパッチは場合により使う、程度。)なので、今度の改造のときは(もちろんパラダイムが変わるのでPokoX2の予定だった)
"aci.action" => "クラス名"
程度にしようと思ってた。実際問題、モジュールargsも滅多に使わない。もともと固定的な設定値を埋め込めるようにするためのマージンだったけど、設定値はアプリ定義の仕組みで行うのがほとんど($_MEMORIESとか。)だったので使ってない。
まあ、DI-Containerはおいしいけど・・・。無くても動くし。
なので、こんな感じかなあ。argsは、attrsにまとめちゃおう。
$story = array( "*.*" => array( "class" => "your.business.logic.class", "lock" => "on", "attrs" => array(), // 省略可能 "next" => array("new", "edit", "delete"), ),
例えば、ユーザーの一覧を出して、新規作成や編集はウィザード形式、削除は単発、みたいなのだとこんな感じ。で、これだと、"ヘルプ"画面はフロー制限外。つまりlock-onされてなくてもオッケー。
まず、PokoX自体のlock-onモードはデフォルト"on"という設定を可能なこと。これにより、"lock" => "on"は省略可能になる。逆にlock-offポイントを明示的に指定する必要がある。
・・・というよりか、
あと、そろそろ、"story"じゃなくて"page"という表現にしようか。
$page = array( "*.list" => "*.*", "*.*" => array( "class" => "UserList.class.php", "next" => array("new", "edit", "delete") ), "*.new" => array("class" => "UserCreate_1.class.php", "next" => array("list", "new1") ), "*.new1" => array(..., "next" => array("new", "new2")), "*.new2" => array(..., "next" => array("new", "new1", "new3")), "*.new3" => array(..., "next" => array("new", "new1", "new2", "new_last")), "*.new_last" => array(..., "lock" => "off", "next" => array("list") ), "*.help" => array(..., "lock" => "free"),
例えば、いきなり"*.new"を叩く場合。lock-idが発行されておらず、しかもlock-onモードなので、lock-idが発行され、"*.*"にmodule-dispatchされる。
lock-idに対しては、現在の bookmark(栞) を"*"に設定される。
ここで、例えば"...?ロックID=...&page=new"というリンクがクリックされたとする。
ロックIDが与えられているので、設定されているbookmarkをロードする。
→"*"になる。で、"*"の"next"を見てみると、"new"がある。つまり、許可されているため、"*.new"のモジュールが実行される。
また、"bookmark"を進める。
ではlistからいきなり、"new1"ストーリーがリクエストされたとする。
bookmark->"*"->"next" = array()
の中に無いため、warningのErrroStackを発行し、bookmarkの"*"を実行する。
で、"new_last"まで到達した場合。"new3"までbookmarkが進んだ状態で
"page=new_last"
がリクエストされると、
bookmark -> "new3" -> next=>array("new_last") -> "new_last"
で、new_lastが実行される。この後、"lock"が"off"であるため、ここが一つの"lock-id"に対する終端モジュール。
つまり、lock-idと紐づいているbookmark情報が破棄される。
途中で"page=help"がリクエストされた場合:
bookmark -> "..." -> "lock-free" -> そのまま"help"実行。bookmarkは更新しない。
helpのViewで、ちゃんとlock-idがリンクに埋め込まれていれば、bookmarkはそのままなので、"lock-on"されたストーリーがそのまま継続される。
上記処理は・・・まあ当然、PokoXのコアが実行することになるのだけれども。
当然、callbackを設定することでlock-idとlock-on, nextの判別処理にHookできる。
PRE_GUARD_HOOK : 戻り値は以下。
・・・HOOKよりかは、&$pokoxを渡すとか、PokoXへのDIとかで、すなおにメソッド内で処理を委譲させた方が良いかも。あるいは、多分Storyコアでいっぺんに処理できる類じゃないから、
$guards = array( "/new.*/" => array("callback_for_new_action_guards") );
みたく、正規表現かけられる形にするとか。こうすれば、上記例では、"new3"まで言った後に、(例えばタブナビゲーションとかで)new1まで戻ったとしよう。
で、new1から再び"new3"に行きたいとき・・・guards側で、例えばFlowScopeにstackされている値を読んで、条件が満たされていれば、設定ファイル上はN.G.だけど、guards側で通す、ということも可能になる。
あとは、PokoXコアとして、MRTNはデフォルトに。まあ・・・そこら辺は今まで温めていた通りで。
ViewかActionかは、モジュールが判別すべき。フレームワーク側で引きずらない!
あと、やっぱ、いい加減、DIコンテナ使おう。・・・いろいろと幅も広がるだろうし・・・。
PHP4の時代は
E_ALL or E_ALL ^ E_NOTICE
で、.htaccessの場合は整数値で直接指定しないと駄目なので
# E_ALL php_value error_reporting 2047 # E_ALL ^ E_NOTICE php_value error_reporting 2039
という早見表が成立していました。
PHP5, PHP6と出てくるにつれてE_ALLの値も変わってきたようです。
実行時設定のPHPマニュアルにもありますが、とりあえず全部出したい場合はIntegerの最大値を指定しておけば良さそうです。
また、エラーレベルはこれからも追加されることがあるので、最大値 (E_ALL に対応する値) は変わる可能性があります。
そこで、E_ALLを指定する場面では 2147483647 のような数を指定するようにしましょう。 これは現状の全ビットに対応した上で、かつ値が将来追加された場合にも対応できます。
PHP使い始めて4年以上。今日、初めてこの罠をしった・・・。
http://jp.php.net/manual/ja/function.unset.php
にありますが、関数内で、その関数の外で定義した変数をunset()しても、unset()されてるのは関数の中だけで、関数の外では以前と同じ値が保持されるんですわ。
例えば外で定義されてる$_BOHEという変数をunset()する関数で、以下のどれが正解かというと・・・
function __UNSET1() { global $_BOHE; unset($_BOHE); } function __UNSET2(&$val) { // ex) __UNSET2($_BOHE); unset($val); } function __UNSET3($var_name) { // ex) __UNSET3('_BOHE'); global $$var_name; unset($$var_name); } function __UNSET4($var_name) { // ex) __UNSET4('_BOHE'); unset($GLOBALS[$var_name]); }
$GLOBALSでとってる__UNSET4()だけが正解。 これは実際に実験して確認しました。
ただ、変数を直接unset()するのは確かに$GLOBALSでなきゃだめなんですが、私自身は今までちょくちょく
function hogehoge() { ... global $_HOGE unset($_HOGE['key']); }
とか大量に書いてきて、それでちゃんと動いてました。
つまり、変数を直接unsetするのはN.G.なんですが、変数が配列の時、そのキー値をunsetするのは平気みたいです。
とりあえず以上です。unset()には気をつけましょう。
何種類か出てくると思います。
Windowsとそれ以外でざっくりと分類するのであれば、DIRECTORY_SEPARATORが"/"か"\"かで見分けても問題ないかなと思います。
PATH_SEPARATORで見分けても良さそうですが、PATH_SEPARATORはPHP4の古いバージョンだと未定義だったりします。昔のPHPコードを見てると、むしろDIRECTORY_SEPARATORからPATH_SEPARATORの定義を切り分けてたりします。
if (!defined('PATH_SEPARATOR')) { if ('/' == DIRECTORY_SEPARATOR) { // UNIX系 define('PATH_SEPARATOR', ':'); } else { // Windows define('PATH_SEPARATOR', ';'); } }
具体的にOS名を取得したい場合は環境変数の"OS"から見分けても良いと思います。PHP_OSという定数も定義済です。環境変数の方が若干細かいようですので、用途に合わせてという感じです。
> php -r "echo getenv('OS');" Windows_NT
> php -r "echo PHP_OS;" WINNT
PHP_OSなどからWindowsか否かを判定する場合、Mac OS X が出現する前は次のようなコードでも問題有りませんでした。
if (false != strpos('win', strtolower(PHP_OS))) { ... }
しかしMac OS Xの場合はOS名に"Darwin"と入るようですので、上記コードだとOSXまでWindowsだと勘違いされます。
Smartyのコードからの拝借ですが、次のように「先頭から3文字」を取得して比較するのが安全だと思われます。
if (substr(PHP_OS,0,3) == 'WIN') { ... }
Smartyのdate_formatの %Y %D の指定がなんか変に感じたので、試しにソースを当たってみたら
if (substr(PHP_OS,0,3) == 'WIN') { $_win_from = array ('%e', '%T', '%D'); $_win_to = array ('%#d', '%H:%M:%S', '%m/%d/%y'); $format = str_replace($_win_from, $_win_to, $format); }
Windowsの場合勝手に変換してくれてるようです。date_formatでは%Dを %m/%d/%y にしちゃう。(実際にフォーマットしているstrftime()自体がそういう仕様)なので、短くかけてラッキーと思っていたら、%y/%m/%dじゃなくてちょっと残念だった。
$str[10] = 'a';
のように、角括弧(bracket)で文字の位置を指定する事で、文字単位でアクセスする事が可能。
但しPHP6以降に「文字位置」と「バイト位置」のどちらになるかは不明。
あんまり使う事は無いのだけれど、文字列操作でsubstr()辺りが出てくると、出番が来たりする。
これ、2008年時点でもPHPマニュアルに載ってないんだけど、PHP4.4.9とPHP5.2.5の両方で確かに動くんだよなぁ・・・。
<?php class C1 { var $p1; var $p2; } $o = new C1(); $o->{"p1"} = 123; $k = "p2"; $o->{$k} = 456; var_dump($o);
→
object(c1)(2) { ["p1"]=> int(123) ["p2"]=> int(456) }
PHPってオブジェクトのプロパティ値は内部的には連想配列で確かもっているので、実はこんな事も出来たりする。
(上のコードの続き) foreach ($o as $prop => $v) { echo "{$prop} = [$v]\n"; }
→
p1 = [123] p2 = [456]
BeanUtilsなんて要らない。
昔のコンテンツの整理とかしていたら、ふとCetaを作ったときのことを思いだした。
Cetaに没頭・・・というか逃避していたのは2000年~2001年の事だけれど、当時はWindows2000が出たばかり。自分は金のない大学生で、中古で購入したLet's noteでWindows98とかでC/C++を勉強していた。
Windows98とかはコマンドプロンプトの使い勝手が悪くて(TABキーによるディレクトリ・ファイル補完が効かなかったような記憶がある)、かつC/C++を勉強するのにエディタを欲していたのだが、コンパイラやコンパイルオプション、環境変数を好き勝手に弄れるエディタが無かった。
書き出せば長くなるので端折るけれど、とにかく当時の自分としては「欲しいツール」が無くて、でもそれが無いとどうにも先に進めそうになかった(*1)ので、勉強がてらつくったのがCetaというエディタだった。
ぶっちゃけ、作り終わる頃にはLinux弄るバイト初めて、一時期はDesktopまでLinuxにしたときがあって、Windowsに戻ってきた頃にはWinXPとかが出ていてsakuraエディタとWin2k以降使い勝手が改善されたコマンドプロンプトだけで事足りるようになってしまった。
Win2k以降、環境変数の変更を反映する為の再起動が不要になった事も大きい。
自分はCetaを作るという名目で色々逃避もした。例えば試験をすっぽかして単位を落とした。BorlandC++BuilderにくっついてきたWin32APIリファレンスを和訳するというアホな行いの為に技術英語と製図の授業を半年分丸々ボイコットした。
当時としては随分アホな事するなぁと我ながら思ってはいたけれど、どうにも我慢というか収まりが効かなくなったあげくの所業だった。
とはいえ、その分の見返りは随分大きい・・・と今になって思うのは、結局ソフト開発を生業とするようになったからだろう。Win32APIをまじめに勉強したのは、その後実際の仕事で(間接的にではあるが)非常に役に立った。
また、MSDN日本語版の存在を知らなかったが故の過ちである「2-300ページのWin32APIリファレンス(英語)をプリントアウトして面白そうな箇所から和訳」というのは、プログラミング系の英語ドキュメントであれば物怖じせず読もうとする姿勢が身に付いたという意味で、非常に役に立った。(*2)
YakiBiki自体はまだなお制作中ということで、現在形ではあるのだけれど、やっていることはCeta制作とよく似ている面があるので多少は過去形にしてみる。
実はYakiBikiのメイン機能であるACL自体は2002年とか2003年当時からほしがっていた機能だったりする。当時はツリー形式のPHPの掲示板スクリプトを弄ってローカルでとりあえずそれっぽくして動かしていた。
但しフレームワーク作りが楽しくてなかなかYakiBikiまで到達できなかった。
Pieceと出会い、Xhwlayを構想して2007年の8月とかにはXhwlayが動き出していた。
で、やっぱり何だか抑えが効かなくなって、部長に直談判して(*3)、半年ほど休職扱いにして貰った。
Ceta制作で半年ほどメインの授業ボイコットしたのと変わらないなぁと。
まぁその半年は色々あって、高い授業料払って人生を勉強させて貰ったり、Webベンチャーにアルバイトとして雇って頂いたりした。
この時、3ヶ月の間に3種類のPHPフレームワークを取っ替え引っ替えで同じアプリを作成し、ベンチマークを取ったりした。
2008年の3月から9月までは色々あった。Ruby on Railsをやったり、どこか逃げ場所を求めてPHP勉強会SIDE-Bを開いたり人に会ったり、精神的に追いつめられたりもした。何というかあっという間の1年半だったけれど、手元のYakiBikiのリビジョンは確かに480を越えているし、Xhwlayは動いているし、こうしてYakiBikiを自分で使っている。
今は直ぐには分からないけれど、これらも多分将来の自分にとって効いてくる布石になるのだろう、きっと。そう願いたい。SIDE-Bに参加して就職先を得た人もいるのだし、きっと無為ではなかった・・・と信じないとやってられないなぁ。
とりあえずYakiBikiまで作り、ひとまず自分としては「欲しいツール」が出揃った訳だけど。
このサイクルだとまた5年後とかに、自分が欲しい「何か」を作る為に半年ほど会社を休むのかも知れない。
その時は自分は何を作りたがっているのか、今から楽しみだったりする。
ここまでは本当に単に「自分が満足する為の何か」を作る為に我が儘し放題だったわけだけれど、もし今度我が儘をしたくなったときは、もうちょっと他の人も満足できる何かを作れると良いなぁ、と思う。
2007年5月前後のメモ。Antのプロパティ値とかで "${...}" とかで別のプロパティ値を埋め込めるのが羨ましくて、PHPで何とかできないかなぁ・・・と試してみた時の記事です。
次のようにINIファイル中で、前に出てきたキーの値を埋め込めるようなINIパーサーが欲しい。
[sect1] a.b.c = hoge a.b.d = bohe key0 = Hello, ${key3}!! key1 = Hello, ${a.b.c}!! key2 = Hello, everyone, ${a.b.c},${a.b.d}!! key3 = Hello, ${ab.c}!!
これが、次のようにparseできれば良い。
array { "a.b.c" => "hoge" "a.b.d" => "bohe" "key0" => "Hello, Hello, ${ab.c}!!!!" "key1" => "Hello, hoge!!" "key2" => "Hello, everyone, hoge,bohe!!" "key3" => "Hello, !!" }
・・・う~~ん・・・上の例の"${ab.c}"のように、未定義の変数表現が出てきたらどうするか。
・・・場合によってはどちらも妥当だろう。この辺は実際にライブラリ側でどうにかすべきだろう。
というわけで、コンセプトコード。正規表現でかなり悩んだけど、それ以外は意外と、すんなり書けてしまった。
<?php $configs = array( "a.b.c" => "hoge", "a.b.d" => "bohe", 'key0' => 'Hello, ${key3}!!', 'key1' => 'Hello, ${a.b.c}!!', 'key2' => 'Hello, everyone, ${a.b.c},${a.b.d}!!', 'key3' => 'Hello, ${ab.c}!!', ); function test($configs) { $configs_copy = $configs; foreach($configs as $k => $v) { if(!preg_match_all('/\$\{([\w\d\/\.,-_]*)\}/', $v, $matches)) { continue; } $originals = $matches[0]; $keys = $matches[1]; $replacer = array(); foreach($originals as $_index => $_o) { $_k = $keys[$_index]; if(isset($configs[$_k])) { $replacer[] = $configs[$_k]; } else { $replacer[] = ""; } } $configs_copy[$k] = str_replace($originals, $replacer, $v); } return $configs_copy; } var_dump(test($configs)); $configs = array( 'pokox.root_dir' => '/opt/lib/php/pokox', 'pokox.app_dir' => '/var/www/app1', 'pokox.kernel_dir' => '${pokox.root_dir}/kernel', 'pokox.vendor_dir' => '${pokox.root_dir}/vendor', ); var_dump(test($configs)); ?>
→出力:
> php AppConfig_alpha.php array(6) { ["a.b.c"]=> string(4) "hoge" ["a.b.d"]=> string(4) "bohe" ["key0"]=> string(25) "Hello, Hello, ${ab.c}!!!!" ["key1"]=> string(13) "Hello, hoge!!" ["key2"]=> string(28) "Hello, everyone, hoge,bohe!!" ["key3"]=> string(9) "Hello, !!" } array(4) { ["pokox.root_dir"]=> string(18) "/opt/lib/php/pokox" ["pokox.app_dir"]=> string(13) "/var/www/app1" ["pokox.kernel_dir"]=> string(25) "/opt/lib/php/pokox/kernel" ["pokox.vendor_dir"]=> string(25) "/opt/lib/php/pokox/vendor" }