跳到主要内容
Vabora 开发极客
首页 归档 分类 标签 案例 服务 关于 聊聊需求

PHP 8.4+ 属性钩子与只读类:把校验收进模型

属性钩子(Property Hooks)让"取值/赋值时做点事"不再依赖魔术方法或 setter 样板。这篇讲它解决什么问题、与 __get/__set 的区别,以及只读类与 clone 的配合。

PHP 8.4+ 属性钩子与只读类:把校验收进模型 封面

#背景

业务模型里最常见的样板代码是访问器:

php
final class Money
{
    private int $amount = 0;

    public function getAmount(): int { return $this->amount; }
    public function setAmount(int $amount): void
    {
        if ($amount < 0) {
            throw new InvalidArgumentException('金额不能为负');
        }
        $this->amount = $amount;
    }
}

一个字段配一对方法,模板代码比业务逻辑还长。属性钩子把这两件事合回属性本身。

#做法

#1. 校验直接写在属性上

php
final class Money
{
    public int $amount = 0 {
        set (int $value) {
            if ($value < 0) {
                throw new InvalidArgumentException('金额不能为负');
            }
            $this->amount = $value;
        }
    }
}

调用侧仍然是 $money->amount = 100;,但非法值进不来。好处不只是少写代码:校验跟着字段走,新增字段时不会忘了加校验。

#2. 派生字段惰性计算

php
final class Post
{
    public string $content_md = '';

    public string $excerpt {
        get => mb_substr(trim(strip_tags($this->content_md)), 0, 80);
    }
}

$post->excerpt 是只读派生值——不需要在每次保存时同步冗余字段,也不会有"改了正文忘了改摘要"的不一致。

#3. 与只读类配合

readonly 属性不能有 set 钩子(只能在构造器里赋值),但可以有 get 钩子做格式化。实践组合是:

  • 值对象(Money、Email、Slug)用 readonly + 构造期校验;
  • 可编辑模型(Post、Setting)用属性钩子做赋值校验。

#4. 与 get/set 的本质区别

魔术方法是在访问不存在的属性时兜底,它破坏了静态分析,IDE 也看不懂;属性钩子是真实属性,类型、补全、readonly、序列化都照常工作。这是它值得迁移的核心理由。

#结果

  • 模型层样板代码明显减少,校验从"分散在各处 setter"变成"贴着字段";
  • 静态分析(IDE 与 php -l)仍能理解属性类型,重构改字段名不会漏。

#取舍

  • 不把复杂逻辑塞进钩子:钩子里只做校验与轻量派生。需要查库、发事件的逻辑仍然放在服务层——钩子会隐式触发,塞重活会让调用链难以追踪。
  • get 钩子里不要改状态:读操作有副作用是最难排查的一类 bug,惰性计算要保证幂等。
  • 对外 API 仍用显式方法:跨模块的契约(如 PostService::save())保持方法调用,属性钩子只在模型内部使用——这样以后换实现不必改调用方。
最后更新: 本文采用 CC BY-NC-SA 4.0 许可,转载请注明出处与作者。
related

相关文章

评论

还没有评论,来说点什么吧。

发表评论

评论需站长审核后展示 · 最多 1000 字