[CakePHP3] JSONをDBテーブルへ格納時にbeforeMarshal()を活用してみた

2020/03/08

author

masyus

英語のmarshalは

  • 《軍事》〔部隊を〕整列[集結]させる
  • 〔考えや事実などを〕整理する、まとめる

という意味で使われます。CakePHP3のbeforeMarshal()は2.の解釈で、

リクエストデータをpatchEntity()等でEntity化する直前に、リクエストデータの中身を加工できる

という目的でTableクラスもしくはBehaviorクラスで使うことが可能です。今回紹介する内容ではJSONを取り扱いたい時beforeMarshal()を活用した事例を書きます。検証したCakePHPのバージョンは3.6.13、PHPは7.3、MySQLは5.7です。

CakePHP3のbeforeMarshal()の使いどころ

ブログCMSを0ベースで構築時、ブログ記事自体にexperimentalな特殊設定を加えたい要件がありました。そのためMySQLで記事テーブルとは別に記事特殊設定テーブルを作り、そこに特殊設定内容を保存しておくことになりました。experimentalなため特殊設定の内容自体が頻繁に追加/変更/削除がある状況でしたため、JSON型のデータを記事特殊設定テーブルへ格納する方法を取るとします。

テーブルにJSONを格納する際、

データ量低減の観点で、可能なら改行コードや半角スペースによるインデントを取り除いた上でinsertしたい

ニーズがあります。その上で、

ブラウザにJSONを表示/追加/編集する時は、予め改行やインデントで整形させた上で表示したい

と考えました。

整形表示はjson_encode()で対応できますが、「JSON保存時はどのタイミングで改行コードやインデントを取るべきか?」と考えた結果、beforeMarshal()が良さそうでした。後々入力値がJSON規格になっているかをValidationでチェックしたいため、Validationの前に実行されるbeforeMarshal()のタイミングは最適でした。

beforeMarshal()のサンプルコード

前述の仕様を満たすコードは下記のようになりました。

<?php
namespace App\Model\Table;

use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use Cake\Event\Event;
use ArrayObject;

class ArticleExtraSettingsTable extends Table
{

// ...省略

    public function beforeMarshal(Event $event, ArrayObject $data, ArrayObject $options)
    {
        // JSONをminify
        if (isset($data['json_setting'])) {
            $data['json_setting'] = json_decode($data['json_setting']);
            $data['json_setting'] = json_encode($data['json_setting'], JSON_UNESCAPED_UNICODE);
        }
    }
}

一度json_decode()でstdClass化したデータを再度json_encode()することで、JSONをminifyした状態にすることができます。ちなみにminifyされたJSONを整形させる場合は

json_encode($json, JSON_PRETTY_PRINT);

のように、json_encode()のオプションにJSON_PRETTY_PRINTを指定することで可能です。

参考