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

  • 本文地址: http://www.laruence.com/2010/05/18/1482.html
  • 转载请注明出处

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

到PHP5以后, 随着Zend Engine 2的发布:

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

PHP5终于可以算是较完美的支持面向对象了.

而这些看似复杂的实现, 在根本上, 还是没有脱离属性数组+方法数组的基本, 接下来我就为大家揭开隐藏在源代码中的秘密.

对象的结构

在PHP5中, 一个对象, 还是以一个zval做为载体的, 还记得什么是Zval么(深入理解PHP原理之变量).

 
  1. typedef union _zvalue_value {
  2.     long lval;
  3.     double dval;
  4.     struct {
  5.         char *val;
  6.         int len;
  7.     } str;
  8.     HashTable *ht;
  9.     zend_object_value obj;
  10. } zvalue_value;

如果, 一个zval是对象, 那么zvalue_value中的obj, 就指向一个zend_object_value的实例.

一个zend_object_value包含俩个成员, 一个是标识符(整形序号), 表明了当前对象存储在全局对象列表的位置, 另外还有一个zend_object_handlers指针, 指向当前对象所属类的handlers(标准操作集合).

真正的对象实体, zend_object中, 保存了如下的关键信息入口:

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

对象的属性

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

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

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

 
  1. class Foo {
  2.     private $_name = "laruence";
  3.     protected $_age = 28;
  4. }
  5. $foo = new Foo();
  6. $arr = (array) $foo;
  7. var_dump($arr["\0Foo\0_name"]);
  8. var_dump($arr["\0*\0_age"]);
  9. //output:
  10. string(8) "laruence"
  11. int(28)

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

在zend_class_entry, 有如下HashTable几个成员,

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

属性如其名, 对象类型的属性, 存放在对象的成员中.

对象的方法

在zend_class_entry中的function_table是个hashTable, 这个函数表的结构和普通的函数表一样(深入理解PHP之函数), 也是以zend_op_array为最终载体, 所以和普通函数一样, 方法也是不区分大小写的, 也是可以附加arg_info的. 而不同的则是, 函数的访问权限则由zend_op_array的fn_flag属性表明.

我们知道, 在PHP内的函数, 都有统一的参数列表(遵守PHP函数约定开发的前提下),

 
  1. #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

 
  1. class Foo {
  2.    public function Say() {
  3.       $this = NULL;
  4.    }
  5. }
  6. ?>
  7. //output:
  8. PHP Fatal error: Cannot re-assign $this in **

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

 
  1. class Foo {
  2.     public $id = "laruence";
  3.     function Say($arr) {
  4.         extract($arr, EXTR_OVERWRITE);
  5.         var_dump($this);
  6.         var_dump($this->id);
  7.     }
  8. }
  9. $a = new Foo();
  10.  
  11. $a->sAY(array("this" => NULL)); //只是未来说明方法名不区分大小写.
  12. ?>
  13. //output:
  14. NULL
  15. string(8) "laruence"

可见this关键字, 并不是简单的符号表中的item. 是在语法分析阶段, 就由ZEND Engine保证其正确性的, 并Attach在函数的结构体中.

对象的标准操作
我们对对象的操作, 比如获取属性, 设置属性, 获取对象的类, 等等, 这些常用的方法, 都是实现在对象的标准方法中的,PHP5中, 提供了23个标准方法.

在zend_object_value中的handlers指针, 就指向类常用操作的方法集合, 默认的, 这个指针指向:

 
  1. ZEND_API zend_object_handlers std_object_handlers = {
  2.     zend_objects_store_add_ref, /* add_ref */
  3.     zend_objects_store_del_ref, /* del_ref */
  4.     zend_objects_clone_obj, /* clone_obj */
  5.  
  6.     zend_std_read_property, /* read_property */
  7.     zend_std_write_property, /* write_property */
  8.     zend_std_read_dimension, /* read_dimension */
  9.     zend_std_write_dimension, /* write_dimension */
  10.     zend_std_get_property_ptr_ptr, /* get_property_ptr_ptr */
  11.     NULL, /* get */
  12.     NULL, /* set */
  13.     zend_std_has_property, /* has_property */
  14.     zend_std_unset_property, /* unset_property */
  15.     zend_std_has_dimension, /* has_dimension */
  16.     zend_std_unset_dimension, /* unset_dimension */
  17.     zend_std_get_properties, /* get_properties */
  18.     zend_std_get_method, /* get_method */
  19.     NULL, /* call_method */
  20.     zend_std_get_constructor, /* get_constructor */
  21.     zend_std_object_get_class, /* get_class_entry */
  22.     zend_std_object_get_class_name, /* get_class_name */
  23.     zend_std_compare_objects, /* compare_objects */
  24.     zend_std_cast_object_tostring, /* cast_object */
  25.     NULL, /* count_elements */
  26. };

默认的, 也是绝大多数的时候, 都是如上的这些标准方法.

需要指明的是, _dimension后缀的方法, 指的是通过数组($obj[‘name’])方式访问对象的属性.

魔术方法

魔术方法定义在class_entry中,

 
  1. union _zend_function *constructor;
  2. union _zend_function *destructor;
  3. union _zend_function *clone;
  4. union _zend_function *__get;
  5. union _zend_function *__set;
  6. union _zend_function *__unset;
  7. union _zend_function *__isset;
  8. union _zend_function *__call;
  9. union _zend_function *__tostring;
  10. union _zend_function *serialize_func;
  11. union _zend_function *unserialize_func;

魔术方法没有默认值, 在类定义的时刻指定.

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

 

Related Posts

  • 深入理解PHP原理之变量生命期(一)

  • PHP stream未能及时清理现场导致Core的bug

  • 扩展PHP[Extending PHP](一)

  • PHP Reflection Extension的一个bug

  • PHP RFC: 让PHP的foreach支持list

Filed in PHP源码分析

你可能感兴趣的:(【PHP】)