CakePHP3.6で作法に則りつつHTMLをそのまま返せるAPIを作る

PHP CakePHP

やっほー るかだよ。
最近寒くてお布団から出られないよ…

CakePHP3.6でAPI作ってたときにHTMLデータをシリアライズ化しないで返せないかなと思っていろいろ調べたのでメモ。

基本的な動作

基本的には公式マニュアル通りにやれば簡単に作れます。

▼参考
REST - 3.6
JSON と XML ビュー

以下のコードはREST - 3.6こっちから流用してます!

<?php
// src/Controller/RecipesController.php
class RecipesController extends AppController
{
    public function initialize()
    {
        parent::initialize();
        $this->loadComponent('RequestHandler');
    }

    // ・・・省略
				
    public function add()
    {
        $recipe = $this->Recipes->newEntity($this->request->getData());
        if ($this->Recipes->save($recipe)) {
            $message = 'Saved';
        } else {
            $message = 'Error';
        }
        $this->set([
            'message' => $message,
            'recipe' => $recipe,
            '_serialize' => ['message', 'recipe']
        ]);
    }

     // ・・・省略
}

Controllerのinitializeで$this->loadComponent('RequestHandler');します。
そしてあとはそれぞれのアクションで処理を書いていき、最終的に

$this->set([
    'message' => $message,
    'recipe' => $recipe,
    '_serialize' => ['message', 'recipe']
]);

ここで'_serialize'に変数名を配列として渡すとシリアライズされたjsonが表示できます。

HTMLデータをそのまま返したい

APIでHTMLデータをそのまま返したいということがあったのですが作法に従うとHTMLタグなどもシリアライズ化されたデータが出てきます。

これを無効化できないかと思って調べてみました。

_serialize()
src/View/JsonView.php

<?php
    // ・・・省略
				
    /**
     * Serialize view vars
     *
     * ### Special parameters
     * `_jsonOptions` You can set custom options for json_encode() this way,
     *   e.g. `JSON_HEX_TAG | JSON_HEX_APOS`.
     *
     * @param array|string|bool $serialize The name(s) of the view variable(s)
     *   that need(s) to be serialized. If true all available view variables.
     * @return string|false The serialized data, or boolean false if not serializable.
     */
    protected function _serialize($serialize)
    {
        $data = $this->_dataToSerialize($serialize);

        $jsonOptions = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT |
            JSON_PARTIAL_OUTPUT_ON_ERROR;

        if (isset($this->viewVars['_jsonOptions'])) {
            if ($this->viewVars['_jsonOptions'] === false) {
                $jsonOptions = 0;
            } else {
                $jsonOptions = $this->viewVars['_jsonOptions'];
            }
        }

        if (Configure::read('debug')) {
            $jsonOptions |= JSON_PRETTY_PRINT;
        }

        return json_encode($data, $jsonOptions);
    }
				
    // ・・・省略				

コメントにも書いてありますが _jsonOptions というオプションにオプションを渡すことでデフォルトのjson_encodeオプションを上書きできそうです。

json_encodeのための定義済み定数に列挙されているオプションが使えます。

$this->set([
    'message' => $message,
    'recipe' => $recipe,
				'_jsonOptions' => JSON_UNESCAPED_SLASHES,
    '_serialize' => ['message', 'recipe']
]);

最低限'_jsonOptions' => JSON_UNESCAPED_SLASHES, を設定することでHTMLタグがシリアライズ化されずに出力されるようになりました。あと他にも必要なオプションがあれば '|' 繋ぎで列挙すればOKです。

ただ、セキュリティ的にはまずそうな気もするので用法用量は守ってやったほうがいいかもですね。