mcpField
L
laravel-value-objects

laravel-value-objects

github.com/leeovery开发1548 次安装2 周前更新
代码仓库

关于 laravel-value-objects

为 Laravel 开发者提供领域值对象的设计指南,说明何时使用值对象而非 DTO,并介绍不可变性、工厂方法、校验和序列化模式。

主要功能

  1. 01区分值对象与 DTO 的用途
  2. 02使用只读类实现不可变性
  3. 03通过静态工厂控制创建
  4. 04封装领域规则与校验
  5. 05实现序列化接口

适用场景

  1. 01建模带行为的领域概念
  2. 02实现金额等不可变值
  3. 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"

→ 完整实现:Money.php

关键模式

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