PHP Trait 为何可能成为语言的关键特性

PHP Trait 为何可能成为语言的关键特性

PHP 社区对 Trait 一向谈不上偏爱。它们常被视为隐式行为的来源。既然如此,Trait 为何依然值得重视?

Trait 大概是 PHP 中唯一可以被归入元编程工具集的特性。它允许在程序真正开始使用某个类之前改变该类的结构,实际上就是把方法、属性和常量注入目标类。

当然,Lisp 这类语言,以及 Elixir 这类更现代的语言,都提供了远比 PHP 强大的元编程设施。

但 PHP 的 Trait 具备一个许多同类机制所缺乏的特性:极低的认知负担。

它的模型非常简单:

php 复制代码
trait Timestamped
{
    public DateTimeImmutable $createdAt;

    public function touch(): void
    {
        $this->createdAt = new DateTimeImmutable();
    }
}

class User
{
    use Timestamped;
}

完成组合之后,在阅读 User 类时,几乎可以把它当作是这样写成的:

php 复制代码
class User
{
    public DateTimeImmutable $createdAt;

    public function touch(): void
    {
        $this->createdAt = new DateTimeImmutable();
    }
}

除少数冲突解决规则之外,类与 Trait 的组合就只是类中定义的一切与 Trait 中定义的一切的简单叠加。

这一规则易于记忆,也相对容易推理。

元编程之所以从未成为大多数程序员的日常工具,原因之一在于:很难理解一个元程序究竟如何改造程序本身。

Trait 在这方面的问题要小得多。它的变换受到约束、范围局限,而且相对直观。

这正是 Trait 的价值所在。

Trait 所缺失的能力

那么,Trait 为何没能在 PHP 中成为更强大的工具?

一个重要原因在于,Trait 与其目标类之间缺乏足够强的组合契约。

目前,Trait 可以通过抽象方法部分地表达自身的要求:

php 复制代码
trait Serializable
{
    abstract protected function data(): array;

    public function serialize(): string
    {
        return json_encode($this->data(), JSON_THROW_ON_ERROR);
    }
}

这已经建立了一种契约:目标类必须以某种方式提供 data()。

但是,当 Trait 依赖的契约规模更大时,这种机制很快就会变得笨重。如果 Trait 期望目标类实现整个接口,它就必须逐一重复接口中的方法:

php 复制代码
trait IteratorConsumer
{
    abstract public function current(): mixed;
    abstract public function next(): void;
    abstract public function key(): mixed;
    abstract public function valid(): bool;
    abstract public function rewind(): void;

    // ...
}

更自然的写法大致如下:

php 复制代码
trait IteratorConsumer require Iterator
{
    // Inside the trait we now know:
    // $this instanceof Iterator
}

而且这并非纯属假想的语法。

PHP 早期的一份 RFC《Horizontal Reuse for PHP》就提出了基本相同的设想:Trait 可以要求其目标类满足某个特定接口。

另一份 RFC《Traits with Interfaces》则提出了这一契约的另一面:

php 复制代码
interface Logger
{
    public function error(string $message): void;
    public function info(string $message): void;
}

trait FileLogger implements Logger
{
    public function error(string $message): void
    {
        // ...
    }

    public function info(string $message): void
    {
        // ...
    }
}

这样一来,Trait 就可以作出保证:

只要将它组合进某个类,它便为该接口提供实现。

这份 RFC 走得更远:使用此类 Trait 的类将自动成为该接口的实现。

由此便得到组合契约两个对称的侧面。

trait T require A 意味着:它只能被组合进已经满足 A 的目标。

而 trait T implements B 则意味着:通过组合它,目标类获得了 B。

几乎可以把 Trait 描述为一种类型变换 T : A → A & B。Trait 接受一个满足某种契约的类,并产出一个满足扩展契约的类。

不过还可以再往前推进一步。

契约应当是双向的

Trait 应当能够声明:一个类在被它组合之前必须满足哪些要求。

与此同时,类也应当能够定义自己愿意接受哪些变换。

目前:

php 复制代码
class User
{
    use SomeTrait;
}

这实际上等于无条件允许 SomeTrait 把它的成员引入 User。

在双向契约之下,组合本身会成为一种经过检查的操作:

复制代码
Trait requirements
        ↓
      Class
        ↓
Class permissions
        ↓
    composition

此时,Trait 便不再仅仅是水平代码复用的机制,而成为一种受到约束、可以预期的编译期类变换机制。

这恰恰是 Trait 潜力远未被充分释放的地方。

它能够为 PHP 带来元编程的部分表达能力,同时又不必引入通用宏系统那样的整体复杂度。

PHP Trait 为何可能成为语言的关键特性

相关推荐
卷无止境21 分钟前
独立开发者的"富矿地带":哪些垂直领域值得你押注一辈子?
后端·python
狼爷1 小时前
从零用 Java 构建 AI Agent 框架:JavaManus 设计与实现深度解析
后端·langchain·aigc
郑州光合科技余经理1 小时前
海外版外卖加盟:总站与分站配送规则怎么分开管
java·开发语言·前端·后端·uni-app·php·ai编程
专业程序开发源1 小时前
springboot简历管理系统81389-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·spring·php·课程设计
专业程序开发源2 小时前
springboot社区养老系统44071-计算机课程设计、毕业设计
vue.js·spring boot·后端·python·django·php·课程设计
小呆呆6662 小时前
副业搞起来,小说,漫画,漫剧的成本优化思路
前端·后端·面试
周杰伦fans2 小时前
8GB显存下模型量化实战指南
人工智能·后端·c#
小蒜学长3 小时前
基于SpringBoot的佳新超市管理系统设计与实现系统(代码+数据库+LW)
java·数据库·spring boot·后端·佳新超市管理系统
IT_陈寒3 小时前
Vite静态资源导入这个坑我帮你们踩过了
前端·人工智能·后端
星光开发者4 小时前
基于决策树随机森林算法的小说推荐系统的设计与实现-计算机毕设【课程设计】78485
vue.js·算法·微信小程序·django·php·课程设计·express