Curry
PHP Framework

REST

RESTfulリクエスト

CurryではRESTfulなリクエストに対応し、コントローラー、アクションで表されるリソースに対して、HTTPメソッドによって処理を分けることができるようになっています。
通常、アクション単位にメソッドを定義し、URLと紐付いていますが、これに加えてGET、POST、PUT、DELETEという4つのHTTPメソッドによって更にPHPアクションメソッドを4つに分割することが可能です。

RESTコントローラー

RESTリクエストに対応するには、コントローラークラスを通常のControllerではなく、RestControllerを継承するだけです。RestControllerはControllerとほぼ同様の働きをしますが、通常のアクションメソッドは、HTTPのGETメソッドによるアクセスの場合のみ実行されます。その他のHTTPメソッドの場合はGETのアクションメソッドの末尾にHTTPメソッド名を付加したメソッドが呼び出されます。

app/controllers/index_controller.php
class IndexController extends RestController
{
    // /index/hogeへGETメソッドでリクエストの場合
    public function hoge()
    {
    }
    // /index/hogeへPOSTメソッドでリクエストの場合
    public function hogePost()
    {
    }
    // /index/hogeへPUTメソッドでリクエストの場合
    public function hogePut()
    {
    }
    // /index/hogeへDELETEメソッドでリクエストの場合
    public function hogeDelete()
    {
    }
}

基本的にRestControllerとControllerの違いはこの部分だけです。
ちなみにindexアクションの場合は少し特殊で以下のようになります。

app/controllers/index_controller.php
class IndexController extends RestController
{
    // /index/indexへGETメソッドでリクエストの場合
    public function index()
    {
    }
    // /index/indexへPOSTメソッドでリクエストの場合
    public function post()
    {
    }
    // /index/indexへPUTメソッドでリクエストの場合
    public function put()
    {
    }
    // /index/indexへDELETEメソッドでリクエストの場合
    public function delete()
    {
    }
}

このように、indexアクションの場合はindexPostなどのように末尾にHTTPメソッド名がつくのではなく、HTTPメソッドそのものの名前となります。

パラメータの扱い

通常のコントローラーではリクエストパラメーターをGET、POST、URLパラメーターに分類し、それぞれReauestクラスのgetQueryメソッド、getPostメソッド、getParamsメソッドで取得できるようになっていますが、RESTコントローラーではそもそもHTTPメソッドごとにアクションが別れるため、このパラメーターの切り分けは無意味です。
そのため、RESTコントローラーではリクエストパラメーターをRequestクラスのgetRestPramsメソッドで一元的に取得できるようになっています。RestControllerのrestParamsフィールドでも同様の値が取得できます。

app/controllers/index_controller.php
class IndexController extends RestController
{
    public function hoge()
    {
        // GETパラメーターを取得()
        $params = $ths->request->getRestParams();
        // $params = $this->restParams; これでも同じ結果
    }
    public function hogePost()
    {
        // POSTパラメーターを取得
        $params = $ths->request->getRestParams();
    }
    public function hogePut()
    {
        // PUTパラメーターを取得
        $params = $ths->request->getRestParams();
    }
    public function hogeDelete()
    {
        // DELETEパラメーターを取得
        $params = $ths->request->getRestParams();
    }
}

レスポンス

RESTでは、同一のリソースに対して行う処理をHTTPメソッドによって切り分けるという考え方です。
WEBサイトを前提に考えるとすると、基本となるGETメソッドがデータ取得の意味合いとなるため、WEBブラウザ上にページを表示するのは通常はGETメソッドの役割となります。逆に言えばその他のメソッドは更新系のメソッドであり、レスポンスとしてはWebサイトのユーザーインターフェイスとしてのHTMLを返すわけではないと考えることもできます。

そのためCurryでは、GETメソッドに対応するアクションメソッドは、通常のコントローラーと同様にビュークラスによってHTMLが出力されますが、その他のメソッドの場合は、特に何もしなければレスポンスなしになります。レスポンスとして何を出力するのかはユーザー次第です。

リダイレクト

更新系の処理の後については、よくあるパターンは更新後の状態でページを再表示するというものです。これはその更新系メソッドのレスポンスとして、処理の最後にGETメソッドと同様のページデータ取得とHTML生成処理を行うという方法も考えられますが、更新処理の2重処理を防ぐためにもリダイレクトするというのが一般的です。
それを実現するのは以下のようなコードになります。

