转自:https://caihongtengxu.github.io/2018/20181126/index.html
Trait 是什么
自 PHP 5.4.0 起,PHP 实现了一种代码复用的方法,称为 trait。
Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制。Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用 method。Trait 和 Class 组合的语义定义了一种减少复杂性的方式,避免传统多继承和 Mixin 类相关典型问题。
Trait 和 Class 相似,但仅仅旨在用细粒度和一致的方式来组合功能。 无法通过 trait 自身来实例化。它为传统继承增加了水平特性的组合;也就是说,应用的几个 Class 之间不需要继承。
Trait 翻译过来就是:特性的意思。 他提供了另一种维度的代码复用 我的理解就是你在引入的时候就等于帮他你把 Trait 中的 Function 复制到了你当前类中 可以让你直接调用
优先级问题
对于我上面犯的错就是典型的优先级问题 官方说的是 优先顺序是当前类中的方法会覆盖 trait 方法,而 trait 方法又覆盖了基类中的方法
首先我定义了一个 trait
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
 | 
 trait TestTrait 
{ 
protected $service; 
public function __construct(TestService $service) 
{ 
$this->service = $service; 
} 
public function showHello() 
{ 
$hello = $this->service->sayHello("reggie"); 
echo $hello; 
} 
} 
 | 
然后在类中引入 trait 调用
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
 | 
 class Test 
{ 
use TestTrait; 
public function __construct() 
{ 
} 
public function showTest() 
{ 
try { 
$this->showHello(); 
} catch (\Exception $exception) { 
echo "have some exception:" . $exception->getMessage(); 
} 
} 
} 
 | 
这个时候会有一个报错 Call to a member function sayHello() on null 这里其实就是优先级的问题 从本质上 你引用trait 就是把他里面的方法复制到了你当前类中, 而我当前类中有一个空的构造函数 就算你在trait中定义了构造(除了我这种脑抽的人应该也没有人会在taint中在定义构造函数了…),根据优先级顺序是当前类覆盖trait,所以trait使用的是当前类的空的构造函数 并不会使用他本身的那个带注入 TestService 的构造函数 所以导致在 showHello 方法中 $this->service 就是一个 null 无法调用到最终的方法
多个Trait处理
在来看一种情况 如果你引入了2个Trait 而且其中都包含一个同名的方法 这个时候调用问题
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
 | 
 trait A 
{ 
public function showName() 
{ 
echo 'reggie'; 
} 
} 
trait B 
{ 
public function showName() 
{ 
echo 'zyan'; 
} 
} 
class Test 
{ 
use A,B; 
public function show() 
{ 
$this->showName(); 
} 
} 
$test = new Test(); 
$test->showName(); 
 | 
这个时候如果你运行的话会有报错
| 
 1 
2 
3 
4 
 | 
 vagrant@homestead:~/code$ php index.php 
PHP Fatal error: Trait method showName has not been applied, because there are collisions with other trait methods on Test in /home/vagrant/code/index.php on line 18 
Fatal error: Trait method showName has not been applied, because there are collisions with other trait methods on Test in /home/vagrant/code/index.php on line 18 
 | 
解决冲突
使用 insteadof(代替)
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
 | 
 class Test 
{ 
use A,B { 
// 这里就声明了使用TraitA中的showName方法代替TraitB中的方法 
A::showName insteadof B; 
// 注意这个时候不能再去声明 B::showName insteadof A 这样的话又回到了原来了 有2个同名方法会报错 还是找不到你要调用哪个 
} 
public function show() 
{ 
// 所以这里执行的话会输出 reggie 
$this->showName(); 
} 
} 
 | 
使用 insteadof + as
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
 | 
 class Test 
{ 
use A,B { 
// 这里就声明了使用TraitA中的showName方法代替TraitB中的方法 
A::showName insteadof B; 
B::showName insteadof A; 
A::showName as showNameA; 
B::showName as showNameB; 
} 
public function show() 
{ 
// 所以这里执行的话会输出 reggie 
$this->showNameA(); 
} 
} 
 | 
关于代码拆分
其中看了 安正超 的文章后感觉学到了很多 引用下
Trait的优点在于随意组合,耦合性低,可读性高。
平常写代码的时候也许怎么拆分才是大家的痛点,分享以下几个技巧:
从需求或功能描述拆分,而不是写了两段代码发现代码一样就提到一起;
拆分时某些属性也一起带走,比如上面第一个例子里的价格,它是“可卖性”必备的属性;
拆分时如果给 Trait 起名困难时,请认真思考你是否真的拆分对了,因为正确的拆分是很容易描述 “它是一个具有什么功能的特性” 的;
参考
http://php.net/manual/zh/language.oop5.traits.php
https://overtrue.me/articles/2016/04/about-php-trait.html
https://zhuanlan.zhihu.com/p/31362082