PHP内核探索:类的成员方法

成员方法从本质上来讲也是一种函数
服务器君一共花费了218.805 ms进行了5次数据库查询,努力地为您提供了这个页面。
试试阅读模式?希望听取您的建议

成员方法从本质上来讲也是一种函数,所以其存储结构也和常规函数一样,存储在zend_function结构体中。 对于一个类的多个成员方法,它是以HashTable的数据结构存储了多个zend_function结构体。 和前面的成员变量一样,在类声明时成员方法也通过调用zend_initialize_class_data方法,初始化了整个方法列表所在的HashTable。 在类中我们如果要定义一个成员方法,格式如下:

class Tipi{
    public function t() {
        echo 1;
    }
}

除去访问控制关键字,一个成员方法和常规函数是一样的,从语法解析中调用的函数一样(都是zend_do_begin_function_declaration函数), 但是其调用的参数有一些不同,第三个参数is_method,成员方法的赋值为1,表示它作为成员方法的属性。 在这个函数中会有一系统的编译判断,比如在接口中不能声明私有的成员方法。 看这样一段代码:

interface Ifce {
   private function method();
}

如果直接运行,程序会报错:Fatal error: Access type for interface method Ifce::method() must be omitted in 这段代码对应到zend_do_begin_function_declaration函数中的代码,如下:

if (is_method) {
    if (CG(active_class_entry)->ce_flags & ZEND_ACC_INTERFACE) {
        if ((Z_LVAL(fn_flags_znode->u.constant) & ~(ZEND_ACC_STATIC|ZEND_ACC_PUBLIC))) {
            zend_error(E_COMPILE_ERROR, "Access type for interface method %s::%s() must be omitted",
                CG(active_class_entry)->name, function_name->u.constant.value.str.val);
        }
        Z_LVAL(fn_flags_znode->u.constant) |= ZEND_ACC_ABSTRACT; /* propagates to the rest of the parser */
    }
    fn_flags = Z_LVAL(fn_flags_znode->u.constant); /* must be done *after* the above check */
} else {
    fn_flags = 0;
}

在此程序判断后,程序将方法直接添加到类结构的function_talbe字段,在此之后,又是若干的编译检测。 比如接口的一些魔术方法不能被设置为非公有,不能被设置为static,如__call()、__callStatic()、__get()等。 如果在接口中设置了静态方法,如下定义的一个接口:

interface ifce {
    public static function __get();
}

若运行这段代码,则会显示Warning:Warning: The magic method __get() must have public visibility and cannot be static in

这段编译检测在zend_do_begin_function_declaration函数中对应的源码如下:

if (CG(active_class_entry)->ce_flags & ZEND_ACC_INTERFACE) {
        if ((name_len == sizeof(ZEND_CALL_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_CALL_FUNC_NAME, sizeof(ZEND_CALL_FUNC_NAME)-1))) {
            if (fn_flags & ((ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC) ^ ZEND_ACC_PUBLIC)) {
                zend_error(E_WARNING, "The magic method __call() must have public visibility and cannot be static");
            }
        } else if() {   //  其它魔术方法的编译检测
        }
}

同样,对于类中的这些魔术方法,也有同样的限制,如果在类中定义了静态的魔术方法,则显示警告。如下代码:

class Tipi {
    public static function __get($var) {
 
    }
}

运行这段代码,则会显示: Warning: The magic method __get() must have public visibility and cannot be static in

与成员变量一样,成员方法也有一个返回所有成员方法的函数--get_class_methods()。 此函数返回由指定的类中定义的方法名所组成的数组。 从 PHP 4.0.6 开始,可以指定对象本身来代替指定的类名。 它属于PHP内建函数,整个程序流程就是一个遍历类成员方法列表,判断是否为符合条件的方法, 如果是,则将这个方法作为一个元素添加到返回数组中。

静态成员方法

类的静态成员方法通常也叫做类方法。 与静态成员变量不同,静态成员方法与成员方法都存储在类结构的function_table 字段。

类的静态成员方法可以通过类名直接访问。

class Tipi{
    public static function t() {
        echo 1;
    }
}
 
Tipi::t();

以上的代码在VLD扩展下生成的部分中间代码如如下:

number of ops:  8
compiled vars:  none
line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
   2     0  >   EXT_STMT
         1      NOP
   8     2      EXT_STMT
         3      ZEND_INIT_STATIC_METHOD_CALL                             'Tipi','t'
         4      EXT_FCALL_BEGIN
         5      DO_FCALL_BY_NAME                              0
         6      EXT_FCALL_END
   9     7    > RETURN                                                   1
 
branch: #  0; line:     2-    9; sop:     0; eop:     7
path #1: 0,
Class Tipi:
Function t:
Finding entry points
Branch analysis from position: 0