app/controllers/index_controller.php
class IndexController extends RestController
{
    public function hoge()
    {
        // データ取得
        $model = $this->model('Hoge');
        $data =  $model->getData();
        // ビューへデータ割り当て
        $this->view->data = $data;
    }
    public function hogePost()
    {
        // データ追加
        $model = $this->model('Hoge');
        $model->insertData($this->restParams);
        // GETメソッドでリダイレクト
        $this->redirect('hoge');
    }
 }

これでPOSTメソッドでデータの追加後にGETメソッドへリダイレクトし、ページ最表示、となります。
このように個々のメソッドでリダイレクト処理を記述しても良いのですが、この動きをお決まりとする場合にはもっと簡単な方法があります。それには、RestControllerのprotectedフィールドである、autoRedirectにtrueを設定するだけです。

app/controllers/index_controller.php
class IndexController extends RestController
{
    // オートリダイレクトを有効に
    protected $autoRedirect = true;

    public function hoge()
    {
        // データ取得
        $model = $this->model('Hoge');
        $data =  $model->getData();
        // ビューへデータ割り当て
        $this->view->data = $data;
    }
    public function hogePost()
    {
        // データ追加
        $model = $this->model('Hoge');
        $model->insertData($this->restParams);

        // オートリダイレクトが有効なため、処理後に自動的にGETメソッドへリダイレクト
    }
 }

こうすることでいちいちredirectメソッドを実行する必要がなく、コーディング量を減らすことができます。
もちろん、特定のメソッドだけで有効にしたい場合はそのメソッド内で$this->autoRedirect = true;と記述すればOKです。
逆に、基本は有効で特定のフィールドだけ無効にしたい場合はフィールドでtrueに設定し、特定のメソッド内でだけfalseをセットします。

処理結果を返す

例えばAjaxによる非同期更新処理などの場合は、処理結果を何らかの形でレスポンスとして返すのが一般的です。
この場合、処理の最後にレスポンス出力の処理を行う事になります。
例えばプレーンテキストをレスポンスとして返す場合は以下のようになります。

app/controllers/index_controller.php
class IndexController extends RestController
{
    public function hogePost()
    {
        // データ追加
        $model = $this->model('Hoge');
        $result = $model->insertData($this->restParams);

        // 結果を文字列としてレスポンス
        $res = 'OK';
        if ($res == false) {
            $res = 'NG';
        }
        $this->response->plain($res);
    }
 }
失敗の場合のレスポンス
NG

コントローラーのresponseフィールドにはResponseクラスのインスタンスが格納されており、これを利用して出力が可能です。単純な文字列を出力する場合はplainメソッドを利用します。

AjaxのレスポンスとしてはJSONやXMLを利用するのも一般的です。
これもResponseクラスで簡単に行えます。

app/controllers/index_controller.php
class IndexController extends RestController
{
    public function hogePost()
    {
        // データ追加
        $model = $this->model('Hoge');
        $result = $model->insertData($this->restParams);

        // 結果を文字列としてレスポンス
        $res['result'] = '0';
        if ($result == false) {
            $res['result'] = '1';
            $res['message'] = '追加に失敗しました。';
        }
        // JSON形式でレスポンス
        $this->response->json($res);
        // XML形式の場合
        //$this->response->xml($res);
    }
 }
失敗の場合のJSON形式のレスポンス
{"result":"1","message":"\u8ffd\u52a0\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002"}
失敗の場合のXML形式のレスポンス
<?xml version="1.0"?>
<response>
  <result>1</result>
  <message>追加に失敗しました。</message>
</response>

任意のテンプレートをHTMLとして表示

GET以外のメソッドはデフォルトではレスポンスなしですが、
ビューを有効にし、テンプレートを指定すれば、GETメソッド同様にテンプレートを出力することも可能です。

app/controllers/index_controller.php
class IndexController extends RestController
{
    public function hogePost()
    {
        // データ追加
        $model = $this->model('Hoge');
        $result = $model->insertData($this->restParams);

        // 処理完了表示ページテンプレート出力
	$this->view->enableRendering(true);
	$this->view->setTemplate('complete');
    }
 }

