自 PHP 5.4.0 起,PHP 实现了一种代码复用的方法,称为 trait。Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制。Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用 method。Trait 和 Class 组合的语义定义了一种减少复杂性的方式,避免传统多继承和 Mixin 类相关典型问题。 Trait 和 Class 相似,但仅仅旨在用细粒度和一致的方式来组合功能。 无法通过 trait 自身来实例化。它为传统继承增加了水平特性的组合;也就是说,应用的几个 Class 之间不需要继承。

<?php//日志类class Logger{ //读取日志信息 public function log(string $message, int $level) { echo "[message]:{$message}" . PHP_EOL; echo "[level]:{$level}" . PHP_EOL; }}//扩展日志功能trait Loggable{ protected $logger; /** * 记录日志 * @param DemoLogger $logger */ public function setLogger(Logger $logger) { $this->logger = $logger; } /** * 读取日志 * @param string $message * @param int $level */ public function getLog(string $message, int $level) { $this->logger->log($message, $level); } public function test() { echo 'trait test' . PHP_EOL; }}//基类class Base{ public static $className = 'Base'; public function test() { echo static::getClassName() . ' test' . PHP_EOL; } //获取类名称 public static function getClassName(): string { //return self::$className; return static::$className;//static延时静态绑定 }}class Foo extends Base{ public static $className = 'Foo'; use Loggable;}$foo = new Foo;$foo->setLogger(new Logger);$foo->getLog('trait works', 1);//打印日志信息$foo->test(); //trait test

分析这里 $foo->test()Foo类中使用use Loggable来扩展Foo类增加日志功能;Trait Loggable类中含有test()方法;Foo类继承Base类,其中Base类中含有test();那么问题来了:$foo->test()到底调用的是继承自父类test(),还是Trait类中的test()?

Trait优先级当前类的函数会覆盖 trait 的同名函数,trait 会覆盖父类的同名函数( use trait 相当于当前类直接覆写了父类的同名函数)

因此,$foo->test()调用的是Trait类中的方法



使用多个Trait及冲突的解决

Trait Alibaba{ public function getCEO(): string { return '阿里巴巴CEO:马云' . PHP_EOL; } public function getAddress(): string { return '阿里巴巴总部位于杭州' . PHP_EOL; }}Trait Tencent{ public function getCEO(): string { return '腾讯CEO:马化腾' . PHP_EOL; } public function getAddress(): string { return '腾讯总部位于深圳' . PHP_EOL; }}class TopBoss{ use Alibaba, Tencent;}$MaBoss = new TopBoss();echo $MaBoss->getCEO();echo $MaBoss->getAddress();

解决方案

使用 insteadof(取代) 操作符来明确指定使用冲突方法中的哪一个as 操作符可以 为某个方法引入别名。 注意,as 操作符不会对方法进行重命名,也不会影响其方法。

最终代码:

class TopBoss{ use Alibaba, Tencent { Tencent::getCEO insteadof Alibaba;//指定冲突时,使用谁 Tencent::getAddress insteadof Alibaba; Alibaba::getAddress as getA;//取别名,可以通过别名调用 Alibaba::getCEO as getC; }}$MaBoss = new TopBoss();echo $MaBoss->getCEO();//腾讯CEO:马化腾echo $MaBoss->getAddress();//腾讯总部位于深圳echo $superBoss->getC();//阿里巴巴CEO:马云echo $superBoss->getA();//阿里巴巴总部位于杭州

Laravel中的代码示例

<?phpnamespace Illuminate\Support;use ArrayAccess;class Optional implements ArrayAccess{ use Traits\Macroable { __call as macroCall; } ... /** * Dynamically pass a method to the underlying object. * * @param string $method * @param array $parameters * @return mixed */ public function __call($method, $parameters) { if (static::hasMacro($method)) { return $this->macroCall($method, $parameters); } ... }