Php内核学习之类与对象详解

本文和大家分享的主要是php内核开发中的类与对象相关内容,一起来看看吧,希望对大家有所帮助。

类是什么,什么是对象,相信不需要我在这里解释。本文也不是要说什么OO思想,而是想探究一个问题。作为 PHP 底层的实现 C 语言,是一个面向过程的语言,C 语言是如何构建出可以使用类与对象的 PHP(PHP 绝对称不上是面向对象的语言),这就是本文探讨的重点。为了方便查阅,也方便说明,以下所涉及的源码和实现均来源于 PHP7,后面所有出现的 PHP 均表示 PHP7 版本。

通常,我们概念中,类是一种抽象,类似于 C 语言中的结构体,本身称不上是一个实体。但在 PHP 的实现中,为了赋予不同的类特定的行为和数据结构,类不得不作为一个实体存在。

类的数据结构

在 PHP 的源码里,类是承载在 zend_class_entry 数据结构上的。

struct _zend_class_entry {

char type;

zend_string *name;

struct _zend_class_entry *parent;

int refcount;

uint32_t ce_flags;

int default_properties_count;

int default_static_members_count;

zval *default_properties_table;

zval *default_static_members_table;

zval *static_members_table;

HashTable function_table;

HashTable properties_info;

HashTable constants_table;

union _zend_function *constructor;

union _zend_function *destructor;

union _zend_function *clone;

union _zend_function *__get;

union _zend_function *__set;

union _zend_function *__unset;

union _zend_function *__isset;

union _zend_function *__call;

union _zend_function *__callstatic;

union _zend_function *__tostring;

union _zend_function *__debugInfo;

union _zend_function *serialize_func;

union _zend_function *unserialize_func;

zend_class_iterator_funcs iterator_funcs;

/* handlers */

zend_object* (*create_object)(zend_class_entry *class_type);

zend_object_iterator *(*get_iterator)(zend_class_entry *ce, zval *object, int by_ref);

int (*interface_gets_implemented)(zend_class_entry *iface, zend_class_entry *class_type); /* a class implements this interface */

union _zend_function *(*get_static_method)(zend_class_entry *ce, zend_string* method);

/* serializer callbacks */

int (*serialize)(zval *object, unsigned char **buffer, size_t *buf_len, zend_serialize_data *data);

int (*unserialize)(zval *object, zend_class_entry *ce, const unsigned char *buf, size_t buf_len, zend_unserialize_data *data);

uint32_t num_interfaces;

uint32_t num_traits;

zend_class_entry **interfaces;

zend_class_entry **traits;

zend_trait_alias **trait_aliases;

zend_trait_precedence **trait_precedences;

union {

struct {

zend_string *filename;

uint32_t line_start;

uint32_t line_end;

zend_string *doc_comment;

} user;

struct {

const struct _zend_function_entry *builtin_functions;

struct _zend_module_entry *module;

} internal;

} info;

};

可以看到,类的属性繁多,这里我不会一一展开说明。可以看到的是,它会占用不小的内存,具体是多少呢?568字节,这其中还不包括类定义的属性和方法所占用的内存。

因此,PHP 类是一个相对比较重的实体,之所以设计的重,也是为了减少真正的对象实体所消耗的资源,毕竟相对于对象,定义的类的数量要少的多。

接口和类有什么区别

接口、traits、类本质上都是一样的,都是基于上文描述的 zend_class_entry 结构。接口的行为和类很相似,只是在使用上有一些限制。比如,不能定义属性,在编译的阶段特殊处理一下,直接禁止接口使用诸如 static_members_table 这样的字段即可。

因此,接口和 traits 的开销与类差不多,我们可以通过以下的方式来验证:

$class = <<<’CL’interface Bar { }

CL;

$m = memory_get_usage();

eval($class); echo memory_get_usage() - $m . "\n"; /* 912 bytes */

继承是如何实现的

继承提供了一种明确表述共性的方法,是一个新类从现有的类中派生的过程。 继承产生的新类继承了原始类的特性,新类称为原始类的派生类(或子类), 而原始类称为新类的基类(或父类)。

对于 PHP 而言,实现继承的关键点就在于将父类的属性和方法复制给子类。这个过程被放在了编译阶段。具体实现方法可以查看Zend/zend_inheritance.c 中的 zend_do_inheritance() 函数。

ZEND_API void zend_do_inheritance(zend_class_entry *ce, zend_class_entry *parent_ce) /* {{{ */

