[CakePHP] RESTを使ってViewをXML形式で返す

REST ? CakePHP Cookbook v2.x documentation
http://book.cakephp.org/2.0/ja/development/rest.html
湘南社中テクニカルブログ: <b>Server</b>CakePHPでRESTのJSON APIを作成する
http://blog.shonanshachu.com/2012/01/cakephprestjson-api.html
Cakephp 2.x の REST API を作成してみる ≪ Hello My World
http://plmin.us/blog/2013/01/29/cakephp-2-x-rest-api/
CakePHP2.0で配列をXMLに変換し出力する | Code Life
http://code-life.net/?p=1096

簡単に言えば、HTTPのURLを指定して、戻り値をXMLやJSONで取得する。XML-PRCの場合は、送るときにXMLデータをPOSTするのだが、RESTの場合はURLを使う。いわゆる ASP.NET MVCのWeb APIと同じ。POSTやPUTなどを使うが、手軽のはPOSTのほう。メソッド名さえ揃えてしまえば、POSTオンリーでもよい気がする。

CakePHP には、もともとRESTの機能を持っていて、XMLで返すことが簡単にできる…はずなのだが、結構苦労する。というのも、

REST ? CakePHP Cookbook v2.x documentation
http://book.cakephp.org/2.0/ja/development/rest.html

を見ると、REST の方法には、

  • _serialize を使う方法
  • xml/ フォルダを作る方法

と2つ載っているものの、_serialize の方法は動かない。正確に言うと、内部データを XML に変換しているときにこけてしまう…う~ん、これがバグなのかどうかはわからない。なので、xml/ フォルダに index.ctp 等の View を作って対応する。
新しい View を作ると便利なのが、元の View(ブラウザで開くときのview)を残しておくことができることで、_serialize を使うと、Controller 自身を書き換えてしまうことになるので、ブラウザからちょいちょいと修正したいときに不便。なので、ブラウザ用の view は残したままで、REST 用の view≒XMLを返す view を新しく追加する、という方式のほうが保守的によい。ただし、セキュリティ的にはブラウザ用の view もロックをしないといけない。データの更新機能は管理者レベルにしたいことが多いので、実運用の場合は別途ロックを掛ける必要がある。

■app/Config/routes.php を書き換える

app/Config/routes.php に Router::parseExtensions(); を追加する。Router::mapResources(‘recipes’); のようにテーブル名をマッピングするようにも書いてあるのだが、これがなくても動く。ちなみに、ドキュメントでは「require CAKE … よりも前に」となっているが、後ろでも動作する。が、先頭に書いておくのがベター。

1
2
3
//Router::mapResources('Stores');    // 無くてもok
Router::parseExtensions('xml');
...

■app/Controller/AppController.php を書き換える

本来は、RequestHandler を個別のControllerに入れるべきなのだが、数が多いので AppController に入れてしまう。こうすると全てのコントローラーとビューで xml/ フォルダへの routes が有効になる。

1
2
3
class AppController extends Controller {
    public $components = array('RequestHandler');
}

■xml/index.ctp を作る

View/Stores/ に xml というフォルダを作って、その中に index.ctp などを新しく作る。

1
2
3
4
5
6
7
8
9
+ View
 + Stores
  - index.ctp
  - view.ctp
  - edit.ctp
  - add.ctp
  + xml
   - index.ctp
   - view.ctp

こうすることで、http://localhost/Stores/ を呼び出したときは View/Stores/index.ctp を、http://localhost/Stores.xml を呼び出したときは View/Stores/xml//index.ctp が呼び出される。多分、xml は小文字(フォルダー名と同じ)にしないとダメだと思う。実験環境は windows 上なので、XML でも xml で通るのだが、Unix 環境だと区別すると思われる。

View/Stores/xml/index.ctp

1
2
3
4
$items = array('items'=
    array('item'=> $stores));
$xml = Xml::fromArray(array('response'=>$items));
echo $xml->asXML();

XML形式で返すように asXML メソッドを使う。Xml::fromArray へ配列を渡す形式は、

XML ? CakePHP Cookbook v2.x documentation
http://book.cakephp.org/2.0/ja/core-utility-libraries/xml.html

を参照。CakePHP の 1.x と 2.x で渡すときの形式が違っているので注意が必要。おそらく _serialize の方法がダメなのは、ここを間違っていると思われ。

返す結果は、以下のように items タグの中に item が複数ある形式になる。「$stores」のところは、Controller で set している名前に随時変更する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<response>
 <items>
  <item>
   <Store>
    <ID>1</ID>
    <SellingCompanyID>1</SellingCompanyID>
    <AreaGroupID>26</AreaGroupID>
...
   <Store>
  <item>
    <item>
...
    </item>
 </items>
</response>

view の場合は、$store を元に単数で返せば ok

1
2
3
<!--?php $item = array('item'=--> $store);
$xml = Xml::fromArray(array('response'=>$item));
echo $xml->asXML();

■実行してみる

□http://localhost/Stores の場合

□http://localhost/Stores.xml の場合

□http://localhost/Stores/view/1 の場合

□http://localhost/Stores/view/1.xml の場合

index.ctp と view.ctp 自体はコピーアンドペーストで作れるので、適当なスクリプトを作って増産しておけばよい。編集機能(edit/add と DELETE)の場合は、むやみに使われないようにフォーム認証(BASIC認証)と合わせて作ることになるので、別途 C# のクライアントを作って試してみる。

~~~

追記

元のコントローラーを使っているのでページング機能が有効になっています。なので、デフォルトでは最初の20件しか取れません。このデータ自体は1万件以上あるので、すべてを取ってきてもダメ、できれば limit を指定したいところですね。全ページ数を取得するかどうかはさておき、「http://localhost/Stores/index/page:2.xml」のようにすれば、2ページ目も取得できます。

指定ページ以上を取得しようとして page:999.xml のようにすると、404 のエラーが発生するので、例外が出ている模様。このあたりを気を付ければ全件を取得できる?

カテゴリー: CakePHP パーマリンク