Press "Enter" to skip to content

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

16 Comments

  1. Buy RDP
    Buy RDP March 5, 2022

    Awesome website! We should never lose our hopes.

  2. Gumagawa Ako
    Gumagawa Ako July 17, 2019

    The content is tasteful, your authored subject matter stylish.

  3. Anonymous
    Anonymous October 25, 2017

    第三方

  4. Jugos10.Net
    Jugos10.Net August 12, 2017

    Hola
    Estuve leyendo tu redacción y hay demasiadas cosas que no sabía
    que me has aclarado, esta maravilloso.. te quería reconocer el espacio que dedicaste, con unas infinitas gracias, por instruir a personas como yo jojojo.
    Besos

  5. 33Kellye
    33Kellye May 17, 2017

    I must say it was hard to find your page in search results.
    You write interesting posts but you should rank your blog higher
    in search engines. If you don’t know how to do it search on youtube:
    how to rank a website Marcel’s way

  6. shanwxu
    shanwxu September 12, 2012

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

  7. hit9
    hit9 June 5, 2012

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

  8. phper
    phper November 7, 2011

    路过~~~~~~~!

  9. aureole
    aureole October 25, 2011

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

  10. abcasus
    abcasus June 18, 2010

    辛苦。

  11. phppan
    phppan May 20, 2010

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

Comments are closed.