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 );
}


