バリデーターあれこれ
今更ながら。
バリデーターを書く場所について悩んでいる。
- バリデーター(or フィルター)
- アクション
- ビジネスロジック
という階層があるとき、ビジネスロジックで何らかのエラーがあった場合はエラー画面を出したい。
具体的には、
+----------+ |ユーザーID| +----------+ | ・・・ | +----------+
というテーブルがあったとき、ユーザー情報の操作画面ではフィルタなりバリデーターなりでセッション情報を確認して不備があればエラー画面を出す。
これはビジネスロジックじゃなくてもよい。セッションはユーザーIDに基づいているのでセッションが正常ならそのIDに基づいたユーザー情報の操作も正常だ。
ところが
+----------+ |ユーザーID| +----------+ | ・・・ | +----------+ | +----------+ | 住所ID | +----------+ |ユーザーID| | ・・・ | +----------+
こういうテーブルがあったとき、住所情報の操作画面では、フォームからの入力値として住所IDを受け取って何らかの処理をしたいのだが、一緒にユーザーIDのチェックもしないといけない。
ここで、住所情報の操作はビジネスロジックのクラスだとすると、ユーザーIDのチェックもビジネスロジックになる。あるいは、取得した住所情報をアクションに持ってきたときにチェックするか。
どちらにせよ、まともにリンクをたどった場合は起こり得ない状況がURL直打ちなどを考慮すると起こり得るので余計な(?)チェックが必要になる。
達成したい処理は住所情報に関する操作なのだがそこにユーザーセッションの処理が加わるのは何となくしっくりこない。
フォームからの入力チェックなのでアクションに移る前にバリデーターで処理したいところだが、住所情報を取得しないことにはユーザーIDと住所IDが正しいのかどうかが分からない。住所情報を取得するためにはビジネスロジックを通過する。
何度でもDBを見に行ってもいいとするならば、バリデーターでユーザーIDと住所IDの検証をすることもできるが、これは無駄すぎる気がする。
AOP的なことをしようかっていうのも考えたが…あまりいい案も浮かばず。
書きながら、Haskellのプログラムを思い出した。
Haskellではフォーム処理やDB処理などのIOを一カ所に集めないと面倒なことになる。
Haskell的考えでいくと、そもそもビジネスロジックからDBに接続にいくのがおかしいということになるのかな。
http://www.symfony-project.org/book/1_2/02-Exploring-Symfony-s-Code
だいたいはここのFigure 2-2のように考えていた。この考えでいくと、全てがIOになるんじゃないかな。まぁそれがPHP的と言えばPHP的だけれども。
で、自分がHaskellで作った小さなWebプログラムを見直してみる。
そこでは
type Action = Context -> IO Context type ItemAction = (Context, Item) -> IO (Context, Item) filterItem :: ItemAction -> Action filterItem a = do ... -- DB接続してItemを取得、正常に取得できなければエラー用Actionを返し ... -- 取得できれば a を実行して結果からItemを除いて返却する。 modifyAndView :: Action modifyAndView = filterItem (modifyItem >=> viewItem) modifyItem :: ItemAction modifyItem (c, i) = do ... viewItem :: ItemAction ...
のように作っていた。
ここでは、Itemを操作するアクションはItemを受け取るようになっていて、何らかのエラーでItemが受け取れない場合はfilterItemによってエラー画面に遷移する。
つまりアクション実行時にはフォームやDBを含めた全てのデータが正常であることが保証されている。この構成だと本当に実装したい処理に集中できる。
ここで外部キーを使ってItemInItemテーブルを追加した場合はどうなるのだろう。たぶん
type ItemInItemAction :: (Context, ItemInItem) -> IO (Context, ItemInItem) filterItemInItem :: ItemInItemAction -> Action ... modifyItemInItem :: ItemInItemAction
というように作っていくのかな。filterItemInItem が ItemとItemInItemの外部キーをチェックするようにして。
まだPHPでどうするかは思い付かないが、何かこの辺にヒントがある気がする。