今更ながらPHPにおけるモデル(DTO)とはどうあるべきか?を考えてみた。
(php8.0前提)
私の結論
まずは結論から。上司に怒られっからね!
<?php
class Human {
const NAME="name"; // DBのkey名
const TEL="tel"; // DBのkey名
private string $name; // DB的にNOT NULLな項目
private ?int $tel; // DB的にNULL許容な項目
private function __construct(){}
/**
* DBレコードからインスタンスを生成するメソッド
**/
public static function fromRow(array $row) :Human {
$human = new Human();
foreach ($row as $key => $value) {
match($key) {
self::NAME => $human->name = $row[self::NAME],
self::TEL => $human->tel = $row[self::TEL],
};
}
// $rowがSELECT * FROM human;で取得している可能性がある
return $human;
}
public function getName() : string {
return $this->name;
}
public function getTel() : ?int {
return $this->tel;
}
}
// 実際には「SELECT * FROM Human;」で取得したレコードを配列として食わせるけど、サンプルね!
$human = Human::fromRow(array(Human::NAME => 'taclose', Human::TEL => null));
echo "名前は? ".$human->getName()."\n";
echo "電話番号は? ".$human->getTel()."\n";
echo "\n";
// sample2: SELECT name FROM Human; (連絡先一覧画面とか作りたい時かな)
$human = Human::fromRow(array(Human::NAME => 'taclose_2'));
echo "名前は? ".$human->getName()."\n";
echo "電話番号は? ".$human->getTel()."\n"; // 狙い通りFatal Errorが出る!!
解説・ポイント
__construct はprivateにする
データベースからレコードを持ってきてDTO生成するというパターンと、入力フォームから受け付けて生成するパターンが考えられるので複数のコンストラクタを実装出来ないPHPにとってはfromRowとかfromJsonとかそういうメソッド定義してあげて、constructはprivateにしちゃうのがかっこよさげ。
getterの型指定をしている
PHPって結構キャストや一度も代入されていない変数への参照に癖があり、verが上がる毎に見直しも入ったりと大変ですよね。
時代に沿ってgetterはmixed型ではなく、型指定を厳格に行う事でDTOを使う側の人間は安心して使えるようになり、余計なキャストやチェックで可読性を下げたり予期せぬ不具合を防止しています。
getterのdefault値は存在しない
SELECTされていない列の情報を参照しようとするとFatal Errorが発生するようにしています。
DB上でnullなのか空文字なのか0なのかわからないのに0だよ!とか返しちゃったら見つかりにくい不具合が増えるだけですからね!
あとはFatal Error発生しないでnull or 0 がデフォルト値で返るモードを用意してgetterで分岐っていう手もあるけど、これやりだすと毎回mode確認したりmode書き換えたりとかが出てきてDTOとしてはいかがなものかと思うわけでして。
以上!参考になるご意見もらえたら反映いたします!