{

zend_property_info *property_info;

zend_function *func;

zend_string *key;

if (UNEXPECTED(ce->ce_flags & ZEND_ACC_INTERFACE)) {

/* Interface can only inherit other interfaces */

if (UNEXPECTED(!(parent_ce->ce_flags & ZEND_ACC_INTERFACE))) {

zend_error_noreturn(E_COMPILE_ERROR, "Interface %s may not inherit from class (%s)", ZSTR_VAL(ce->name), ZSTR_VAL(parent_ce->name));

}

} else if (UNEXPECTED(parent_ce->ce_flags & (ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_FINAL))) {

/* Class declaration must not extend traits or interfaces */

if (parent_ce->ce_flags & ZEND_ACC_INTERFACE) {

zend_error_noreturn(E_COMPILE_ERROR, "Class %s cannot extend from interface %s", ZSTR_VAL(ce->name), ZSTR_VAL(parent_ce->name));

} else if (parent_ce->ce_flags & ZEND_ACC_TRAIT) {

zend_error_noreturn(E_COMPILE_ERROR, "Class %s cannot extend from trait %s", ZSTR_VAL(ce->name), ZSTR_VAL(parent_ce->name));

}

/* Class must not extend a final class */

if (parent_ce->ce_flags & ZEND_ACC_FINAL) {

zend_error_noreturn(E_COMPILE_ERROR, "Class %s may not inherit from final class (%s)", ZSTR_VAL(ce->name), ZSTR_VAL(parent_ce->name));

}

}

...

}

多态是如何实现的

至于多态,是调用同一个方法,对于不同的对象能够产生不同的行为,往往会定义一个接口规范具体实现类需要实现的方法:

<?php interface Animal {

public function run();

}

class Dog implements Animal {

public function run() {

echo ’dog run’;

}

}

class Cat implements Animal{

public function run() {

echo ’cat run’;

}

}

class Context {

private $_animal;

public function __construct(Animal $animal) {

$this->_animal = $animal;

}

public function run() {

$this->_animal->run();

}

}

在 PHP 的实现中,核心点在于传入方法的参数可以通过指定接口或者任意级的父类来限制。在 PHP 的设计中,这个语法称之为类型提示。相较于标准类型,对象的类型提示的判断需要额外处理一些逻辑,实现代码在 Zend/zend_execute.c 中:

static zend_always_inline int zend_verify_arg_type(zend_function *zf, uint32_t arg_num, zval *arg, zval *default_value,void **cache_slot) {

...

if (UNEXPECTED(!zend_check_type(zf, cur_arg_info, arg, &ce, cache_slot, default_value, 0))) {

zend_verify_arg_error(zf, cur_arg_info, arg_num, ce, arg);

return 0;

}

return 1;

}

static zend_always_inline zend_bool zend_check_type(

const zend_function *zf, const zend_arg_info *arg_info,

zval *arg, zend_class_entry **ce, void **cache_slot,

zval *default_value, zend_bool is_return_type){

...

ZVAL_DEREF(arg);

if (EXPECTED(arg_info->type_hint == Z_TYPE_P(arg))) {

if (arg_info->class_name) {

if (EXPECTED(*cache_slot)) {

*ce = (zend_class_entry *) *cache_slot;

} else {

*ce = zend_verify_arg_class_kind(arg_info);

if (UNEXPECTED(!*ce)) {

return 0;

}

*cache_slot = (void *) *ce;

}

if (UNEXPECTED(!instanceof_function(Z_OBJCE_P(arg), *ce))) {

return 0;

}

}

return 1;

}

...

}

可以看到,如果参数类型被定义为类或者接口,则调用 zend_verify_arg_class_kind() 函数获取对应的类数据,然后调用instanceof_function() 函数。此函数首先会遍历实例所在类的所有接口,递归调用其本身,判断实例的接口是否为指定类的实例。

对象

对象的数据结构

还是要把 PHP 对象的数据结构说明一下:

struct _zend_object {

zend_refcounted_h gc;

uint32_t handle;

zend_class_entry *ce;

const zend_object_handlers *handlers;

HashTable *properties;

zval properties_table[1]; /* C struct hack */

};

这个结构体包含以下几个属性:

· ce 是指向上一章节我们提到的类数据的指针。

· handle 在 PHP7 内核分析:变量的设计 的关于对象池的概念中已经说明了,内容比较多,不再次展开说明。

· properties 用于存储定义在类中的属性。

· properties_table 用于存储动态定义的属性。(这里还利用了 C 语言的 struct hack 的 trick)

· handlers 指向了标准对象处理函数 std_object_handlers 。

· gc 记录了当前对象被引用的状态,用于垃圾回收机制处理。

