PHPだと foo.php?a[]=10&a[]=20... が $_GET['a'] で array(10, 20) として取り出せますし、
foo.php?a[k1]=10&a[k2]=20... が $_GET['a'] で array('k1' => 10, 'k2' => 20) として取り出せます。
Servletで同等の事出来ないかなと思って調べてみたのですが、どうしてもStrutsやSpringなど、一定規模のフレームワークを導入した上で、Beanなどのクラスにマッピングするなどの手間をかけないと出来ない?っぽい?です。
リクエストパラメータに限ったことではなくて、JSPとかの一種のテンプレートエンジンでもよくあるニーズとして「動的にプロパティ参照やメソッド呼び出しを実行したい」というのがあって、割りとメジャーどころがライブラリを揃えてくれてます。文字列で表現された式(Expression)を動的に評価する一種のミニプログラミング言語(Language)なので、「式言語(Expression Language)」として、JSP周辺技術を調べているとよく目にします。
今回はMVELをGroovyで試してみました。
@Grapes([ @Grab('org.mvel:mvel2:2.1.4.Final') ]) import org.mvel2.PropertyAccessor def data = [ 'k1' : 'abc', 'k2' : 10, 'k3' : [10, 20, 30], 'k4' : [ 'k4_1' : 'def', 'k4_2' : 200, 'k4_3' : ['ABC', 'DEF', 'GHI'], ], 'k5' : 'xyz', 'k6' : [ ['id' : 1, 'name' : 'bob', 'age' : 10, 'hobby' : ['cooking'] ], ['id' : 2, 'name' : 'jon', 'age' : 20, 'hobby' : ['cooking', 'sports'] ], ['id' : 3, 'name' : 'eve', 'age' : 30, 'hobby' : ['programming'] ], ], ] assert 'abc' == PropertyAccessor.get('k1', data) assert 10 == PropertyAccessor.get('k2', data) assert [10, 20, 30] == PropertyAccessor.get('k3', data) assert 10 == PropertyAccessor.get('k3[0]', data) assert 20 == PropertyAccessor.get('k3[1]', data) assert 30 == PropertyAccessor.get('k3[2]', data) try { def r = PropertyAccessor.get('k3[3]', data) assert 'failed' == '' } catch (StringIndexOutOfBoundsException e) { /* expected */ } assert 'def' == PropertyAccessor.get('k4.k4_1', data) assert 200 == PropertyAccessor.get('k4.k4_2', data) assert ['ABC', 'DEF', 'GHI'] == PropertyAccessor.get('k4.k4_3', data) assert 'DEF' == PropertyAccessor.get('k4.k4_3[1]', data) assert 'bob' == PropertyAccessor.get('k6[0].name', data) assert 'jon' == PropertyAccessor.get('k6[1].name', data) assert 'eve' == PropertyAccessor.get('k6[2].name', data) assert ['cooking', 'sports'] == PropertyAccessor.get('k6[1].hobby', data)
こんな感じで、参照についてはかなり直感的に記述できます。
では肝心の、値の設定についてはどうかというと・・・
@Grapes([ @Grab('org.mvel:mvel2:2.1.4.Final') ]) import org.mvel2.PropertyAccessor def data = [:] PropertyAccessor.set(data, 'k1', 'abc')
→
Caught: [Error: could not access/write property (k1) in: Foo] [Near : {... k1 ....}] ^ [Line: 1, Column: 3] [Error: could not access/write property (k1) in: Foo] [Near : {... k1 ....}] ^ [Line: 1, Column: 3] at org.mvel2.PropertyAccessor.set(PropertyAccessor.java:374) at org.mvel2.PropertyAccessor.set(PropertyAccessor.java:135) at org.mvel2.PropertyAccessor$set.call(Unknown Source) ...
となってしまい、プロパティが無いのでassignが出来ずエラーになってしまいました・・・。
他、実際にStrutsでリクエストパラメータのマッピングに使われているOGNLも有力候補に考えてはいたのですが、"Object-Graph"とあるくらい、やっぱりBean構造と表裏一体で使われるものっぽいですね。なんというか、何が来るか分からないごった煮を乱暴にListやMapに変換する用途には全く向いてなさそうだなぁという感じでした。
「何が来るかわからないごった煮を乱暴にListやMapに変換する」手法は、どちらかというとJSONのパース処理系が近いことしてるんですよね。そうか、リクエストパラメータを一個のJSONパラメータとしてやりとりすれば良いのか・・・ってそんな乱暴な・・・とも言えないか。アリか?でも受け付ける側は楽ですが、HTMLのformとか生成する側が手間になりそうですね。Formのsubmitでもフォーム要素をJSONにまとめる必要が出てくるので、JavaScript必須ですね。
ただ、今日一日このテーマで午前中から「あーでもないこーでもない、このライブラリはどうだ?あのライブラリはどうだ?」と漁ってるんですが、じゃぁ実際、自分がPHPでオレオレフレームワーク作った時に、PHPが自動で処理してくれる連想配列の値をそのままビジネスロジックに渡したかというとそんなこたーないんですわ。必ずビジネスロジックにとって受け入れやすい形式に変換してから渡します。これは入力値チェックがどうのこうのという話ではなく、単純にURLクエリやPOSTパラメータって究極的には文字列なので、ビジネスロジックにまるごと渡すわけには行かず、やっぱり変換のために1レイヤー挟んだほうが圧倒的に構造が理解しやすいしプログラミングも安全になるわけです。もし入力値チェックやビジネスロジック側に渡すときの表現形式が変わっても、変更箇所を限定しやすくなります。そういう観点ではStrutsがOGNL使ってBeanに変換するのもその一環であって、Strutsの世界観に限定すれば全く何の異論もありません。
・・・なので、仮に、PHPライクな連想配列に変換するレイヤーを一枚挟んだとしても、結局、もう一枚ビジネスロジック向けの表現にvalidationと合わせて変換してあげるレイヤーが必要になってくることは確実です。それだったら、もうPHPライクな変換レイヤーなんか放り捨てた方が良いかなと。んなマイナーな中間レイヤー入れても、利用者が混乱するだけですし。
そんな感じで、この調査メモはクローズです。丸一日潰して何やってんだ感はありますが、まぁELとかOGNLの動向を復習できたのでそれはそれでいっか。