trait是PHP5.4.0中引入的新概念,既像接口又像类。trait是类的部分实现(即常量、属性和方法),可以混入一个或多个现有的PHP类中。trait有两个作用:表明类可以做什么(像接口);提供模块化实现(像是类)。
为什么要使用trait,书中举了一个例子比较形象。首先PHP使用的是一种典型的继承模型,在这种模型中通常是先编写一个通用的根类,实现基本的功能,然后扩展这个根类,创建更具体的类,直接父类继承实现。大多数情况下都能工作的很好,但是如果想让两个无关的PHP类具有类似的行为,就出现问题了。例如,RetailStore和Car两个PHP类都能使用地理编码技术把地址字符串转换为经纬度。
第一个方案,创建一个父类Geocodable,让RetailStore和Car都继承这个类。能解决问题,但是强制让两个无关的类继承同一个父类,很不好。
第二个方案,创建一个Geocodable接口,定义实现地理编码功能需要的方法,然后让RetailStore和Car都实现这个接口。这种解决方法好,因为每个类都能保有自然的继承层次结构。但是,要在两个类中重复实现相同的地理编码功能,这不符合DRY原则。
第三个方案就是利用trait了,创建一个Geocodabletrait,定义并实现地理编码相关的方法,然后把在RetailStore和Car两个类中混入这个trait。这么做不会搅乱这两个类原本自然的继承层次结构。
创建trait,就是利用trait关键字,内部的实现跟类差不多。
// 定义trait
trait TraitTest {
protected $var;
public function setVar($value) {
$this->var = $value;
}
public function getVar() {
if(isset($this->var) === false) {
return false;
}
return $this->var;
}
}
使用trait的时候就是利用use关键字,将trait插入到类中,然后其他实例化和调用跟普通类一样。
class ClassTest {
// 使用trait
use TraitTest;
// 类的定义...
}
$classTest = new ClassTest();
$classTest->setVar(1111);
echo $classTest->getVar();
一个类可以插入多个trait,但是当插入的不同的trait的常量、属性或方法发生冲突时,就会报错。可以利用insteadof或as解决冲突
trait TraitTest2 {
public function func() {
echo 'function2';
}
}
trait TraitTest3 {
public function func() {
echo 'function3';
}
}
class ClassTest {
// 发生冲突时, 可以利用 insteadof 解决冲突;
use TraitTest2, TraitTest3 {
TraitTest2::func insteadof TraitTest3;
}
}
$classTest = new ClassTest();
// 输出为function2
$classTest->func();
还有一个很重要的点就是,从基类继承的成员会被 trait 插入的成员所覆盖。优先顺序是来自当前类的成员覆盖了 trait 的方法,而 trait 则覆盖了被继承的方法。
此外插入了trait的类同样可以继承,继承的时候会连插入的trait一同继承。
class ChildClass extends ClassTest {
}
$childClass = new ChildClass();
$childClass->setVar(2222); // 输出2222
echo $childClass->getVar();
更多详细的的trait内容可以看 官方文档 。
总的来说感觉Trait更像是一个语法糖,而且有一点很不好的地方就是在trait中可以调用类中的成员,在语法上并没有约束,只能通过文档进行约束。如果调用的成员不存在的话,就出错了。
欢迎访问我的个人博客