对象是怎样创建的

创建对象的过程分成两个步骤:

<?php class A {

}

$a = new A();

我们通过 VLD 可以看到,创建对象的操作产生了两个 OPCODE:

Finding entry points

Branch analysis from position: 0

Jump found. Position 1 = -2

filename: /Users/joshua/Programs/local/test/php/opcode/object.php function name: (null)

number of ops: 5

compiled vars: !0 = $a

line #* E I O op fetch ext return operands

-------------------------------------------------------------------------------------

3 0 E > NOP

7 1 NEW $2 :-6

2 DO_FCALL 0

3 ASSIGN !0, $2

4 > RETURN 1

branch: # 0; line: 3- 7; sop: 0; eop: 4; out1: -2

path #1: 0,

Class A: [no user functions]

第一步,初始化对象变量。通过 zend_fetch_class() 获取存储类的变量,然后调用 zend_objects_new() (代码位于Zend/zend_objects.c) 函数为对象分配内存空间。

ZEND_API zend_object *zend_objects_new(zend_class_entry *ce)

{

zend_object *object = emalloc(sizeof(zend_object) + zend_object_properties_size(ce));

zend_object_std_init(object, ce);

object->handlers = &std_object_handlers;

return object;

}

第二步,调用类定义的构造函数,其调用和其它的函数调用是一样。

对象方法调用的过程是怎样的

我们来看下对象在调用方法的时候的处理逻辑:

<?php

class A {

public function do() {

echo ’test’;

}

}

$a = new A();

$a->do();

同样使用 VLD 查看产生的 OPCODE:

Finding entry points

Branch analysis from position: 0

Jump found. Position 1 = -2

filename: /Users/joshua/Programs/local/test/php/opcode/object.php function name: (null)

number of ops: 7

compiled vars: !0 = $a

line #* E I O op fetch ext return operands

-------------------------------------------------------------------------------------

3 0 E > NOP

10 1 NEW $2 :-6

2 DO_FCALL 0

3 ASSIGN !0, $2

11 4 INIT_METHOD_CALL !0, ’do’

5 DO_FCALL 0

6 > RETURN 1

branch: # 0; line: 3- 11; sop: 0; eop: 6; out1: -2

path #1: 0,

Class A:

Function do:

Finding entry points

// 省略调用对象方法产生的 OPCODE

...

可以看到相对于调用函数,在调用对象方法之前,会执行 ZEND_INIT_METHOD_CALL 操作。这个操作主要为了处理 __call() 和__callStatic() 魔术方法,以及验证方法的调用权限,以及是否为静态调用等。

对象的方法调用与非对象的方法调用基本无异,唯一的区别在于可以使用 $this 变量获取到当前所在对象。因此,在ZEND_INIT_METHOD_CALL 操作中,最后初始化调用栈的时候会将当前对象传入。

static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)

{

...

call = zend_vm_stack_push_call_frame(call_info,

fbc, opline->extended_value, called_scope, obj);

call->prev_execute_data = EX(call);

EX(call) = call;

ZEND_VM_NEXT_OPCODE();

}

最终在 Zend/zend_execute.h 文件的 zend_vm_init_call_frame() 的函数被设置到调用上下文数据中,供后续流程使用。

static zend_always_inline void zend_vm_init_call_frame(zend_execute_data *call, uint32_t call_info, zend_function *func, uint32_t num_args, zend_class_entry *called_scope, zend_object *object)

{

call->func = func;

if (object) {

Z_OBJ(call->This) = object;

ZEND_SET_CALL_INFO(call, 1, call_info);

} else {

Z_CE(call->This) = called_scope;

ZEND_SET_CALL_INFO(call, 0, call_info);

}

ZEND_CALL_NUM_ARGS(call) = num_args;

}

结语

PHP 向我们展示了,如何用面向过程的语言实现一门支持类与对象的语言。其设计的思路和方法,大有我们学习和参考的价值,有兴趣的同学可以就感兴趣的问题再深入挖掘,也欢迎大家和我分享交流。

来源:≈正念≈

时间: 2024-08-13 21:48:53

Php内核学习之类与对象详解的相关文章

JavaWeb学习(三)----JSP内置对象详解

