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 を書き換える
先頭の行に以下を追加します。
Router::parseExtensions('xml');
2.app/Controller/AppController.php を書き換える
RequestHandler コンポーネントを有効にすること、ページングの数を多めにとっておきます。
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 は、配列にします。
$items = array('items'=> array('item'=> $stores)); $xml = Xml::fromArray(array('response'=>$items)); echo $xml->asXML();
view.ctp, add.ctp, edit.ctp は一つだけの要素を返します。
$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 の中に要素が入っています。
この入れ子が面倒な場合は、
$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 の中身を見るようにすれば汎用化できるかも。
<?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 ); }