Menu

PHP trait:use、多个 trait 与 insteadof

PHP trait 是一段可复用的方法和属性,用 use TraitName; 把它复制进类里。学习使用多个 trait、用 insteadof 和 as 解决命名冲突、trait 中的抽象成员和静态成员、哪个方法优先,以及 trait、接口和抽象类的区别。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

PHP trait 是一段可复用的方法和属性。用 trait Name { ... } 声明它,再在任意类的类体里用 use Name; 引入它。PHP 会把 trait 的成员复制到类里,所以它们的工作方式和你直接写在类里完全一样。

User 和 Robot 没有共同的父类,但现在都有了 hello()。trait 正是为这种情况而存在的:PHP 的类只能继承一个类,而 trait 可以在互不相关的类之间横向共享代码。

带属性的 trait

trait 可以把属性和方法一起带进来。这些方法像类的任何方法一样使用 $this,因为 trait 一旦被复制进来,它们就是这个类的方法:

类不能声明和 trait 属性同名的属性,除非两处声明完全相同(可见性、类型和默认值都一样);否则会产生致命错误。

使用多个 trait

在一条 use 语句里列出多个 trait,用逗号分隔,或者写多行 use。类会得到所有这些 trait 的成员:

用 insteadof 和 as 解决冲突

当两个 trait 定义了同名的方法时,PHP 不会去猜,而是直接以致命错误停止。在 use 代码块里,insteadof 选出胜者,as 以另一个名字保留落选的方法:

如果只写 use EnglishGreeting, FrenchGreeting; 而不加代码块,脚本在打印任何内容之前就会停止:

PHP Fatal error:  Trait method FrenchGreeting::greet has not been applied as Host::greet, because of collision with EnglishGreeting::greet in /home/index.php on line 12

as 还可以改变可见性。use Loggable { log as protected; } 只在这个类里把 trait 的 log() 变成 protected,而 use Loggable { log as private writeLog; } 会添加一个 private 的别名。

trait 中的抽象成员和静态成员

trait 可以声明抽象方法,要求使用它的类提供某样东西。它也可以有静态方法和静态属性。每个使用该 trait 的类都会得到一份自己的静态属性副本:

Car 和 Bike 各自计数,因为静态属性被复制到了每个类里。这和在共同父类中声明的静态属性不同,后者由所有子类共享;static 页面展示了那种情况。

哪个方法优先:类、trait 还是父类

如果同一个方法名同时存在于类本身、它使用的 trait 和它的父类中,PHP 的选择顺序是:类自己的方法,然后是 trait 的,然后是父类的。在 trait 里,parent:: 仍然能访问父类的版本:

trait 不是类型

instanceof 永远不会匹配 trait,以 trait 名作为类型声明的参数会对每个对象抛出 TypeError。要判断“这个类有没有这种行为”,就给 trait 配一个接口,或者用 class_uses() 检查这个类:

上面是常见的模式:接口 Exportable 用于函数的类型声明和 instanceof 检查,trait ExportsProperties 是一个现成的实现,类可以直接使用,也可以替换掉。单独的 trait 只是没有约定的共享代码;当你发现自己在业务逻辑里检查 class_uses() 时,说明这个 trait 需要配一个接口。对于一组相关类共享的代码,抽象类可能是更简单的选择。

常见问题

PHP 中的 trait 是什么?

trait 是用 trait Name { } 声明的一组具名的方法(和属性),类通过 use Name; 把它引入。PHP 会把 trait 的成员复制到类里,所以它们的表现就像你直接写在类里一样。它用来在没有共同父类的类之间共享代码。

一个 PHP 类可以使用多个 trait 吗?

可以:写 use Timestamps, SoftDeletes;,或者写多行 use。如果两个 trait 定义了同名的方法,你必须用 insteadof 选出一个,比如 use A, B { A::hello insteadof B; },还可以用 B::hello as helloB; 以新名字保留另一个。

PHP 中 trait 和接口有什么区别?

接口是没有代码的类型:它规定一个类必须有哪些方法,并且可以配合 instanceof 使用。trait 是没有类型的代码:它把方法体粘贴进类里,但 $obj instanceof SomeTrait 永远为 false。两者经常搭配使用:接口提供类型,trait 提供默认实现。

trait 可以有构造函数或抽象方法吗?

两者都可以。trait 可以声明 abstract public function name(): string;,要求使用它的类提供这个方法;它也可以定义 __construct,不过 trait 里的构造函数很容易和类自己的构造函数冲突,很少是个好主意。

类和 trait 定义了同一个方法时,哪个优先?

类自己的方法优先于 trait 的方法,trait 的方法优先于从父类继承的方法。所以顺序是:类,然后 trait,然后父类。

Coddy 编程语言插图

和 Coddy 一起学编程

开始