从以上的内容可以看出整个静态成员方法的调用是一个先查找方法,再调用的过程。 而对于调用操作,对应的中间代码为 ZEND_INIT_STATIC_METHOD_CALL。由于类名和方法名都是常量, 于是我们可以知道中间代码对应的函数是ZEND_INIT_STATIC_METHOD_CALL_SPEC_CONST_CONST_HANDLER。 在这个函数中,它会首先调用zend_fetch_class函数,通过类名在EG(class_table)中查找类,然后再执行静态方法的获取方法。

if (ce->get_static_method) {
    EX(fbc) = ce->get_static_method(ce, function_name_strval, function_name_strlen TSRMLS_CC);
} else {
    EX(fbc) = zend_std_get_static_method(ce, function_name_strval, function_name_strlen TSRMLS_CC);
}

如果类结构中的get_static_method方法存在,则调用此方法,如果不存在,则调用zend_std_get_static_method。 在PHP的源码中get_static_method方法一般都是NULL,这里我们重点查看zend_std_get_static_method函数。 此函数会查找ce->function_table列表,在查找到方法后检查方法的访问控制权限,如果不允许访问,则报错,否则返回函数结构体。 关于访问控制,我们在后面的小节中说明。

静态方法和实例方法的小漏洞

细心的读者应该注意到前面提到静态方法和实例方法都是保存在类结构体zend_class_entry.function_table中,那这样的话, Zend引擎在调用的时候是怎么区分这两类方法的,比如我们静态调用实例方法或者实例调用静态方法会怎么样呢?

可能一般人不会这么做,不过笔者有一次错误的这样调用了,而代码没有出现任何问题, 在review代码的时候意外发现笔者像实例方法那样调用的静态方法,而什么问题都没有发生(没有报错)。 在理论上这种情况是不应发生的,类似这这样的情况在PHP中是非常的多的,例如前面提到的create_function方法返回的伪匿名方法, 后面介绍访问控制时还会介绍访问控制的一些瑕疵,PHP在现实中通常采用Quick and Dirty的方式来实现功能和解决问题, 这一点和Ruby完整的面向对象形成鲜明的对比。我们先看一个例子:

<?php
 
error_reporting(E_ALL);
 
class A {
    public static function staticFunc() {
        echo "static";
    }
 
    public function instanceFunc() {
        echo "instance";    
    }
}
 
A::instanceFunc(); // instance
$a = new A();
$a->staticFunc();  // static
?>

上面的代码静态的调用了实例方法,程序输出了instance,实例调用静态方法也会正确输出static,这说明这两种方法本质上并没有却别。 唯一不同的是他们被调用的上下文环境,例如通过实例方法调用方法则上下文中将会有$this这个特殊变量,而在静态调用中将无法使用$this变量。

不过实际上Zend引擎是考虑过这个问题的,将error_reporting的级别增加E_STRICT,将会出出现E_STRICT错误:

Strict Standards: Non-static method A::instanceFunc() should not be called statically

这只是不建议将实例方法静态调用,而对于实例调用静态方法没有出现E_STRICT错误,有人说:某些事情可以做并不代表我们要这样做。

PHP在实现新功能时通常采用渐进的方式,保证兼容性,在具体实现上通常采用打补丁的方式,这样就造成有些”边界“情况没有照顾到。

延伸阅读