[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4065790.html 联系方式:[email protected] [系列]JSP学习系列文章:(持续更新) JavaWeb学习(一)----JSP简介及入门(含Tomcat的使用) JavaWeb学习(二)----JSP脚本元素.指令元素.动作元素 JavaWeb学习(三)----JSP内置对象

Linux学习总结之LVM2详解

大纲: 简介 版本 LVM基本术语 LVM模块 具体操作 对添加的硬盘进行分区( fdisk /dev/[hs]d[a-z] ) 对创建的分区创建物理卷(pvcreate) 给逻辑卷创建逻辑容器(卷组) 在卷组创建大小不同的逻辑卷(lvcreate) 给已存在的卷组扩大容量 实现在线扩大LVM容量 实现缩减LVM容量(不支持在线缩减) 减小卷组容量 利用给LVM创建快照,并完成备份并还原 简介: LVM是Logical Volume Manager(逻辑卷管理器)的简写,又译为逻辑卷宗管理器.逻

jQuery的deferred对象详解(转)

jQuery的开发速度很快,几乎每半年一个大版本,每两个月一个小版本. 每个版本都会引入一些新功能.今天我想介绍的,就是从jQuery 1.5.0版本开始引入的一个新功能----deferred对象. 这个功能很重要,未来将成为jQuery的核心方法,它彻底改变了如何在jQuery中使用ajax.为了实现它,jQuery的全部ajax代码都被改写了.但是,它比较抽象,初学者很难掌握,网上的教程也不多.所以,我把自己的学习笔记整理出来了,希望对大家有用. 本文不是初级教程,针对的读者是那些已经具备

J2EE学习篇之--JDBC详解

今天我们来说一下关于JDBC的相关知识,关于JDBC我想大家都不陌生了,而且我记得早就开始使用它了,记得那是大二的时候做课程设计,但是那时候是为了完成任务,所以遇到问题就google,那时候也没有时间去整理,所以这次就来详细说一下关于JDBC的知识 摘要: JDBC(Java Data Base Connectivity,java数据库连接),由一些接口和类构成的API. J2SE的一部分,由java.sql,javax.sql包组成. 应用程序.JDBC API.数据库驱动及数据库之间的关系

jQuery的deferred对象详解(转)

jQuery的开发速度很快,几乎每半年一个大版本,每两个月一个小版本. 每个版本都会引入一些新功能.今天我想介绍的,就是从jQuery 1.5.0版本开始引入的一个新功能----deferred对象. 这个功能很重要,未来将成为jQuery的核心方法,它彻底改变了如何在jQuery中使用ajax.为了实现它,jQuery的全部ajax代码都被改写了.但是,它比较抽象,初学者很难掌握,网上的教程也不多.所以,我把自己的学习笔记整理出来了,希望对大家有用. 本文不是初级教程,针对的读者是那些已经具备

jQuery的deferred对象详解(一)

最近一段时间,都在研究jquery里面的$.Deffered对象,几天都搞不明白,其中源码的运行机制,网上查找了相关的资料,<jQuery的deferred对象详解>阮一峰老师的文章,里面阐述deferred讲的非常清楚,也让我大彻大悟,为了以后能很好的查阅,现将阮老师的文字转载过来. 一.什么是deferred对象? 开发网站的过程中,我们经常遇到某些耗时很长的javascript操作.其中,既有异步的操作(比如ajax读取服务器数据),也有同步的操作(比如遍历一个大型数组),它们都不是立即

JavaFX学习之道:详解JavaFX架构与框架

JavaFX 2.0平台是基于Java技术的富客户端平台.它使应用程序开发者更加容易的开发和部署跨平台的富互联网应用(RIA).JavaFX 2.0文档包含了JavaFX 2.0所提供的功能的概述. 图1描述了JavaFX 2.0平台的架构组件.后面的部分将对每一个组件进行逐一的描述.在JavaFX通用API的下面是用来运行JavaFX代码的引擎.这个引擎包括以下子组件:JavaFX高性能图形引擎(Prism);新的更小但更有效率的窗体系统(Glass);媒体引擎和Web引擎.虽然这些组件不是包

dom对象详解--document对象(二)

   dom对象详解--style对象 style对象 style对象和document对象下的集合对象styleSheets有关系,styleSheets是文档中所有style对象的集合,这里讲解的重点是style对象,styleSheets不是重点. style对象定义:Represents the current settings of all possible inline styles for agiven element,即表示当前元素的样式设置. 例,可拖动的窗口 <!DOCTYP

JS的window对象详解

一.说明 他是JS中最大的对象,它描述的是一个浏览器窗口,一般要引用他的属性和方法时,不需要用"Window.XXX"这种形式,而是直接使用"XXX".一个框架页面也是一个窗口. 二.Window窗口对象有如下属性 1.name 窗口的名称,由打开它的连接(<a target="...">)或框架页(<frame name="...">)或某一个窗口调用的 open() 方法(见下)决定.一般我们不会用