CakePHP3を触ってみました 〜ジェイソンあらわる〜

この記事はだいぶ前に書かれたものなので情報が古いかもしれません
13日は金曜が一番多いらしいぞ

この記事を三行にまとめると

JSONデータを返したい場合があると思います
3系を使う方が良さそうですぞ
13日は金曜になることが一番多いらしいですよ
APIの開発などでJSONデータを返したい場合があると思います。今日はそれをCakePHPでやってみましょう。わりとお手軽にできるので、知っとくと便利かも。

今回は「http://norm-nois.com/api/index」にリクエストを投げてJSONデータを取得する仕様を考えてみます。



JSONデータの出力(CakePHP2)

まずはCakePHP2で作る場合です。やるべきことは、ApiController.phpを用意することと、/api/indexにアクセスした場合は強制的にjsonデータを出力するようにroutes.phpに設定を書くことです。

さっそくコードを書いてみましょう。

//ApiController.php
class ApiController extends AppController {
  public $components = array('RequestHandler');

  public function index() {
    $site = 'あかつきのお宿';
    $this->set(compact('site'));
    $this->set('_serialize', 'site');
  }
}

//routes.php
Router::connect('/api/index', array('controller' => 'api', 'action' => 'index', 'ext' => 'json'));//この一行を追加

基本的には通常のページを作るのと同じですね。

ポイントは、ApiController.phpの方で「RequestHandler」コンポーネントを読み込むことと、「_serialize」に値をセットすること、それからroutes.phpのRouter::connectの中身ですね。今回は「/api/index」のページのみJSONに対応するためにこんな書き方をしましたが、もし全部のページに対応したいのならこう↓。

Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display', 'ext' => 'json'));

ようは「’ext’ => ‘json’」という要素を追加すればオッケーってことですね。

上記のように書いた場合、画面にはこんな↓JSONデータが出力されます。

"\u3042\u304b\u3064\u304d\u306e\u304a\u5bbf"

パッと見何て書いてあるか分かりませんが「あかつきのお宿」と表示されています。json_encode()はデフォルトだとマルチバイト文字をエスケープするので、こんな感じになってます。エスケープしない方法は後ほどやりましょう。

今は文字列だけを表示しましたが、もちろん配列を渡すことも可能です。

//$this->set('_serialize', 'site')を書き換える
$this->set('_serialize', array('site'));

//出力結果
{"site": "\u3042\u304b\u3064\u304d\u306e\u304a\u5bbf"}

「array(‘site’ => ‘あかつきのお宿’)」がJSONデータになった状態です。

複数の変数を渡す場合も書き方は同じ。

//ApiController.php
$site = 'あかつきのお宿';
$url = 'http://norm-nois.com/api/index';
$this->set(compact('site', 'url'));
$this->set('_serialize', array('site', 'url'));

//出力結果
{"site": "\u3042\u304b\u3064\u304d\u306e\u304a\u5bbf","url": "http:\/\/norm-nois.com\/api\/index"}

サイト名とURLのJSONデータです。



エスケープキャンセル(CakePHP2)

ではマルチバイトのエスケープのキャンセルをば。

やり方は二つあります。

一つはコアライブラリにある「JsonView.php」の中身をいじること。「/Cake/View/JsonView.php」を「App/View/JsonView.php」にコピって「_serialize()」というメソッドをちょちょっと書き換えます。

//JsonView.php
protected function _serialize($serialize) {
  〜中略〜

  //return json_encode($data);を書き換え
  return json_encode($data, JSON_UNESCAPED_UNICODE);
}

//出力結果
{"site":"あかつきのお宿","url":"http:\/\/norm-nois.com\/api\/index"}

json_encode()の第二引数に「JSON_UNESCAPED_UNICODE」をセットするとエスケープをキャンセルできます。

ちなみに今は出力内容が一行で表示されていますが、print_r()に対するpr()のように、整形して見たい場合には「JSON_PRETTY_PRINT」をセットします。

//JsonView.php
return json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)

//出力結果
{
  "site": "あかつきのお宿",
  "url": "http:\/\/norm-nois.com\/api\/index"
}

二つ以上指定する場合は、コンマ区切りでも配列でもなく「|」で区切ります。バーティカルバーとかバーティカルラインと呼ばれるやつです。俺も今回初めて読み方知ったわ。

ちなみにデバッグを出力する設定にしている場合は、Cakeさんが勝手にJSON_PRETTY_PRINTをつけて表示してくれます。



もう一つのやり方は、ビューファイルを使って自分でjson_encode()を行うこと。

これまでの書き方はビューファイルの作成を必要としませんでした。でも「$this->set(‘_serialize’, ***)」をコントローラーで書かない場合は、通常と同じようにctpファイルが読み込まれます。

//ApiController.php
$site = 'あかつきのお宿';
$url = 'http://norm-nois.com/api/index';
$data = array('site' => $site, 'url' => $url);
$this->set(compact('data'));

//View/Api/json/index.ctp
<?php echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) ?>

