在最开始接触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中文網其他相關文章!