※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ポイントを明示的に指定する必要がある。 ・・・というよりか、 - ロックするのが"lock-on", - ロック解除(ID破棄)が"lock-off"。 - IDロックが不要なページは、"lock-free"と表現すべきか。 あと、そろそろ、"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"されたストーリーがそのまま継続される。 * Guardian-Hookについて。 上記処理は・・・まあ当然、PokoXのコアが実行することになるのだけれども。 当然、callbackを設定することでlock-idとlock-on, nextの判別処理にHookできる。 PRE_GUARD_HOOK : 戻り値は以下。 - PRE_GUARD_OK ... リクエストされたpageへ進んでよい。 - PRE_GUARD_NG ... pageへ進めない。bookmarkされたpageを実行。 - PRE_GUARD_DEFAULT ... デフォルトのGuardianへ処理を委譲。 ・・・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コンテナ使おう。・・・いろいろと幅も広がるだろうし・・・。