//出力結果
{
  "site":"あかつきのお宿",
  "url":"http:\/\/norm-nois.com\/api\/index"
}

気をつけなきゃいけないのは、ビューファイルの置く場所。通常のページを表示する場合はApiフォルダの下にindex.ctpを作りますが、ここではApiフォルダの下にさらに「json」というフォルダを作って、その中にindex.ctpを入れます。

これはroutes.phpに書いた「’ext’ => ‘json’」が関係してます。もし「’ext’ => ‘xml’」と書けば、読み込まれるビューファイルは「Api/View/xml/index.ctp」になります。

ちなみに「’ext’ => ‘aaa’」とかにすると、そんな形式のもんはねえって判断されて、普通に「Api/View/index.ctp」が読み込まれるようです。



URLに.jsonをつける(CakePHP2)

例えば「http://norm-nois.com/api/index」では通常のHTMLページを表示して、「http://norm-nois.com/api/index.json」というURLでJSONを受け取れるようにしたいなんて場合もあるかもしれない。

その場合は、routes.phpにこんな設定を書く。

//以下の一行を削除
Router::connect('/api/index', array('controller' => 'api', 'action' => 'index', 'ext' => 'json'));

//以下の二行を追加
Router::parseExtensions();
Router::setExtensions(array('json'));

この二行を追加すれば「/api/index.json」に限らずどのURLでも「.json」をつけることが可能です。json以外でも、例えば「setExtensions(array(‘pdf’))」と書けば「http://norm-nois.com/api/index.pdf」といったURLも使えるようになります。

コントローラーやビューの書き方は先ほどと一緒です。「_serialize」に値をセットすればビューファイルが不要になるし、JsonView.phpファイルをいじればエスケープや整形表示ができるのも同様。

「Router::connect(‘/api/index’, array(‘controller’ => ‘api’, ‘action’ => ‘index’, ‘ext’ => ‘json’));」を削除せずに残しておいた場合は、「http://norm-nois.com/api/index」と「http://norm-nois.com/api/index.json」のどちらもJSONを受け取るページになります。どのパターンが良いかは仕様次第ですかね。



JSONデータの出力(CakePHP3)

CakePHP2の説明だけでだいぶ長くなっちゃいましたね。さーせん。コーヒーブレイクでも入れて、もうちょっとだけおつき合いくだせえ。

次はCakePHP3です。書き方がちょっと違うだけでやり方はCakePHP2とそんな変わりませんが、CakePHP3の方がやや楽になってます。

//ApiController.php
namespace App\Controller;
use App\Controller\AppController;
class ApiController extends AppController {
  public function initialize() {
    parent::initialize();
    $this->loadComponent('RequestHandler');
  }

  public function index() {
    $site = 'あかつきのお宿';
    $url = 'http://norm-nois.com/api/index';
    $this->set(compact('site', 'url'));
  }
}

//routes.php
Router::scope('/', function ($routes) {
  $routes->connect('/api/index', ['controller' => 'Api', 'action' => 'index', '_ext' => 'json']);
  $routes->fallbacks('DashedRoute');
});

//出力結果
{"site":"\u3042\u304b\u3064\u304d\u306e\u304a\u5bbf","url":"http:\/\/norm-nois.com\/api\/index"}

CakePHP2と違うのは、コントローラーで「_serialize」に値をセットする必要がないところですね。CakePHP3では「$this->set()」でセットされた値を自動的にJSONデータで出力してくれます。テンプレートファイルも不要。

routes.phpもちょっと変わってます。「’ext’ => ‘json’」じゃなくて「’_ext’ => ‘json’」です。先頭のアンダーバーを忘れずに。



エスケープキャンセル(CakePHP3)

CakePHP2に比べると、3の場合のエスケープキャンセルは簡単です。コントローラーに次の一行を追加するだけでOK。

//ApiController.php
public function index() {
  $site = 'あかつきのお宿';
  $url = 'http://norm-nois.com/api/index';
  $this->set(compact('site', 'url'));

  //この一行を追加
  $this->viewVars['_jsonOptions'] = JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT;
}

//出力結果
{
  "site": "あかつきのお宿",
  "url": "http:\/\/norm-nois.com\/api\/index"
}

これだけでOKです。コアファイルをいじったりテンプレートを自作する必要はナッシング。



URLに.jsonをつける(CakePHP3)

これもroutes.phpに一行追加するだけ。

//routes.php
Router::scope('/', function ($routes) {
  //以下の一行を追加
  $routes->extensions(['json']);
});

楽勝だーね。






ふいー。相変わらず長くてすみません。でも作業自体はそんな大変ではないので、だばーっと流し読みしながらちゃちゃーっと実装しちゃってくださいな。

2に比べると3の方が楽かなーって感じですね。だからJSON出力用の何かを新規に作りたいのであれば、3系を使う方が良さそうですぞ。



その他のCakePHP3を触ってみましたの記事はこちら
まとめという名の箸休め
 もしかしたら何か関連しているかも? 
 質問や感想などお気軽にコメントしてください