CakePHPのWeb APIレスポンスをXML型式で返す

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 );
}
カテゴリー: CakePHP, WPF パーマリンク