CakePHP の通常 View ができたので、XML を返す Web API用のビューを作ります。
[CakePHP] RESTを使ってViewをXML形式で返す | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/4871
と基本は同じです。View の各フォルダに xml フォルダを作成して対応させます。いちいち同じファイルを作らないといけないのが面倒ですが、適当なスクリプトを作って自動化させておくとよいでしょう。
1.app/Config/routes.php を書き換える
先頭の行に以下を追加します。
1 | Router::parseExtensions( 'xml' ); |
2.app/Controller/AppController.php を書き換える
RequestHandler コンポーネントを有効にすること、ページングの数を多めにとっておきます。
1 2 3 4 | class AppController extends Controller { public $components = array ( 'RequestHandler' ); public $paginate = array ( 'limit' => 1000 ); } |
デフォルトでは 20 件ぐらいなのですが、XML型式の場合はそれでは不便です。なので1000件をlimitにしておきます。本当はコントローラごとに細かく制限するのがよいのですが、それはシステム特性というとで、ひとまずこうしておくと大丈夫です。この数をもっと大きくしてもいいのですが、レスポンスも悪くなるし、システム的にこのぐらいがしておく、という感じですね。
3.XML 用の View を作成する
Controller で set した値を xml/*.ctp ファイルに書いていきます。CURD すべてに対応するために、index.ctp, view.ctp, add.ctp, edit.ctp を作っておけば OK です。
index.ctp は、配列にします。
1 2 3 | $items = array ( 'items' => array ( 'item' => $stores )); $xml = Xml::fromArray( array ( 'response' => $items )); echo $xml ->asXML(); |
view.ctp, add.ctp, edit.ctp は一つだけの要素を返します。
1 2 3 | $item = array ( 'item' => $store ); $xml = Xml::fromArray( array ( 'response' => $item )); echo $xml ->asXML(); |
response 要素を作って、その中に items あるいは item を作っていますが、これは好みですね。どちらもルート要素はひとつになるので、response は必要ないのですが、受け取る側(この場合は WPF )で、同じ名前のほうがいいだろうと思ってこうしています。
ただし、これだと web api の OK/NG が返しにくいのが難点です。Web API の受け取り側のつくりとしては、
- API自体の成功/失敗を早めに拾いたい
- APIレスポンスの型式はできるだけ同じ形式であってほしい。
- OK/NGのレスポンスはできるだけ同じ形式であってほしい。
というのがあります。XMLをパースするときに、指定の要素があったりなかったりすのは結構面倒です。また、正常時のXML構造と異常時にXML構造は同じであったほうがプログラムが簡単になります。そうい意味では、あらかじめ response の属性として OK/NG をつけたりバージョンをつけるのがよいのですが…今回は、最初の実装時に入れ忘れたのこのままです。
あと、xml/*.ctp 自体は、非常に単純にしておかないとデバッグが面倒なんですよね。なので、できるだけ簡単にするために、こんなスタイルにしてます。
■一覧(index)を実行すると
XML 型式で返す場合は、http://localhost:81/cakephp-2.4.5/Stores.xml のように、最後に「.xml」をつけます。こうすると、$belongsTo などで指定したアソシエーションと一緒にデータを取得できます。
レスポンス自体には一切改行がないので閲覧には不便なので、IEなどのブラウザを利用するとよいです。
■ひとつの要素(view)を実行する
ID を指定して表示したい場合は、http://localhost:81/cakephp-2.4.5/Stores/view/20.xml のように指定します。最後に「.xml」をつければいいので、他の edit や add も同じです。
似た感じに見えますが、一覧の場合は response/items/item の中にそれぞれの要素が入っていて、view の場合は response/item の中に要素が入っています。
この入れ子が面倒な場合は、
1 2 3 | $items = array ( 'items' => array ( 'item' => $store )); $xml = Xml::fromArray( array ( 'response' => $items )); echo $xml ->asXML(); |
のように items 要素を入れてしまってもいいと思います。こうすることで、ひとつでも複数の要素でも、response/items/item の階層でデータが取れます。こっちのほうがいいかもしれませんね。
■add/edit はどうするのか?
add も edit もレスポンスとしては view と同じです。が、新規に作成したり編集したりするデータをクライアントから送らないといけません。ブラウザから編集するページは cake bake で自動生成しているので、WPF クライアントからサーバーへ POST するプログラムを作ります。これはちょっと次回へ。
■おまけ
適当ですが、こんな風にスクリプトを書いて、xml/*.ctp を作成します。
app フォルダに allbakexml.php のファイルで保存して実行してください。$views に複数形、単数形の順に書かないとダメなのは、Controller か Model の中身を見るようにすれば汎用化できるかも。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | <?php $views = array ( array ( 'adjustmentjobs' , 'adjustmentjob' ), ... array ( 'stores' , 'store' ), array ( 'workingsituations' , 'workingsituation' ), ); foreach ( $views as $v ) { $v0 = $v [0]; $v1 = $v [1]; $xml0 =<<< END <?php $items = array ( 'items' => array ( 'item' => $ $v0 )); $xml = Xml::fromArray( array ( 'response' => $items )); echo $xml ->asXML(); END ; mkdir ( "view/$v0/xml" ); $path = "view/$v0/xml/index.ctp" ; # print "$xml0n" ; file_put_contents ( $path , $xml0 ); $xml1 =<<< END <?php $item = array ( 'item' => $ $v1 ); $xml = Xml::fromArray( array ( 'response' => $item )); echo $xml ->asXML(); END ; # print "$xml0n" ; file_put_contents ( "view/$v0/xml/view.ctp" , $xml1 ); file_put_contents ( "view/$v0/xml/add.ctp" , $xml1 ); file_put_contents ( "view/$v0/xml/edit.ctp" , $xml1 ); } |