はじめに
- これはバッドノウハウです
- 諸々の事情で**どうしてもこれしか方法がない!**という場合の最終手段です
- CakePHP3.x以降では仮想プロパティに置き換えられたため、この方法は使えません
※参考 afterFind イベントやバーチャルフィールドはありません
TL;DR
- virtualFieldSeparatorが含まれるテーブルカラム名があると結果セットが想定通りにならない
- virtualFieldSeparatorを変更することで想定通りの結果セットを得られる
- 正しい対応方法はテーブルカラム名を修正できるならする、カラム参照しているコードも修正する(なのでバッドノウハウ)
結論
$db = $this->getDataSource();
$db->virtualFieldSeparator = "___";
$res = $db->query("
SELECT hoge_table.id, hoge_table.item__1, fuga_table.item_2
FROM hoge_table
LEFT JOIN fuga_table ON hoge_table.id = fuga_table.id
WHERE hoge_table.id = 100"
);
var_dump($res);
/*
[0] => Array
(
[hoge_table] => Array
(
[id] => 100
[item__1] => hoge
)
[fuga_table] => Array
(
[item_2] => fuga
)
)
*/
virtualFieldSeparatorとは
- CakePHP2.xに実装されたvirtual fieldという機能で使用する
- SELECT句で別名定義したカラムを結果セットの配列で任意のテーブルへ含めるための命名ルール
テーブル名__カラム名
の__
のことです
例えば、以下のクエリではfieldsにSQL関数を書いた場合、モデル名の下にSQL関数の結果が入ります
$res = $this->query("SELECT id, CONCAT(item1, item2) as item FROM hoge_table WHERE id = 100")
var_dump($res);
/*
[0] => Array
(
[hoge_table] => Array
(
[id] => 100
)
[0] => Array
(
[item] => hoge
)
)
*/
virtual fieldの命名ルールを使うとhoge_table
以下にまとまります
$res = $this->query("
SELECT id, CONCAT(item1, item2) as hoge_table__item
FROM hoge_table
WHERE id = 100"
)
var_dump($res);
/*
[0] => Array
(
[hoge_table] => Array
(
[id] => 100
[item] => hogefuga
)
)
*/
以下のようなカラム名の場合、意図せずvirtual fieldの命名ルールに該当してしまい、想定と異なる結果セットとなってしまいます
$res = $this->query("
SELECT id, item__1
FROM hoge_table
WHERE id = 100"
)
var_dump($res);
/*
[0] => Array
(
[hoge_table] => Array
(
[id] => 100
)
[0] => Array
(
[item__1] => hoge
)
)
*/
カラム名が変更できればitem__1
をitem_1
などに変更するのが一番いい対応方法になります。
とはいえ、稼働中のソフトウェアであればDBのカラム変更は影響範囲も大きく、場合によっては稼働停止してメンテナンスする必要があるかもしれないため、場合によってはカラム変更が現実的にできない場合もあるでしょう。
次善策としてはカラムに別名をつける、が挙げられそうです
$res = $this->query("
SELECT id, item__1 as item_1
FROM hoge_table
WHERE id = 100"
)
var_dump($res);
/*
[0] => Array
(
[hoge_table] => Array
(
[id] => 100
[item__1] => hoge
)
)
*/
別名で回避できるならそうしたいところですが、対象カラムが多いと参照箇所も比例して多くなり、コードの修正やテストに時間がかかることが予想されます。
このような事象が問題と認識されずに開発が進み、リリース間近になってレビューで指摘されて修正時間がない、というような状況で特定の機能だけは想定通りの結果セットでないと設計上問題がある、のような特殊な状況においてはvirtualFieldSeparator自体を一時的に変更することで他機能への影響なしで対応することができます。
本記事では""(デフォルト値、アンダースコア2個)から"_"(アンダースコア3個)に変更することでvirtual field命名ルールを変更しています。
virtualFieldSeparatorの変更はこのセッションでのDB Connection closeまで有効となり、他セッションでには影響しません。
$db = $this->getDataSource();
$db->virtualFieldSeparator = "___";
このような反則的な対応をしないようにDB設計やソースレビューを細分化するなどで早期発見して本来あるべき対応方法になるようにすることが大切だと骨身に染みました。