ZendFramework2ガイド

機能編

JSONをレスポンス

Ajaxでの通信フォーマットとしてのJSON

JSONとはJavaScript Object Notationの略で、Javascriptのオブジェクトの表現形式です。

現在はAjaxによる、クライントとサーバーの間で非同期通信により、画面遷移を伴わない動的なクライアントアプリケーションが一般的になっています。Ajaxはそれ自体が特別な技術であるわけではなく、Javascript、HTTP、XMLという様な当たり前に存在する技術を組合せた通信の手法に過ぎません。実際に行なわれる通信はHTTPそのもので、Javascriptのプログラムからブラウザの機能を利用してHTTPリクエストを送信出来る。通信の際のデータフォーマットとしてXMLとして利用するというだけの話です。これは、リクエストを受けるサーバー側から見ると、他の通信と何ら変わらず、来たリクエストを処理してレスポンスするというだけで、それがクライアント側からは非同期でリクエストしているか、そうではないかという事は、サーバーに取ってはあまり関係のない話なわけです。

で、AjaxのxはXMLを表し、上述の通りリレスポンスのデータフォーマットとしてXMLを想定してこの名付けなわけですが、XMLって、クライアントサイドのJavascriptとしても、サーバーサイドのPHPとしても、結構扱いが面倒くさいわけです。そこで今、非同期通信時のデータフォーマットとして主流なのはJSONです。これはJavascriptのオブジェクトを表現する記法で、文字列としても表現することが可能なため、JSON形式の文字列をPHP側で作り、それをレスポンスすることで、Javascript側としてはこれほど扱いやすいフォーマットは無いわけです。だって名前の通り、Javascript用のフォーマットなわけですから。

ちょっと語弊はあるかもしれませんが、JSONって結局連想配列みたいなもので、PHPでは連想配列をjson_encodeという関数に通すだけでJSON形式の文字列に変換してくれるわけです。なので、クライアントからの非同期リクエストを想定したPHP側の機能ではjson_encodeしたものをそのままechoしておくだけで良いことになります。

Zend2の場合で言うと、Javascriptで非同期リクエストが来るように作った場合、当然そのリクエスト先のURLがあるわけで、そうするとそのURLのルーティング先であるアクションメソッドが存在する事になるはずです。このアクションメソッドはJavascriptからの非同期通信を想定していますので、例えばそれがHTMLのDOMの一部を書き換えるための、その一部のHTMLそのものを帰すような作りのアクションの場合はテンプレートをそのまま出力しておけば良いという考え方になりますが、例えばデータ更新を行うようなリクエストであった場合は少し事情が異なり、通常ならそのデータ更新が成功したのか、失敗したのかというような情報と、失敗したのなら、失敗理由を表すエラーメッセージを添えたりするわけです。これはまさに連想配列で表せる情報で、例えば以下のようになります。

array(
    'result' => false,
    'error_message' => 'Failed to update.',
);

これをJSONに変換すると以下の様な文字列になります。

{"result":false,"error_message":"Failed to update."}

これをそのままレスポンスすると、Javascript側ではこれをそのままobjectとして扱うことができるわけです。

で、Zend2のアクションメソッド内でどのような処理を行うかというと、単純に考えると、

use Zend\Json\Json;

class IndexController extends AbstractActionController
{
    public function updateAction()
    {
        $model = new Hoge();
        $result = $model->update($this->params()->fromPost());
        
        $response = array('result' => $result);
        if ($result == false) {
            $response['error_message'] = 'Failed to update.';
        }

        echo Json::encode($response);
        exit;
    }
}

という感じで、連想配列としてレスポンスするデータを組み立て、JSONに変換してechoします。

json_encode関数でも良いですが、Zend2にはJSONのライブラリが用意されているのでそれを利用しています。
で、アクションメソッドで何もreturnしないと、勝手にViewModelが裏で生成され、テンプレートを出力しようとするので、レスポンスが変になってしまいます。なのでアクションメソッド内でexitして、スクリプトをそこで強制終了しているわけです。
しかし、これはあまりに原始的な方法で、強制的にexitしてフレームワークの挙動を一切無視しています。
結果がよければそれで良いとも言えますが、Zend2はしっかりとしたフレームワークなので、こういうパターンのレスポンスも想定していそうな気がしませんか?
そうです。echoしてexitなんてカッコ悪い事しなくても、ちゃんとやり方があるんです。それをこれから紹介します。

