がるの健忘録

エンジニアでゲーマーで講師で占い師なおいちゃんのブログです。

「validationをValue Objectで行う」を考えてみた

なんか「ふと思いついた」程度のお話ではあるのですが。

まず前提として「Value Objectとは、一意性がなく交換可能なもの」としておきます。おいちゃんの好み的に「イミュータブル」であって欲しいと思ってますが、その辺はまぁ余談。
あと、ちょろっと出てくる「Entity」については「一意性がある、データの塊」くらいに把握しておいてもらえればとりあえず会話は成り立つかなぁ、と思ってます。

超大雑把に、ですが。
例えば「User」っていうentityがあって、このUser entityの中には「user_idというValue Objectと姓名というValue ObjectとemailというValue Objectと誕生日というValue Object」があって、みたいな感じをイメージしていただけるとよろしかろうか、と思います。

このとき。https://gallu.hatenadiary.jp/entry/2019/05/16/225759 で書きましたが、少なくとも「Controller的な所で、入ってくる時にvalidateする」のをおいちゃんはあまり好まないので。
上述の場合はentityにvalidateを実装していた、のですが。
ふと「あれ? 各Value Objectでvalidateしてもいいんぢゃね?」とか思ったんですね。

emailは、それが「userの中にあろうとも」「ほかのナニカの中にあろうとも」基本的には「email用のvalidate」をしたいだろうし。
誕生日も同様だろうし、姓名も同様だと思うんですね。
だとすると「各validate処理自体は各Value Objectでやって、entityはそれをとりまとめて依頼して結果を拾い集めるだけ」ってやると、なんか割とうまくいくんぢゃね? とか思ったですます。

超大雑把にコードっぽいものを書いてみると。
以前は、entityに

$validate = [
    'user_id' => 'required|int', // 文字列で指定する書き方
    'email' => ['required', 'email'], // 配列で指定する書き方
    'birthday' => ['date'],
];

とかって書いてたイメージなのですが。
これを、まずentityのほうでは

$type_cast = [
    'user_id' => ValueObject\UserId::class,
    'email' => ValueObject\Email::class,
    'birthday' => ValueObject\Birthday::class,
];

って感じで「データが入ってくる時にこの型(クラス)にキャストするよ」って宣言しておいて、あとは各Value Object側で適宜validateしておくれやす、的な。
Value Objectのvaludateは多分「コンストラクタで値を取り込んだ時にvalidate、駄目なら例外投げるのをentityが個々にキャッチ」って感じじゃないかなぁ???

まぁ「完全にValue Objectのみに実装」だと、例えば「BBSとかのentityで"投稿されたメッセージ"までValue Object作るん? 面倒くね?」とか「こっちのAというValue Objectがこの値の時はBというValue Objectがこうなってて欲しい、的に相互が関連するもの」もあるので、その辺はまぁ「entityへの実装も許容する」ほうが現実問題として楽だろう、と思うのですが。

そすると、Enity的には、超雑ですがこんなイメージかなぁ、と(カラムとか適当に追加してます)。

// 各カラムのキャスト型の指定
$type_cast = [
    'user_id' => ValueObject\UserId::class,
    'email' => ValueObject\Email::class,
    'birthday' => ValueObject\Birthday::class,
    'memo' => 'string', // これは単純に「文字列型にする」くらいのイメージの指定(スカラ型はこんな感じでいいかなぁ、と思ってる)
];
// (ValueObjectでは行われない追加の)valudate指定
$validate = [
    'hoge' => ValidateRule\HogeRule::class, // validateルールをクラスで指定しておく(ちょっと複雑な子もこれで安心)
    'memo' => ['required', 'min_length:10', 'max_length:100'], // 従来のvalidateもここでちゃんと書ける
];
// 「1カラムでは片付かない」valudateの追加実装
protected function validate(): bool
{
    // 元々のvalidateの実装をcall
    $r = parent::validate();

    // 追加のvalidate
    $r2 = {AがXXの時はBがYYであること、的な実装が書いてあるとおもいねぇ};

    // 親のvalidateと追加のvalidateをがっちゃんこしてreturn
    return $r && $r2;
}

(おいちゃんが作ってるSlimLittleToolsだと、validation、厳密には「insertのみで適用する用」と「updateのみで適用する用」も書けるので、もうちょっとだけ複雑にはなりますが、大枠はこんなもんかなぁ)

そうすると、 https://twitter.com/mpyw/status/1619951659047858181 に書かれているような感じの「validateのルールをクラス名で渡す」も一緒にあわせて実装すると、まぁ少し「方法が多様になる」にしても、気をつければ割ときれいだったり楽だったりする実装になるんじゃないかなぁ? と。
(多分、ValidateRuleとかって名前とValueObjectValidationとかって名前のインタフェースとか切るんだろうなぁ、と、もわもわ妄想中)

なんかちょっと突飛だけどどうかなぁ?
……と思ったら、思ったよりあちこちで考察されてましたよ。ですよね~w

https://qiita.com/kotauchisunsun/items/e319e4c4b093d6add74b#valueobject%E5%9E%8B
https://qiita.com/j5ik2o/items/bd77f2da6d445ee4268a

なんか「ふと思いついた」だけなので、どこかで検証コードというかアプリケーションざっくり実装してみたいなぁ。
「検証用に使うための汎用の仕様」とか作って、なんか色々な方法で実装してみようかしらん? とか、思ってみたりもしたりはする。

いったん今回は「脳内妄想垂れ流し」なんで、コード書いたらまた追記します ノ