英語のmarshalは
- 《軍事》〔部隊を〕整列[集結]させる
- 〔考えや事実などを〕整理する、まとめる
という意味で使われる。CakePHP3のbeforeMarshal()は2.の解釈で、
「リクエストデータをEntity化させる前にデータをごにょごにょする」
という目的で、TableもしくはBehaviorクラスで使うことが可能だ。今回はそんなbeforeMarshal()を、jsonデータを取り扱いたい時に活用した事例を書く。検証したCakePHPのバージョンは3.6.13。
MySQL5.7以降では、SQLでjsonデータを取り扱える
MySQL5.7以降ではcolumnにjsonデータを格納すると、json内のキーを対象にSQLで検索できるようになった。
https://dev.mysql.com/doc/refman/5.7/en/json.html
テーブルの正規化とか考えたらjsonデータをDBに保存させるのはイマイチな感はあるが、まだ正規化できる余地のない未知なデータをどうしてもDBに持たせたいときには有効な選択肢。
僕の実例では、とあるテーブルに様々な特殊設定を加えたい要件があり、しかも設定自体が随時追加/変更/削除が頻繁にあるようなものだったため、jsonデータを格納する方法を使ってみている。
CakePHP3のbeforeMarshal()の使いどころ
beforeMarshal()の挙動は具体的にいうと、
リクエストデータをpatchEntity()等でEntity化する直前に、リクエストデータの中身を加工できる
というもの。CakePHP2でいうところのbeforeValidate()に近い動きだ。
MySQLのテーブルにjsonデータを格納する際、可能なら改行コードや半角スペースによるインデントを含まずに格納しておきたいニーズがある。だが、画面からjsonデータを追加/編集したい時には整形させた上で表示したいのが心情。
整形表示はjson_encode()等でやれるが、保存時はどのタイミングで改行コードやインデントを取るべきかと考えた時、beforeMarshal()が良さそうだと考えついた。json規格になっているかをvalidationでチェックしたいニーズを汲むと、beforeMarshal()のタイミングが適切かなと。具体的にはこんな感じ:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<?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された1行のjsonデータを整形させるには
1 |
json_encode($json, JSON_PRETTY_PRINT); |
という書き方で、json_encode()のオプションにJSON_PRETTY_PRINTを指定することで可能。