CakePHP 4 で JSON 出力を実装する方法

はじめに

CakePHP には JSON 形式で出力するための機能が組み込まれています。方法にはいくつかありますが、今日は1つのアクションで JSON 出力のみを行う場合と、URL に応じて HTML 表示と JSON 出力の両方を行う場合の2パターンをご紹介します。

目次
  1. JSON 形式で出力
  2. 画面表示と JSON 出力を1つのアクションで行う
  3. 注意事項
  4. おわりに

1. JSON 形式で出力

実装するアクションの出力が JSON 形式だけでいい場合の実装例をご紹介します。動作確認には下記テーブルを使用しました。

CREATE TABLE `scores` (
  `id` int(11) NOT NULL,
  `name` varchar(255) NOT NULL,
  `nickname` varchar(255) NOT NULL,
  `score` int(11) NOT NULL,
  `created` datetime NOT NULL,
  `modified` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

ALTER TABLE `scores`
  ADD PRIMARY KEY (`id`),
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

INSERT INTO `scores` (`id`, `name`, `nickname`, `score`, `created`, `modified`) VALUES
  (1, '佐藤 太郎', 'ブラウンドッグ', '10280', '2020-02-15 12:34:01', '2020-02-15 23:45:02'),
  (2, '鈴木 次郎', 'ブルーバード', '5310', '2020-02-16 12:34:03', '2020-02-16 23:45:04'),
  (3, '高橋 三郎', 'ホワイトキャット', '7670', '2020-02-17 12:34:05', '2020-02-17 23:45:06');
> cd \path\to\cakephp4
> bin/cake bake model scores --no-test --no-fixture
--no-test --no-fixture はテスト用のファイルを作成しないためのオプションです。
今回の例はモデルファイルがなくても動きます。
/src/Controller/SampleController.php
<?php
declare(strict_types=1);

namespace App\Controller;

class SampleController extends AppController
{
  public function index()
  {
    $this->loadModel('Scores');
    $scores = $this->Scores->find()
      ->select(['id', 'nickname', 'score'])
      ->all();
    $this->set(compact('scores'));

    $this->set('message', 'これはサンプルです');

    // JSON で出力
    $this->viewBuilder()
      ->setClassName('Json')
      ->setOption('serialize', ['message', 'scores'])
      ->setOption('jsonOptions', JSON_FORCE_OBJECT);
  }
}

このコードは Cookbook の例を参考に作成しました。テンプレートファイルを作成する必要はありません。setClassName() はページ下部の見出し「Example Usage」のサンプルコードに出ています。

JSON_FORCE_OBJECT は、JSON 出力したい元データが連想配列ではないときに、JSON 出力形式をオブジェクトにするためのオプションです。

今回の例では ->setOption('jsonOptions', JSON_FORCE_OBJECT) の有無で scores の結果が下記のように変わります。

// ->setOption('jsonOptions', JSON_FORCE_OBJECT) 有り
{
  ...
  "scores": { // ← オブジェクト
    "0": {
      "id": 1,
      "nickname": "\u30db\u30ef\u30a4\u30c8\u30c9\u30c3\u30b0",
      "score": 10280
    },
    ...
  }
}

// 無し
{
  "scores": [ // ← 配列
    {
      "id": 1,
      "nickname": "\u30db\u30ef\u30a4\u30c8\u30c9\u30c3\u30b0",
      "score": 10280
    },
    ...
  ]
}

出力結果が 配列 か オブジェクト かは JSON を読み取る側とも関係することなので覚えておくと良いと思います。

2. 画面表示と JSON 出力を1つのアクションで行う

先の例では JSON 出力のみを行うアクションでしたが、通常の HTML と JSON 出力の両方を URL で切り換える方法もあります。

/src/Controller/SampleController.php
public function index()
{
  $this->loadModel('Scores');
  $scores = $this->Scores->find()
    ->select(['id', 'nickname', 'score'])
    ->all();
  $this->set(compact('scores'));

  $this->set('message', 'これはサンプルです');

  $this->viewBuilder()
    ->setOption('serialize', ['message', 'scores'])
    ->setOption('jsonOptions', JSON_FORCE_OBJECT);
}
/templates/Sample/index.php
<h1><?= $message ?></h1>
<table>
  <thead>
    <tr>
      <th>ID</th>
      <th>Nickname</th>
      <th>Score</th>
    </tr>
  </thead>
  <tbody>
    <?php foreach ($scores as $score) : ?>
      <tr>
        <td><?= $score->id ?></td>
        <td><?= h($score->nickname) ?></td>
        <td><?= number_format($score->score) ?>
      </tr>
    <?php endforeach; ?>
  </tbody>
</table>
/config/routes.php
$routes->scope('/', function (RouteBuilder $builder) {
  $builder->setExtensions(['json']); // ←追加
  ...
}

ブラウザで /sample と /sample.json にアクセスしてみてください(/sample/index.json でも OK です)。/sample でアクセスすると HTML が表示されて、/sample.json の場合は JSON 形式で出力されると思います。

3. 注意事項

JSON 出力を実装する際には1つ注意すべきことがあります。それはデータを取得する際に、JSON に出力したくないフィールドは含めないということです。

例えば先に詳細したコードで $scores を取得する箇所を下記に変更して、/sample と /sample.json を開いてみてください。

/src/Controller/SampleController.php ※ 望ましくない例
public function index()
{
  $this->loadModel('Scores');
  $scores = $this->Scores->find()->all(); // ← ここを変更
  $this->set(compact('scores'));

  $this->set('message', 'これはサンプルです');

  $this->viewBuilder()
  ->setOption('serialize', ['message', 'scores'])
  ->setOption('jsonOptions', JSON_FORCE_OBJECT);
}

/sample への影響はほとんどありませんが、/sample.json には name フィールドなどが含まれてしまいます。

HTML を併せて表示する場合だと、そちらで確認した気になってしまい JSON に不要なデータが含まれていることを見逃してしまう場合があります。またデータ量が多い場合や、コードが複雑な場合には、そのリスクが高くなります。動作確認の際には必ず JSON に不要なデータは含まれていないを確認することをお勧めします。

4. おわりに

フロントエンドとサーバサイドでデータのやり取りなど、JSON を使う場面は増えてきています。

CakePHP 4 で実装する際に、その JSON 出力機能を使わずに json_encode() などで実現することもできますが、今回紹介したようなコードの方が少なくて済むと思います。

学習コストはかかりますが覚えると強力な武器になるので、JSON 出力を行う際にはご検討ください。