本文目录导读:

- 使用 PHP 8.1+ readonly 属性(推荐)
- 使用 Laravel 的
ImmutableTrait - 基于 Laravel Collections 的不可变模式
- 使用 Spatie Data Transfer Object
- 结合 Laravel Eloquent 的不可变模型
- 使用 PHP 的 __set 魔术方法
- 最佳实践建议
在 Laravel 中创建不可变对象(Immutable Objects),有几种常见的方法:
使用 PHP 8.1+ readonly 属性(推荐)
<?php
namespace App\ValueObjects;
use Carbon\CarbonImmutable;
class UserProfile
{
public readonly string $name;
public readonly string $email;
public readonly CarbonImmutable $createdAt;
public function __construct(
string $name,
string $email,
?CarbonImmutable $createdAt = null
) {
$this->name = $name;
$this->email = $email;
$this->createdAt = $createdAt ?? CarbonImmutable::now();
}
// 通过 with 方法返回新实例来"修改"属性
public function withName(string $name): self
{
return new self($name, $this->email, $this->createdAt);
}
public function withEmail(string $email): self
{
return new self($this->name, $email, $this->createdAt);
}
}
使用 Laravel 的 Immutable Trait
<?php
namespace App\ValueObjects;
use Illuminate\Support\Traits\Conditionable;
use Illuminate\Support\Traits\Macroable;
use Illuminate\Support\Traits\Tappable;
class Configuration
{
use Conditionable, Macroable, Tappable;
private array $data;
public function __construct(array $data)
{
$this->data = $data;
}
public function get(string $key, mixed $default = null): mixed
{
return $this->data[$key] ?? $default;
}
// 返回新实例而非修改原对象
public function merge(array $data): self
{
return new self(array_merge($this->data, $data));
}
public function toArray(): array
{
return $this->data;
}
}
基于 Laravel Collections 的不可变模式
<?php
namespace App\Support;
use Illuminate\Support\Collection;
class ImmutableCollection
{
private Collection $items;
public function __construct(array $items = [])
{
$this->items = collect($items);
}
public function add(mixed $item): self
{
return new self($this->items->push($item)->all());
}
public function remove(int $index): self
{
return new self(
$this->items->reject(fn($value, $key) => $key === $index)->all()
);
}
public function map(callable $callback): self
{
return new self($this->items->map($callback)->all());
}
public function all(): array
{
return $this->items->all();
}
}
使用 Spatie Data Transfer Object
<?php
namespace App\DataTransferObjects;
use Spatie\DataTransferObject\DataTransferObject;
class OrderDTO extends DataTransferObject
{
public string $orderNumber;
public float $total;
public array $items;
public ?string $customerNote;
// 所有属性都是只读的
protected bool $immutable = true;
}
// 使用示例
$order = new OrderDTO([
'orderNumber' => 'ORD-001',
'total' => 150.00,
'items' => ['Product A', 'Product B'],
]);
// 获取副本
$modifiedOrder = $order->with('total', 200.00);
结合 Laravel Eloquent 的不可变模型
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
class Product extends Model
{
// 禁用批量赋值修改
protected $guarded = ['*'];
// 返回不可变的 Carbon 实例
protected function serializeDate(\DateTimeInterface $date): string
{
return Carbon::instance($date)->toImmutable()->toISOString();
}
// 通过只读访问器返回副本
public function getPriceWithTaxAttribute(): \Money\Money
{
return $this->price->multiply('1.2');
}
// 修改时返回新实例
public function replicateWith(array $attributes = []): self
{
return tap($this->replicate(), function ($instance) use ($attributes) {
foreach ($attributes as $key => $value) {
$instance->$key = $value;
}
});
}
}
使用 PHP 的 __set 魔术方法
<?php
namespace App\ValueObjects;
class Address
{
private string $street;
private string $city;
private string $country;
public function __construct(string $street, string $city, string $country)
{
$this->street = $street;
$this->city = $city;
$this->country = $country;
}
// 禁止动态修改属性
public function __set(string $name, mixed $value): void
{
throw new \LogicException("Cannot modify immutable object's property: {$name}");
}
// 允许通过方法创建修改后的副本
public function withStreet(string $street): self
{
return new self($street, $this->city, $this->country);
}
public function toArray(): array
{
return [
'street' => $this->street,
'city' => $this->city,
'country' => $this->country,
];
}
}
最佳实践建议
- 优先使用 PHP 8.1+ readonly 属性 - 最简洁且性能最好
- *使用 `with` 方法模式** - 返回新实例而不是修改原对象
- 配合 CarbonImmutable - 处理日期时间时使用不可变版本
- 避免继承 - 不可变类最好保持 final
- 使用显式的 getter 方法 - 而不是直接访问属性
// 完整示例
final readonly class Money
{
public function __construct(
private int $amount,
private string $currency = 'USD'
) {}
public function getAmount(): int
{
return $this->amount;
}
public function getCurrency(): string
{
return $this->currency;
}
public function add(Money $money): self
{
if ($this->currency !== $money->currency) {
throw new \InvalidArgumentException('Currency mismatch');
}
return new self($this->amount + $money->amount, $this->currency);
}
public function multiply(float $multiplier): self
{
return new self((int) ($this->amount * $multiplier), $this->currency);
}
}
通过这些方法,你可以创建真正不可变的对象,提高代码的可预测性和安全性。