第09回: DTOとValue Object — データに「意味と安全性」を持たせる

章: 第1章: オブジェクト指向の深化

array を引数に渡し続けていませんか?

function createUser(array $data) — この引数の $data に何が入っているか、型チェックなしに分かりますか?

配列でデータを受け渡すと「どのキーが必要か」「値の型は何か」がコードを見ただけでは分かりません。プロパティ名のタイポや型の不整合が実行時まで発覚しません。

DTOとValue Objectはデータに「型と意味」を与えることで、こうした問題を設計段階で防ぎます。

DTOの実装


<?php
// DTO(Data Transfer Object): データの受け渡しを整理する
readonly class CreateUserInput {
    public function __construct(
        public string $name,
        public string $email,
    ) {}
}

// 使い方: 配列ではなくDTOを渡す
function createUser(CreateUserInput $input): void {
    // $input->name, $input->email など型保証された形でアクセス
}

$input = new CreateUserInput(name: '田中', email: 'tanaka@example.com');
createUser($input);

Value Objectの実装


<?php
// Value Object(値オブジェクト): 値の一貫性を保証する
readonly class Email {
    public string $value;

    public function __construct(string $email) {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException("Invalid email: {$email}");
        }
        $this->value = $email;
    }
}

// メールアドレスは必ずバリデーション済みの状態になる
$email = new Email('tanaka@example.com'); // OKまたは例外

配列 vs DTO vs Value Object

観点 配列 DTO Value Object
型安全性 なし あり(プロパティ型) あり(+バリデーション付き)
自己文書化 コードを読んで推測 プロパティ名が明確 クラス名が意図を表す
バリデーション 別途実装が必要 別途実装が必要 コンストラクタで保証
不変性 なし readonlyで保証 readonlyで保証

チェックポイント: DTOは「データの入れ物」、Value Objectは「意味のある値そのもの」です。EmailMoney のように「単なる文字列/数値では表現しきれない値」にはValue Objectを使いましょう。

組み合わせて使う


<?php
readonly class CreateUserInput {
    public function __construct(
        public string $name,
        public Email $email,  // Value Objectで型安全に
    ) {}
}

チェックポイント: readonly キーワード(PHP 8.1+)はDTOとValue Objectの不変性を宣言的に表現する最良の方法です。

まとめ & 次のステップ

  • DTOは複数のデータをひとまとめにして型安全な受け渡しを実現する
  • Value Objectは値にバリデーションと意味を持たせ、不正な値の生成を防ぐ
  • readonly class を活用することで不変性を簡潔に表現できる

次回はイミュータブルオブジェクトを学びます。「一度作ったら変更できない」設計が、なぜバグを減らし並行処理を安全にするかを深掘りします。

Related Articles