laravel-value-objects について
Laravel 開発者向けに、ドメインの値オブジェクトの設計を解説するガイドです。DTOとの使い分けに加え、不変性、ファクトリーメソッド、検証、シリアライズを扱います。
主な機能
- 01値オブジェクトとDTOの用途の区別
- 02readonlyクラスによる不変性
- 03静的ファクトリーによる生成制御
- 04ドメインルールと検証のカプセル化
- 05シリアライズインターフェースの実装
活用シーン
- 01振る舞いを持つドメイン概念のモデル化
- 02金額などの不変値の表現
- 03サードパーティーライブラリのAPIラップ
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ leeovery/ agentic-skills --skill laravel-value-objectsインストール後、Claude Code で/laravel-value-objectsを入力して読み込まれたか確認します。
同梱ファイルreferences/Money.php
詳細
Laravel 値オブジェクト
値オブジェクトは、ドメインの概念を表すシンプルで不変なオブジェクトです。
関連ガイド:
- DTO - DTO はデータ転送に、値オブジェクトはドメインの概念に使用します
使用する場面
値オブジェクトを使用する場面:
- 振る舞いを持つ複雑なドメイン値がある
- 不変性が必要
- 詳細なバリデーションロジックがある
- 等価性の比較が必要
- ドメインルールをカプセル化する
DTO を使用する場面:
- レイヤー間でデータを転送する
- ドメインの振る舞いが不要
- DTOを参照
シンプルな値オブジェクト
<?php
declare(strict_types=1);
namespace App\Values;
use App\Enums\ProcessResult as ProcessResultEnum;
class ProcessResult
{
public function __construct(
public readonly ProcessResultEnum $result,
public readonly ?string $message = null,
) {}
public static function success(?string $message = null): self
{
return new self(ProcessResultEnum::Success, $message);
}
public static function skip(?string $message = null): self
{
return new self(ProcessResultEnum::Skip, $message);
}
public static function fail(?string $message = null): self
{
return new self(ProcessResultEnum::Fail, $message);
}
public function isSuccess(): bool
{
return $this->result === ProcessResultEnum::Success;
}
public function isFail(): bool
{
return $this->result === ProcessResultEnum::Fail;
}
}
金額の値オブジェクト
使用例
ProcessResult
// In actions
return ProcessResult::success('Order processed successfully');
return ProcessResult::skip('Order already processed');
return ProcessResult::fail('Payment declined');
// Checking results
if ($result->isSuccess()) {
// Handle success
}
if ($result->isFail()) {
// Handle failure
}
Money(Brick\Money ラッパー)
// Creating money values
$price = Money::of(29.99); // From major units (£29.99)
$shipping = Money::ofMinor(500); // From minor units (£5.00)
$usdPrice = Money::of(19.99, 'USD'); // Explicit currency
// Arithmetic (returns new instances)
$total = $price->plus($shipping);
$discounted = $total->minus(Money::of(5));
$refund = $total->negated();
// Comparison
$total->isZero();
$total->isGreaterThan($price);
$total->isEqualTo($other);
// Display
echo $total->format(); // "£34.99"
echo $refund->format(showNegativeInParentheses: true); // "(£34.99)"
// Storage (minor units as int)
$total->getMinorAmount(); // 3499
$total->getCurrencyCode(); // "GBP"
主なパターン
1. 不変性
final readonly classを使用します。すべてのプロパティが不変になり、クラスを拡張できません。
final readonly class Money implements JsonSerializable, Stringable
{
private function __construct(private BrickMoney $money) {}
}
2. プライベートコンストラクター+静的ファクトリー
生成方法を制御します。
private function __construct(/* ... */) {}
public static function of(BigNumber|int|float|string $amount, string $currency = 'GBP'): self
public static function ofMinor(BigNumber|int|float|string $minorAmount, string $currency = 'GBP'): self
public static function success(?string $message = null): self
3. ライブラリーのラップ
__call()による委譲を使い、サードパーティーライブラリーを独自の API の背後にラップします。
public function __call(string $name, array $arguments): mixed
{
// Delegate to wrapped library, wrapping results as needed
}
4. 新しいインスタンスを返す
操作は常に新しいインスタンスを返します(不変性)。
$discounted = $price->minus(Money::of(5)); // $price unchanged
$refund = $price->negated(); // $price unchanged
5. シリアライズ用インターフェースを実装する
値オブジェクトは通常、フレームワークの機能やストレージとの連携のために、JsonSerializable、Stringable、Wireable(Livewire)を実装します。
ディレクトリー構成
app/ValueObjects/
├── Money.php
├── ProcessResult.php
├── Coordinate.php
└── EmailAddress.php
まとめ
値オブジェクト:
- 不変です(
readonlyを使用します) - 静的ファクトリーメソッドを持ちます
- ドメインロジックをカプセル化します
- 操作の結果として新しいインスタンスを返します
- コンストラクターで検証します
単純なデータ転送ではなく、振る舞いを持つドメインの概念に使用します。
コメント
Markdown 対応