PHP内核-类和面向对象的代码详解

黄舟
黄舟 原创
2023-03-06 13:18:02 904浏览

在最开始接触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类名ParentClassIfceTipi
type类别2(用户自定义)2 (用户自定义)2 (用户自定义,1为系统内置类)
parent父类ParentClass类
refcount引用计数112
ce_flags类的类型0144524352
function_table函数列表function_name=iMethod | type=2 | fn_flags=258function_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类开始行数151822
line_end类结束行数162038


二。变量与成员变量

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中文网其它相关文章!

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。