PHP trait 是一段可复用的方法和属性。用 trait Name { ... } 声明它,再在任意类的类体里用 use Name; 引入它。PHP 会把 trait 的成员复制到类里,所以它们的工作方式和你直接写在类里完全一样。
<?php
trait Greets
{
public function hello(): string
{
return 'Hello from ' . static::class;
}
}
class User
{
use Greets;
}
class Robot
{
use Greets;
}
echo (new User())->hello(), "\n";
echo (new Robot())->hello(), "\n";
User 和 Robot 没有共同的父类,但现在都有了 hello()。trait 正是为这种情况而存在的:PHP 的类只能继承一个类,而 trait 可以在互不相关的类之间横向共享代码。
在一条 use 语句里列出多个 trait,用逗号分隔,或者写多行 use。类会得到所有这些 trait 的成员:
<?php
trait Loggable
{
private array $log = [];
public function log(string $msg): void
{
$this->log[] = $msg;
}
public function history(): array
{
return $this->log;
}
}
trait Sluggable
{
public function slug(string $text): string
{
return trim(preg_replace('/[^a-z0-9]+/', '-', strtolower($text)), '-');
}
}
class Article
{
use Loggable, Sluggable;
public function __construct(public string $title)
{
$this->log("created '$title'");
}
public function url(): string
{
$url = '/blog/' . $this->slug($this->title);
$this->log("url built: $url");
return $url;
}
}
$a = new Article('PHP Traits, Explained!');
echo $a->url(), "\n";
print_r($a->history());
用 insteadof 和 as 解决冲突
当两个 trait 定义了同名的方法时,PHP 不会去猜,而是直接以致命错误停止。在 use 代码块里,insteadof 选出胜者,as 以另一个名字保留落选的方法:
<?php
trait EnglishGreeting
{
public function greet(): string { return 'Hello'; }
}
trait FrenchGreeting
{
public function greet(): string { return 'Bonjour'; }
}
class Host
{
use EnglishGreeting, FrenchGreeting {
EnglishGreeting::greet insteadof FrenchGreeting;
FrenchGreeting::greet as greetFrench;
}
}
$h = new Host();
echo $h->greet(), "\n";
echo $h->greetFrench(), "\n";
如果只写 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 的别名。
<?php
trait CountsInstances
{
private static int $instances = 0;
public static function created(): int
{
return self::$instances;
}
// The using class must say what it is called
abstract public function label(): string;
public function register(): string
{
self::$instances++;
return $this->label() . ' #' . self::$instances;
}
}
class Car
{
use CountsInstances;
public function label(): string { return 'Car'; }
}
class Bike
{
use CountsInstances;
public function label(): string { return 'Bike'; }
}
echo (new Car())->register(), "\n";
echo (new Car())->register(), "\n";
echo (new Bike())->register(), "\n";
echo 'Cars: ', Car::created(), ', Bikes: ', Bike::created(), "\n";
Car 和 Bike 各自计数,因为静态属性被复制到了每个类里。这和在共同父类中声明的静态属性不同,后者由所有子类共享;static 页面展示了那种情况。
<?php
class Base
{
public function hello(): string { return 'Base'; }
public function bye(): string { return 'Base bye'; }
}
trait Friendly
{
public function hello(): string { return 'Trait, then ' . parent::hello(); }
public function bye(): string { return 'Trait bye'; }
}
class Child extends Base
{
use Friendly;
public function bye(): string { return 'Child bye'; }
}
$c = new Child();
echo $c->hello(), "\n"; // trait overrides parent
echo $c->bye(), "\n"; // class overrides trait
<?php
interface Exportable
{
public function export(): array;
}
trait ExportsProperties
{
public function export(): array
{
return get_object_vars($this);
}
}
class Invoice implements Exportable
{
use ExportsProperties;
public function __construct(public int $id = 7, public float $total = 99.5) {}
}
$inv = new Invoice();
print_r($inv->export());
var_dump($inv instanceof Exportable);
print_r(class_uses($inv));
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; 以新名字保留另一个。