在最开始接触PHP的时候,都是面向过程的方法来自己做一些很简单的网站在玩,写PHP代码就是堆砌,拓展性与维护性太差改个逻辑极不方便。后来发现PHP是支持面向对象的,忽然觉得自己那是后还真是年轻,真是孤陋寡闻呀,毕竟PHP是用C来实现,也不足为奇。
前言:
从我们接触PHP开始,我们最先遇到的是函数:数组操作函数,字符串操作函数,文件操作函数等等。 这些函数是我们使用PHP的基础,也是PHP自出生就支持的面向过程编程。面向过程将一个个功能封装, 以一种模块化的思想解决问题。
从PHP4起开始支持面向对象编程。但PHP4的面向对象支持不太完善。 从PHP5起,PHP引入了新的对象模型(Object Model),增加了许多新特性,包括访问控制、 抽象类和final类、类方法、魔术方法、接口、对象克隆和类型提示等。并且在近期发布的PHP5.3版本中,针对面向对象编程增加了命名空间、延迟静态绑定以及增加了两个魔术方法__callStatic()和__invoke()。
那么,在PHP底层,其是怎么实现的呢,其结构如何?
一。类的结构
引用TIPI的一个事例:
class ParentClass { } interface Ifce { public function iMethod(); } final class Tipi extends ParentClass implements Ifce { public static $sa = 'aaa'; const CA = 'bbb'; public function __constrct() { } public function iMethod() { } private function _access() { } public static function access() { } }
这里定义了一个父类ParentClass,一个接口Ifce,一个子类Tipi。子类继承父类ParentClass, 实现接口Ifce,并且有一个静态变量$sa,一个类常量 CA,一个公用方法,一个私有方法和一个公用静态方法。 这些结构在Zend引擎内部是如何实现的?类的方法、成员变量是如何存储的?访问控制,静态成员是如何标记的?
首先,我们看看类的内部存储结构:
取上面这个结构的部分字段,我们分析文章最开始的那段PHP代码在内核中的表现。 如下所示:
字段名 | 字段说明 | ParentClass类 | Ifce接口 | Tipi类 |
name | 类名 | ParentClass | Ifce | Tipi |
type | 类别 | 2(用户自定义) | 2 (用户自定义) | 2 (用户自定义,1为系统内置类) |
parent | 父类 | 空 | 空 | ParentClass类 |
refcount | 引用计数 | 1 | 1 | 2 |
ce_flags | 类的类型 | 0 | 144 | 524352 |
function_table | 函数列表 | 空 | function_name=iMethod | type=2 | fn_flags=258 | function_name=__construct | type=2 | fn_flags=8448 function_name=iMethod | type=2 | fn_flags=65800 function_name=_access | type=2 | fn_flags=66560 function_name=access | type=2 | fn_flags=257 |
interfaces | 接口列表 | 空 | 空 | Ifce接口 接口数为1 |
filename | 存放文件地址 | /tipi.php | /tipi.php | /ipi.php |
line_start | 类开始行数 | 15 | 18 | 22 |
line_end | 类结束行数 | 16 | 20 | 38 |
二。变量与成员变量
如PHP内核的存储机制(分离/改变)所介绍,
变量要么是定义在全局范围中,叫做全局变量,要么是定义在某个函数中, 叫做局部变量。
成员变量是定义在类里面,并和成员方法处于同一层次。如下一个简单的PHP代码示例,定义了一个类, 并且这个类有一个成员变量。
class Tipi { public $var; }
1.成员变量的访问:
访问这个成员变量当然是通过对象来访问。
2.成员变量的规则:
1.接口中不允许使用成员变量
2.成员变量不能拥有抽象属性
3.不能声明成员变量为final
4.不能重复声明属性
在声明类的时候初始化了类的成员变量所在的HashTable,之后如果有新的成员变量声明时,在编译时zend_do_declare_property。函数首先检查成员变量不允许的这4 条情况。
比如:.
class Tipi { public final $var; }
运行程序将报错,违反了第三条:Fatal error: Cannot declare property Tipi::$var final, the final modifier is allowed only for methods and classes in .. 这个错误由zend_do_declare_property函数抛出
三。函数与成员方法
成员方法从本质上来讲也是一种函数,所以其存储结构也和常规函数一样,存储在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函数中的代码。
四。方法(Function)与函数(Method)的异同
在前面介绍了函数的实现,函数与方法的本质是比较相似的,都是将一系列的逻辑放到一个集合里执行, 但二者在使用中也存在很多的不同,这里我们讨论一下二者的实现。 从实现的角度来看,二者内部代码都被最终解释为op_array,其执行是没有区别的(除非使用了$this/self等对象特有的变法或方法), 而二者的不同体现在两个方面:
1.是定义(注册)的实现;
2.是调用的实现;
定义(注册)方式的实现
函数和方法都是在编译阶段注册到compiler_globals变量中的,二者都使用相同的内核处理函数zend_do_begin_function_declaration() 和zend_do_end_function_declaration()来完成这一过程。 二者的内部内容会被最终解释并存储为一个op_codes数组,但编译后“挂载”的位置不同,如下图:
PHP中函数与方法的注册位置
调用方式的实现
定义位置的不同,以及性质的不同,决定了方法比函数要进行更多的验证工作, 方法的调用比函数的调用多一个名为ZEND_INIT_METHOD_CALL的OPCODE,
其作用是把方法注册到execute_data.fbc , 然后就可以使用与函数相同的处理函数 ZEND_DO_FCALL_BY_NAME进行处理。
以上是PHP内核-类和面向对象的代码详解的详细内容。更多信息请关注PHP中文网其他相关文章!