章: 第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は「意味のある値そのもの」です。
Moneyのように「単なる文字列/数値では表現しきれない値」には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を活用することで不変性を簡潔に表現できる
次回はイミュータブルオブジェクトを学びます。「一度作ったら変更できない」設計が、なぜバグを減らし並行処理を安全にするかを深掘りします。