此文章所在专题列表如下:

  1. PHP内核探索:从SAPI接口开始
  2. PHP内核探索:一次请求的开始与结束
  3. PHP内核探索:一次请求生命周期
  4. PHP内核探索:单进程SAPI生命周期
  5. PHP内核探索:多进程/线程的SAPI生命周期
  6. PHP内核探索:Zend引擎
  7. PHP内核探索:再次探讨SAPI
  8. PHP内核探索:Apache模块介绍
  9. PHP内核探索:通过mod_php5支持PHP
  10. PHP内核探索:Apache运行与钩子函数
  11. PHP内核探索:嵌入式PHP
  12. PHP内核探索:PHP的FastCGI
  13. PHP内核探索:如何执行PHP脚本
  14. PHP内核探索:PHP脚本的执行细节
  15. PHP内核探索:操作码OpCode
  16. PHP内核探索:PHP里的opcode
  17. PHP内核探索:解释器的执行过程
  18. PHP内核探索:变量概述
  19. PHP内核探索:变量存储与类型
  20. PHP内核探索:PHP中的哈希表
  21. PHP内核探索:理解Zend里的哈希表
  22. PHP内核探索:PHP哈希算法设计
  23. PHP内核探索:翻译一篇HashTables文章
  24. PHP内核探索:哈希碰撞攻击是什么?
  25. PHP内核探索:常量的实现
  26. PHP内核探索:变量的存储
  27. PHP内核探索:变量的类型
  28. PHP内核探索:变量的值操作
  29. PHP内核探索:变量的创建
  30. PHP内核探索:预定义变量
  31. PHP内核探索:变量的检索
  32. PHP内核探索:变量的类型转换
  33. PHP内核探索:弱类型变量的实现
  34. PHP内核探索:静态变量的实现
  35. PHP内核探索:变量类型提示
  36. PHP内核探索:变量的生命周期
  37. PHP内核探索:变量赋值与销毁
  38. PHP内核探索:变量作用域
  39. PHP内核探索:诡异的变量名
  40. PHP内核探索:变量的value和type存储
  41. PHP内核探索:全局变量Global
  42. PHP内核探索:变量类型的转换
  43. PHP内核探索:内存管理开篇
  44. PHP内核探索:Zend内存管理器
  45. PHP内核探索:PHP的内存管理
  46. PHP内核探索:内存的申请与销毁
  47. PHP内核探索:引用计数与写时复制
  48. PHP内核探索:PHP5.3的垃圾回收机制
  49. PHP内核探索:内存管理中的cache
  50. PHP内核探索:写时复制COW机制
  51. PHP内核探索:数组与链表
  52. PHP内核探索:使用哈希表API
  53. PHP内核探索:数组操作
  54. PHP内核探索:数组源码分析
  55. PHP内核探索:函数的分类
  56. PHP内核探索:函数的内部结构
  57. PHP内核探索:函数结构转换
  58. PHP内核探索:定义函数的过程
  59. PHP内核探索:函数的参数
  60. PHP内核探索:zend_parse_parameters函数
  61. PHP内核探索:函数返回值
  62. PHP内核探索:形参return value
  63. PHP内核探索:函数调用与执行
  64. PHP内核探索:引用与函数执行
  65. PHP内核探索:匿名函数及闭包
  66. PHP内核探索:面向对象开篇
  67. PHP内核探索:类的结构和实现
  68. PHP内核探索:类的成员变量
  69. PHP内核探索:类的成员方法
  70. PHP内核探索:类的原型zend_class_entry
  71. PHP内核探索:类的定义
  72. PHP内核探索:访问控制
  73. PHP内核探索:继承,多态与抽象类
  74. PHP内核探索:魔术函数与延迟绑定
  75. PHP内核探索:保留类与特殊类
  76. PHP内核探索:对象
  77. PHP内核探索:创建对象实例
  78. PHP内核探索:对象属性读写
  79. PHP内核探索:命名空间
  80. PHP内核探索:定义接口
  81. PHP内核探索:继承与实现接口
  82. PHP内核探索:资源resource类型
  83. PHP内核探索:Zend虚拟机
  84. PHP内核探索:虚拟机的词法解析
  85. PHP内核探索:虚拟机的语法分析
  86. PHP内核探索:中间代码opcode的执行
  87. PHP内核探索:代码的加密与解密
  88. PHP内核探索:zend_execute的具体执行过程
  89. PHP内核探索:变量的引用与计数规则
  90. PHP内核探索:新垃圾回收机制说明

本文地址:http://www.nowamagic.net/librarys/veda/detail/1500,欢迎访问原出处。

不打个分吗?

转载随意,但请带上本文地址:

http://www.nowamagic.net/librarys/veda/detail/1500

如果你认为这篇文章值得更多人阅读,欢迎使用下面的分享功能。
小提示:您可以按快捷键 Ctrl + D,或点此 加入收藏

大家都在看

阅读一百本计算机著作吧,少年

很多人觉得自己技术进步很慢,学习效率低,我觉得一个重要原因是看的书少了。多少是多呢?起码得看3、4、5、6米吧。给个具体的数量,那就100本书吧。很多人知识结构不好而且不系统,因为在特定领域有一个足够量的知识量+足够良好的知识结构,系统化以后就足以应对大量未曾遇到过的问题。

奉劝自学者:构建特定领域的知识结构体系的路径中再也没有比学习该专业的专业课程更好的了。如果我的知识结构体系足以囊括面试官的大部分甚至吞并他的知识结构体系的话,读到他言语中的一个词我们就已经知道他要表达什么,我们可以让他坐“上位”毕竟他是面试官,但是在知识结构体系以及心理上我们就居高临下。

所以,阅读一百本计算机著作吧,少年!

《代码之美》 聂雪军 (译者)

《代码之美》介绍了人类在一个奋斗领域中的创造性和灵活性:计算机系统的开发领域。在每章中的漂亮代码都是来自独特解决方案的发现,而这种发现是来源于作者超越既定边界的远见卓识,并且识别出被多数人忽视的需求以及找出令人叹为观止的问题解决方案。《代码之美》33章,有38位作者,每位作者贡献一章。每位作者都将自己心目中对于“美丽的代码”的认识浓缩在一章当中,张力十足。38位大牛,每个人对代码之美都有自己独特的认识,现在一览无余的放在一起,对于热爱程序的每个人都不啻一场盛宴。 虽然《代码之美》的涉猎范围很广,但也只能代表一小部分在这个软件开发这个最令人兴奋领域所发生的事情。

更多计算机宝库...