しかしこの場合、ブラウザの更新ボタンによって2重処理の危険性があります。
そのため、実際は2重処理防止のチェック処理を挟むか、別アクションとしてリダイレクトするのが望ましいと言えます。

app/controllers/index_controller.php
class IndexController extends RestController
{
    public function hogePost()
    {
        // データ追加
        $model = $this->model('Hoge');
        $result = $model->insertData($this->restParams);

        // 処理完了表示アクションへリダイレクト
	$this->redirect('complete');
    }

    public function complete()
    {
    }
 }

PUTやDELETEメソッドのリクエスト方法

そもそも、HTMLの仕様上、フォームからの送信はGETかPOSTしかできません。
それではPUTやDELETEはどのようにして送信するかですが、方法は2つあります。
一つはAjaxを利用する方法、もうひとつは擬似的になりますがCurryの仕組みを利用してフォームから送信する方法です。

Ajaxでリクエスト

HTMLのフォームからはPUTメソッドやDELETEメソッドは送信できませんが、Ajaxを利用すれば可能です。
ここではjQueryを利用してAjaxによるPUTメソッドの実行を例にします。

htdocs/js/index.js
$(function(){
    $('form input:button').click(function(){
        $.ajax({
            'type' : 'PUT',
            'url' : '/index/hoge',
            'data' : {
                'param1' : $(this).find('input[name="param1"]').val(),
                'param2' : $(this).find('input[name="param2"]').val(),
                'param3' : $(this).find('input[name="param3"]').val()
            },
            'dataType' : 'json',
            'success' : function(res){
                if (res.result != '0') {
                    alert(res.message);
                }
            }
        });
    });
});
app/controllers/index_controller.php
class IndexController extends RestController
{
    public function hogePut()
    {
        $model = $this->model('Hoge');
        $result = $model->updateData($this->restParams);

        $res['result'] = '0';
        if ($result == false) {
            $res['result'] = '1';
            $res['message'] = '追加に失敗しました。';
        }
        $this->response->json($res);
    }
 }

Curryの仕組みを利用してのフォーム送信

Curryではフォームから擬似的にPUTやDELETEメソッドを送信することが可能です。
それには、formにスタイルシートのクラスとして"rest"を指定した上で、method属性に"PUT"などを指定するだけです。

app/views/templates/index/hoge.tpl
<form class="rest" method="PUT" action="/index/hoge/">
  <input type="text" name="param1" />
  <input type="text" name="param2" />
  <input type="text" name="param3" />
  <input type="submit" name="send" value="更新" />
</form>
app/controllers/index_controller.php
class IndexController extends RestController
{
    public function hogePut()
    {
        $model = $this->model('Hoge');
        $result = $model->updateData($this->restParams);

        $res['result'] = '0';
        if ($result == false) {
            $res['result'] = '1';
            $res['message'] = '追加に失敗しました。';
        }
        $this->response->json($res);
    }
 }

このフォームを送信する際、実際にはmethod属性を"POST"に書き換えて送信しています。
そして元々method属性に書かれていた"PUT"はフォームからの送信パラメータの一つとして別途、送信されます。
Curryではそのパラメータが存在すればPUTメソッドだと判断し、Putのアクションメソッドを呼び出します。なので実際にはPUTメソッドでリクエストされているわけではなく、擬似的なものです。

Curryでは、コントローラーがRestControllerを継承している場合に限り、POSTパラメーターから"_method"というキーを検索します。存在すればこれをHTTPメソッドとして扱います。
そのため、以下の例のように最初からフォームのmethod属性をPOSTにし、別途、input:hiddenでvalueに"PUT"をセットしておけば同じ動きとなります。

<form method="POST" action="/index/hoge/">
  <input type="hidden" name="_method" value="PUT" />
  <input type="text" name="param1" />
  <input type="text" name="param2" />
  <input type="text" name="param3" />
  <input type="submit" name="send" value="更新" />
</form>

前者の、method属性に"PUT"を指定するフォームの場合、Curryフレームワークのパッケージに同梱している"site/htdocs/js/rest.js"が重要になります。擬似的なRESTリクエストを実現しているのはこのJavascripだからです。このjsはjQueryとネイティブのJavascriptの両方に対応しています。