msgbartop
PHP语言, PHP扩展, Zend引擎相关的研究,技术,新闻分享 – 左手代码 右手诗
msgbarbottom

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

在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是被标准操作发起调用的, 所以如果我们自己编写扩展中定义的类, 如果不使用标准方法, 那么也需要在适当的时机, 主动调用这些魔术方法.


分享到:



Related Posts:

Tags: , , ,

9 Responses to “深入理解PHP原理之对象(一)”

  1. shanwxu |

    想知道鸟哥用的是什么编辑器啊,看起来很舒服

  2. hit9 |

    您好!请问zend_std_call_method中的zend_literal类型是什么啊.

  3. phper |

    路过~~~~~~~!

  4. aureole |

    大神,我在你博客你面扎营啦哈哈。
    PHP对象原来是这回事……呵呵

  5. abcasus |

    辛苦。

  6. 深入理解 PHP之require/include顺序 « 大熊猫 – konakona ——PHP程序员 |

    [...] 本文地址: http://www.laruence.com/2010/05/18/1482.html [...]

  7. xingai |

    xingai再次学习了!

  8. phppan |

    之前由于一个纠结的私有变量的问题而看过php中类实现的一些代码,但也是粗略了解,
    今天拜读鸟哥的文章,再次学习了!

  9. Tweets that mention 深入理解PHP原理之对象(一) | 风雪之隅 -- Topsy.com |

    [...] This post was mentioned on Twitter by 姚东旭. 姚东旭 said: [推荐] 深入理解PHP原理之对象(一) http://goo.gl/gUMh [...]

Leave a Reply

*