关于 laravel-value-objects
为 Laravel 开发者提供领域值对象的设计指南,说明何时使用值对象而非 DTO,并介绍不可变性、工厂方法、校验和序列化模式。
主要功能
- 01区分值对象与 DTO 的用途
- 02使用只读类实现不可变性
- 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;
}
}
Money 值对象
使用示例
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