ViewModelの代わりにJsonModel

Zend\View\Renderer名前空間には、ビューとしてのレスポンスを直接生成するRendererと呼ばれるコンポーネント達がいます。デフォルトで使われるのはPhpRendererというやつです。Zend2のデフォルトのテンプレートはPHPタグを埋め込む形式のHTMLテンプレートで、言って見ればただのPHPファイルです。この形式のテンプレートを読み込んで変数を割り当て、レスポンスするHTMLを生成しているのがPhpRendererなわけですが、同じ名前空間にJsonRendererというのがいます。これは、PhpRendererのようなテンプレートエンジン的な動きをするRendererではありませんが、コントローラーから受け取った配列データをそのままJSON形式に変換してくれるRendererです。

このJsonRendererを利用してJSON形式の情報をレスポンスしたい場合には、通常アクションメソッドでViewModelのインスタンスを返す代わりにJsonModelのインスタンスを返せばいいのです。

use Zend\View\Model\JsonModel;

class IndexController extends AbstractActionController
{
    public function updateAction()
    {
        $model = new Hoge();
        $result = $model->update($this->params()->fromPost());
        
        $response = array('result' => $result);
        if ($result == false) {
            $response['error_message'] = 'Failed to update.';
        }

        $jsonModel = new JsonModel($response);
        $jsonModel->setTerminal(true);
        return $jsonModel;
    }
}

JsonModelのインスタンスにsetTermimnal(true)としているのは、レイアウトを無効にする設定です。
Zend2のテンプレートはレイアウトとアクションテンプレートの2層になっており、それぞれ、ViewModelのインスタンスを生成します。もっとも、レイアウトのViewModelはフレームワークが裏で作ってくれているので、そのことは意識することはありません。そしてアクションメソッドでreturnするViewModelはアクションテンプレート用のViewModelです。なのでこれをJsonModelに変えてreturnしても、実はレイアウト用のViewModelは別に存在し、そのままではレイアウトテンプレートの中にJSONを出力しようとして変なことになります。
なので、setTerminal(true)としてレイアウトを無効にする必要があるわけです。

RendererとしてJsonRendererが選択される設定

JsonをレスポンスするためアクションメソッドでやることはJsonModelを返すだけのことですが、実はこれだけでは動きません。

Zend2のMVCの仕組みとして、アクションメソッドがViewModelをreturnした場合には、それに対してはRendererとして数ある中からPhpRendererが選択されるようになっており、
同じようにJsonModelをreturnした場合には、JsonRendererを選択するという想定になっているわけですが、実はデフォルトではPhpRenderer以外は有効になっておらず、なにをしてもPhpRendererが選択されてしまう状態になっています。

そこでJsonRendererを有効にする必要があるわけです。
何をするかというと、module.config.phpに設定を加えるだけです。

module/Hoge/config/module.config.php
return array(

    'view_manager' => array(
	
        'strategies' => array(
            'ViewJsonStrategy',
        ),

module.config.phpのview_managerの設定に上記のように'strategies'という設定を追加し、ViewJsonStrategyという記述を追加します。
これで、アクションメソッドでJsonModelのインスタンスをreturnすれば、JSONが出力されます。

Strategyについては詳しくは「テンプレートエンジンの変更(Smarty)」のところで解説していますが、"renderer"イベント時にRendererのインスタンスを返してくれるクラスです。もう少し詳しく言うと、rendererイベントは、renderイベント内で2次的に動くイベントなのですが、このrendererイベントにはZend\View\Strategy名前空間にあるStrategyクラスがイベントリスナとして登録されています。StrategyクラスはselectRendererというメソッドを備えており、Rendererクラスのインスタンスを返すだけの役割を持っています。こうしてrendererイベントでRendererのインスタンスを取得し、このRendererで出力を生成するという仕組です。
で、デフォルトでは数あるStrategyの中でPhpRendererStrategyだけが有効になっており、得られるRendererはPhpRendererしかありえない状態になっているわけです。そこに上記の設定によってJsonStrategyも有効にする事により、選択肢としてJsonRendererも入ってくることになります。後はアクションメソッドから来たビューモデルがViewModelなのか、JsonModelなのかによって、選択されるRendererが切り替わるというわけです。