## traits 详解 >[info] traits 知识并不是ThinkPHP教学的内容,但软删除的模型中应用了这项新技术~~ * * * * * > 我们知道,PHP面向对象是单继承的,无法同时从两个或以上的基类继承属性或方法。从PHP 5.4起, Traits(构件类)技术出现,使多继承成为现实! * * * * * >[warning] 注意: 1. Traits类的声明使用trait关键词, 2. 类中使用use关键字声明要组合的Traits类名称; 3. Traits类不能直接实例化 * * * * * #### 实例演示: ~~~ <?php //创建trait类:Test1 trait Test1 { public function demo1() { return 'Test1::demo1()<br />'; } } //创建基类(父类):Father class Father { public function demo1() { return 'Father::demo1<br />'; } } //创建自定义类:MyClass 是Father的派生类(子类) class MyClass extends Father { use Test1; public function demo1() { return 'MyClass::demo1<br />'; } } //创建自定义MyClass的对象 $obj = new MyClass; //访问$obj对象的demo1()方法 echo $obj -> demo1(); ~~~ > 1. 现在我们已经有了三个类:trait类,基类Father和派生类MyClass; > 2. 每个类中都有一个demo1( )方法; > 3. $obj是MyClass实例化的对象,那么:`echo $obj -> demo1();`将会调用哪个类的demo1()方法呢? * 让我们运行一下吧: ~~~ MyClass::demo1 ~~~ > 可见,实际调用的是MyClass类的demo1()方法。这符合继承的原理,因为子复写了父类同名方法。 > <font color="red">下面我们把MyClass类中的demo1()方法删除</font> * 修改后的代码如下: ~~~ <?php //创建trait类:Test1 trait Test1 { public function demo1() { return 'Test1::demo1()<br />'; } } //创建基类(父类):Father class Father { public function demo1() { return 'Father::demo1<br />'; } } //创建自定义类:MyClass 是Father的派生类(子类) class MyClass extends Father { use Test1; } //创建自定义MyClass的对象 $obj = new MyClass; //访问$obj对象的demo1()方法 echo $obj -> demo1(); ~~~ > 按照之前知识,如果调用的方法,在子类中没有定义,则调用父类中同名方法,真的是这样的吗?让我们测试一下吧: * 这时运行结果如下: ~~~ Test1::demo1() ~~~ > 觉得奇怪吗?为什么不是父类的输出结果:`Father::demo1()`,而是:`Test1::demo1()`呢。这因为子类中,除了要继承Father类,还要继承trait类Test1,显然,子类MyClass中的`use Test1` 的继承优先级要高于原父类的Father,于是产生覆盖效果。 > <span style="color:red;">trait类同名方法覆盖了基类Father中的同名方法。</span> #### 那么我们屏蔽掉:`use Test1;`,会调用父类同名方法吗? * 再次修改一下代码: ~~~ <?php //创建trait类:Test1 trait Test1 { public function demo1() { return 'Test1::demo1()<br />'; } } //创建基类(父类):Father class Father { public function demo1() { return 'Father::demo1<br />'; } } //创建自定义类:MyClass 是Father的派生类(子类) class MyClass extends Father { } //创建自定义MyClass的对象 $obj = new MyClass; //访问$obj对象的demo1()方法 echo $obj -> demo1(); ~~~ > 现在子类MyClass中已经没有use Test1;trait类Test已失效。 * 现在执行结果,如不出意料,应该是父类的demo1()方法的结果: ~~~ Father::demo1 ~~~ >[info] 果然如此,调用是父类中的demo1()方法。 * * * * * >[info] 现在我们思考另一个问题:如果有二个trait类,都是一个demo1方法,怎么办?这是个很常见的问题,必须得到解决。 * 我们再次修改代码,增加一个trait类Test2: ~~~ <?php //创建trait类:Test1 trait Test1 { public function demo1() { return 'Test1::demo1()<br />'; } } //创建trait类:Test2 trait Test2 { public function demo1() { return 'Test2::demo1()<br />'; } } //创建基类(父类):Father class Father { public function demo1() { return 'Father::demo1<br />'; } } //创建自定义类:MyClass 是Father的派生类(子类) class MyClass extends Father { use Test1,Test2; } //创建自定义MyClass的对象 $obj = new MyClass; //访问$obj对象的demo1()方法 echo $obj -> demo1(); ~~~ > 现在有二个trait类Test1和Test2,这二个类中都有demo1方法,`echo $obj -> demo1();`好纠结,应该调用哪个呢? * 运行结果,肯定是错误的: ~~~ //大意是trait中的方法demo1 没有提交运行,因为有重复的定义 Fatal error: Trait method demo1 has not been applied, because there are collisions with other trait methods on MyClass in /Users/zhupeter/Documents/web/test/index.php on line 31 ~~~ >[info] 解决方案有二种: 1. 设置冲突时,调用哪一个方法:`insteadof;` 2. 给可能冲突的方法起一个别名:`as;` ~~~ <?php //创建trait类:Test1 trait Test1 { public function demo1() { return 'Test1::demo1()<br />'; } } //创建trait类:Test2 trait Test2 { public function demo1() { return 'Test2::demo1()<br />'; } } //创建基类(父类):Father class Father { public function demo1() { return 'Father::demo1<br />'; } } //创建自定义类:MyClass 是Father的派生类(子类) class MyClass extends Father { use Test1,Test2{ //方案1:Test2的demo1方法与Test1的demo1方法冲突时,调用Test2的demo1方法; Test2::demo1 insteadof Test1; //Test2中的demo1方法可以使用方法别名td2调用 Test2::demo1 as td2; } } //创建自定义MyClass的对象 $obj = new MyClass; //访问$obj对象的demo1()方法 //根据use中设置的调用规则,现在调用的应该是Test2类中的demo1方法 echo $obj -> demo1(); //第二套解决方案:给可能冲突的该当起别名来规避 //现在调用的仍然是Test2类中的demo1方法 echo $obj -> td2(); ~~~ * 结过分析,运行结果也是可以预见的: ~~~ Test2::demo1() Test2::demo1() ~~~ #### 这种特殊类的声明,学会了吗?亲们~~ * * * * * #### 总结: >[danger] ThinkPHP5中的软删除等很多特殊操作,都是通过trait类来实现的,简而言之,traits 提供了实现多继承的一种机制。一定要掌握这种新技术。