您的位置:首页 > 编程语言 > PHP开发

深入理解PHP原理之对象(一)

2020-02-15 00:00 3331 查看

在PHP4以前, PHP并不支持面向对象, 到PHP4的时候, PHP引入了一些OOP的关键字, 请注意我用的”关键字”, 因为在PHP4中的对象, 不过就是一个数组(属性)加上一个函数数组(方法), 没有访问权限控制, 没有析构函数(当然可以模拟), 等等.
到PHP5以后, 随着Zend Engine 2的发布:

1. 访问权限控制
2. 接口的引入
3. 魔术方法(PHP4中可以通过overload来有限模拟)
4. 接口的应用
5. 内置接口
等等.

PHP5终于可以算是较完美的支持面向对象了.
而这些看似复杂的实现, 在根本上, 还是没有脱离属性数组+方法数组的基本, 接下来我就为大家揭开隐藏在源代码中的秘密.
对象的结构
在PHP5中, 一个对象, 还是以一个zval做为载体的, 还记得什么是Zval么(深入理解PHP原理之变量).

typedef union _zvalue_value {
long lval;
double dval;
struct {
char *val;
int len;
} str;
HashTable *ht;
zend_object_value obj;
} zvalue_value;

如果, 一个zval是对象, 那么zvalue_value中的obj, 就指向一个zend_object_value的实例.
一个zend_object_value包含俩个成员, 一个是标识符(整形序号), 表明了当前对象存储在全局对象列表的位置, 另外还有一个zend_object_handlers指针, 指向当前对象所属类的handlers(标准操作集合).
真正的对象实体, zend_object中, 保存了如下的关键信息入口:

1. ce, zend_class_entry 类入口
2. properties, hashTable 普通属性集

对象的属性
如上所述, 普通属性是一个的hashTable, 在PHP5以后, 引入了访问权限控制, 而访问权限属性, 是通过属性名进行区分的(为此Zend引入了zend_mangle_property_name).

1. public  属性名
2. private \0类名\0属性名
3. protected \0*\0属性名

PHP通过这种比较ugly但是简单高效的方法, 实现了对属性访问权限的标识.知道了这个, 我们就可以干一些不合常理的事请, 比如访问对象的私有/保护属性(见: Bug #44273 access to private and protected class variables allowed when casting to array):

class Foo {
private $_name = "laruence";
protected $_age = 28;
}
$foo = new Foo();
$arr = (array) $foo;
var_dump($arr["\0Foo\0_name"]);
var_dump($arr["\0*\0_age"]);
//output:
string(8) "laruence"
int(28)

既然我说到了普通属性, 那么也就有不普通的属性:Static/Constant, 因为静态属性和常量属性都是和类相关而不是和对象相关的, 那么想到然的, 这些属性也就应该保存在类的结构中, 也就是zend_class_entry中. 对象的方法同理也是和类绑定的.
在zend_class_entry, 有如下HashTable几个成员,

1. function_table, 方法集
2. default_static_members, 静态属性
3. default_properties, 默认的属性
4. constants_table, 常量表

属性如其名, 对象类型的属性, 存放在对象的成员中.
对象的方法
在zend_class_entry中的function_table是个hashTable, 这个函数表的结构和普通的函数表一样(深入理解PHP之函数), 也是以zend_op_array为最终载体, 所以和普通函数一样, 方法也是不区分大小写的, 也是可以附加arg_info的. 而不同的则是, 函数的访问权限则由zend_op_array的fn_flag属性表明.
我们知道, 在PHP内的函数, 都有统一的参数列表(遵守PHP函数约定开发的前提下),

#define INTERNAL_FUNCTION_PARAM_PASSTHRU ht, return_value, return_value_ptr, this_ptr, return_value_used TSRMLS_CC

在调用方法的时候, this关键字由函数参数中的this_ptr指明, 而对于用户定义的函数, this关键字则有Zend VM保证.
说到这里, 举个列子:
如下代码, 会得到Fatal

<?php
class Foo {
public function Say() {
$this = NULL;
}
}
?>
//output:
PHP Fatal error:  Cannot re-assign $this in **

这是一个初级的保护措施, 防止this关键字被改写, 难道PHP就仅仅是做了这个保护? 不然, 让我们绕过这个保护措施看看, 如下:

<?php
class Foo {
public $id  = "laruence";
function Say($arr) {
extract($arr, EXTR_OVERWRITE);
var_dump($this);
var_dump($this->id);
}
}
$a = new Foo();
$a->sAY(array("this" => NULL)); //只是未来说明方法名不区分大小写.
?>
//output:
NULL
string(8) "laruence"

可见this关键字, 并不是简单的符号表中的item. 是在语法分析阶段, 就由ZEND Engine保证其正确性的, 并Attach在函数的结构体中.
对象的标准操作
我们对对象的操作, 比如获取属性, 设置属性, 获取对象的类, 等等, 这些常用的方法, 都是实现在对象的标准方法中的,PHP5中, 提供了23个标准方法.
在zend_object_value中的handlers指针, 就指向类常用操作的方法集合, 默认的, 这个指针指向:

ZEND_API zend_object_handlers std_object_handlers = {
zend_objects_store_add_ref,             /* add_ref */
zend_objects_store_del_ref,             /* del_ref */
zend_objects_clone_obj,                 /* clone_obj */
zend_std_read_property,                 /* read_property */
zend_std_write_property,                /* write_property */
zend_std_read_dimension,                /* read_dimension */
zend_std_write_dimension,               /* write_dimension */
zend_std_get_property_ptr_ptr,          /* get_property_ptr_ptr */
NULL,                                   /* get */
NULL,                                   /* set */
zend_std_has_property,                  /* has_property */
zend_std_unset_property,                /* unset_property */
zend_std_has_dimension,                 /* has_dimension */
zend_std_unset_dimension,               /* unset_dimension */
zend_std_get_properties,                /* get_properties */
zend_std_get_method,                    /* get_method */
NULL,                                   /* call_method */
zend_std_get_constructor,               /* get_constructor */
zend_std_object_get_class,              /* get_class_entry */
zend_std_object_get_class_name,         /* get_class_name */
zend_std_compare_objects,               /* compare_objects */
zend_std_cast_object_tostring,          /* cast_object */
NULL,                                   /* count_elements */
};

默认的, 也是绝大多数的时候, 都是如上的这些标准方法.
需要指明的是, _dimension后缀的方法, 指的是通过数组($obj[‘name’])方式访问对象的属性.
魔术方法
魔术方法定义在class_entry中,

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 *__tostring;
union _zend_function *serialize_func;
union _zend_function *unserialize_func;

魔术方法没有默认值, 在类定义的时刻指定.
对于一些魔术方法, 比如__get/__set是被标准操作发起调用的, 所以如果我们自己编写扩展中定义的类, 如果不使用标准方法, 那么也需要在适当的时机, 主动调用这些魔术方法.

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息