PHP 值对象哈希与相等实现指南
基础值对象实现
class Money {
private string $currency;
private int $amount; // 以最小单位存储
public function __construct(string $currency, float $amount) {
if ($amount < 0) {
throw new InvalidArgumentException("Amount cannot be negative");
}
$this->currency = strtoupper($currency);
$this->amount = (int) round($amount * 100); // 转换为分
}
// 获取值
public function getCurrency(): string { return $this->currency; }
public function getAmount(): float { return $this->amount / 100; }
// 值对象比较
public function equals(mixed $other): bool {
if (!$other instanceof self) return false;
return $this->currency === $other->currency
&& $this->amount === $other->amount;
}
// PHP 8.1+ __serialize
public function __serialize(): array {
return [
'currency' => $this->currency,
'amount' => $this->amount
];
}
}
实现 __hash 方法(PHP 8.0+)
class UserId implements Stringable {
private string $id;
public function __construct(string $id) {
if (!preg_match('/^[a-f0-9]{24}$/', $id)) {
throw new InvalidArgumentException("Invalid UserId format");
}
$this->id = $id;
}
// 哈希实现
public function __hash(): string {
return sha1($this->id);
}
// 相等性判断
public function equals(mixed $other): bool {
if (!$other instanceof self) return false;
return $this->id === $other->id;
}
// 用于 array_key 场景
public function __toString(): string {
return $this->id;
}
// 用于排序
public function compareTo(mixed $other): int {
if (!$other instanceof self) {
throw new InvalidArgumentException("Cannot compare with different type");
}
return strcmp($this->id, $other->id);
}
}
组合值对象
readonly class Address {
public function __construct(
public string $street,
public string $city,
public string $postalCode,
public string $country
) {
// 验证
if (empty($street) || empty($city)) {
throw new InvalidArgumentException("Required fields missing");
}
}
// 基于所有属性生成哈希
public function __hash(): string {
return crc32(implode('|', [
$this->street,
$this->city,
$this->postalCode,
$this->country
]));
}
// 完整比较
public function equals(mixed $other): bool {
if (!$other instanceof self) return false;
return $this->street === $other->street
&& $this->city === $other->city
&& $this->postalCode === $other->postalCode
&& $this->country === $other->country;
}
}
使用 SplObjectStorage 的案例
class OrderLine {
private string $productId;
private int $quantity;
private Money $unitPrice;
public function __construct(string $productId, int $quantity, Money $unitPrice) {
$this->productId = $productId;
$this->quantity = $quantity;
$this->unitPrice = $unitPrice;
}
// 用于 SplObjectStorage
public function __hash(): string {
return md5(serialize([
'product' => $this->productId,
'price' => $this->unitPrice->__serialize()
]));
}
// 业务逻辑相等性
public function equals(mixed $other): bool {
if (!$other instanceof self) return false;
// 忽略数量,只比较产品和价格
return $this->productId === $other->productId
&& $this->unitPrice->equals($other->unitPrice);
}
// 用于集合操作
public static function sum(array $lines): self {
if (empty($lines)) {
throw new InvalidArgumentException("Empty lines collection");
}
$first = $lines[0];
$totalQuantity = 0;
$totalAmount = 0;
foreach ($lines as $line) {
if (!$line->equals($first)) {
throw new InvalidArgumentException("Cannot sum different products");
}
$totalQuantity += $line->quantity;
}
return new self(
$first->productId,
$totalQuantity,
$first->unitPrice
);
}
}
// 使用示例
$line1 = new OrderLine('PROD-001', 2, new Money('USD', 10.50));
$line2 = new OrderLine('PROD-001', 3, new Money('USD', 10.50));
$merged = OrderLine::sum([$line1, $line2]);
echo $merged->getQuantity(); // 5
在集合中使用值对象
class ValueObjectCollection implements IteratorAggregate {
private array $items = [];
private SplObjectStorage $hashIndex;
public function __construct() {
$this->hashIndex = new SplObjectStorage();
}
// 添加值对象(去重)
public function add(object $valueObject): void {
if (!$this->hashIndex->contains($valueObject)) {
$this->items[] = $valueObject;
$this->hashIndex->attach($valueObject);
}
}
// 检查是否存在
public function contains(object $valueObject): bool {
return $this->hashIndex->contains($valueObject);
}
// 获取以哈希为键的映射
public function toHashMap(): array {
$map = [];
foreach ($this->items as $item) {
if (method_exists($item, '__hash')) {
$map[$item->__hash()] = $item;
}
}
return $map;
}
// 批量相等性比较
public function sameAs(array $others): bool {
if (count($this->items) !== count($others)) return false;
foreach ($others as $other) {
$found = false;
foreach ($this->items as $existing) {
if ($existing->equals($other)) {
$found = true;
break;
}
}
if (!$found) return false;
}
return true;
}
public function getIterator(): ArrayIterator {
return new ArrayIterator($this->items);
}
}
// 使用示例
$vatRates = new ValueObjectCollection();
$vatRates->add(new class('Standard', 20.0) {
public function __construct(
public string $name,
public float $rate
) {}
public function __hash(): string {
return md5($this->name . '|' . $this->rate);
}
public function equals(mixed $other): bool {
return $other instanceof self
&& $this->name === $other->name
&& $this->rate === $other->rate;
}
});
使用 PHP 8.1 readonly 属性
readonly class Color {
public function __construct(
public int $red,
public int $green,
public int $blue
) {
// 自动验证范围
foreach (['red', 'green', 'blue'] as $color) {
if ($this->$color < 0 || $this->$color > 255) {
throw new InvalidArgumentException("Color values must be 0-255");
}
}
}
// 基于 RGB 值生成唯一哈希
public function __hash(): string {
return sprintf('%02x%02x%02x', $this->red, $this->green, $this->blue);
}
// 比较
public function equals(Color $other): bool {
return $this->red === $other->red
&& $this->green === $other->green
&& $this->blue === $other->blue;
}
// 转换为十六进制
public function toHex(): string {
return '#' . $this->__hash();
}
}
性能优化建议
class OptimizedValueObject {
private ?string $cachedHash = null;
// 惰性计算哈希
public function __hash(): string {
if ($this->cachedHash === null) {
$this->cachedHash = $this->calculateHash();
}
return $this->cachedHash;
}
private function calculateHash(): string {
// 使用更快的哈希算法
return md5(serialize([
// 按顺序排列属性
$this->getImportantProperty(),
$this->getOtherProperty()
]));
}
// 批量比较优化
public static function batchEquals(array $items, array $targets): array {
$targetMap = [];
foreach ($targets as $target) {
$targetMap[$target->__hash()] = $target;
}
$result = [];
foreach ($items as $key => $item) {
$hash = $item->__hash();
$result[$key] = isset($targetMap[$hash])
&& $item->equals($targetMap[$hash]);
}
return $result;
}
}
最佳实践总结
- 始终实现 equals 方法 - 比较对象结构而非引用
- 提供 __hash 方法 - 支持集合操作和性能优化
- 保持不可变性 - 值对象创建后不应改变
- 实现 __toString - 便于调试和日志
- 使用 readonly 属性 - PHP 8.1+ 自动不可变
- 缓存哈希值 - 计算昂贵的哈希可以缓存
- 权衡精度和性能 - crc32 快但可能碰撞,sha1 更安全
这些模式确保值对象在 PHP 中正确实现哈希和相等性,支持集合操作、映射和性能优化。

版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播