Trait是PHP5.4中新加入的一种的语法结构,用来方便我们实现对象的扩展,是除extend、implements外的另外一种扩展对象的方式。
我将在接下来的内容中介绍以下内容:
- Trait语法在PHP内核中是如何实现的。
- 在(1)的基础上总结我们在使用Trait语法时,应该避免的方式。
一、对象结构的修改
为了实现Trait语法,PHP内核中对实现对象的结构体做了修改:
- struct _zend_class_entry
- {
- ... ...
- zend_class_entry **traits;
- zend_uint num_traits;
- zend_trait_alias **trait_aliases;
- zend_trait_precedence **trait_precedences;
- ... ...
- }
复制代码 [p]其中增加了4个属性,其中比较关键的为两个:
@zend_class_entry **traits;
这个存放着这个类实现的各个trait,可以看出,每一个trait在内核中和对象一样,也是通过zend_class_entry结构实现的。
[p]@num_traits
这个对象实现的trait个数。
二、语法解析及功能实现
理解了上面两个属性的含义后,这是我们便可以开始看PHP是如何来解析trait语法结构的。
这样我们的对象a便使用b这个trait。”use b;”对应的token为(去除了最后的分号token):
- T_USE :use
- T_WHITESPACE :
- T_STRING :t_a
复制代码
我们顺着T_USE在zend_language_parser.y中寻找,其实use有两个用途,一个与trait有关,一个与namespace有关,所以在这个文件中也能相应的找个两个地方,但我们这一次只关心它与trait配对的那个。在582行左右,我们找到了相应的几条与trait有关的解析规则,它们都集中在 582行左右,这里只举例:
- trait_use_statement:
- T_USE trait_list trait_adaptations;
- trait_list:
- fully_qualified_class_name { zend_do_implements_trait(&$1 TSRMLS_CC); }
- | trait_list ',' fully_qualified_class_name { zend_do_implements_trait(&$3 TSRMLS_CC); };
复制代码
use b;对应的是zend_do_implements_trait函数,我们检查这个函数发现,它增加了一条ZEND_ADD_TRAIT指令,对应的是zend_do_implement_trait函数(注意:和前面的相比少了一个s),意思便是向当前正在定义的对象中的(zend_class_entry **traits;)属性里添加一个新的trait,并更新(zend_uint num_traits)等。
在我们对象定义结束后,会调用zend_do_end_class_declaration()函数,而这个函数在PHP5.4版本中做了修改,如果它检测到当前定义的对象使用了trait,则会增加一条ZEND_BIND_TRAITS指令,ZEND虚拟机在执行这条指令的时候便调用zend_do_bind_traits函数来将trait中定义的属性、方法copy给对象,是的,就是完完全全的Copy!
现在一切水落石出,我们已经找到了最关键的zend_do_bind_traits函数,请点击图片看注释:
![]()
首先,将trait中定义的函数copy给对象ce,使用的zend_do_traits_method_binding函数,请点击图片看注释:
![]()
然后再把trait中定义的属性copy给对象ce,使用的是zend_do_traits_property_binding函数,请点击图片看注释:
![]()
好了,先写这么多,大体的流程我们已经分析完了,现在我们便可以总结出几点东西来了: - 从本质上说,trait和include文件的概念差不多
- trait可以更加方便的实现代码复用,因为我们用继承关系实现的无法在父类中访问子类的private属性与方法,而trait就和把代码直接写在对象里效果一样。
- 使用trait时候应该坚决避免命名冲突,尤其是同时使用多个trait时。
- 如果产生了命名冲突,如果两者的可见性、初始值、static与否完全相同,则trait中的会覆盖掉对象中的,并抛出E_STRICT错误,否则会抛出E_COMPILE_ERROR错误,终止编译。
昨天在wordpress上写的时候,对贴大篇幅代码这块一直很头疼,要么超长折行、要么不支持源码与非源码自由切,一切换>就成>了。到最后索性使用了图片,哪位兄台可以给推荐个好用的解决方案。 |