Home  >  Article  >  Backend Development  >  [Translation][php extension development and embedded] Chapter 11 -php5 objects

[Translation][php extension development and embedded] Chapter 11 -php5 objects

黄舟
黄舟Original
2017-02-10 10:12:061487browse


php5 objects

##It’s a bit confusing to compare php5 objects with their predecessors, php4 objects. Fair enough, but the API functions used by php5 objects are still built according to the API of php4. If you have read Chapter 10 "php4 Objects", you will be somewhat familiar with the content of this chapter. Before starting this chapter, you can like Chapter 10 Same as the beginning, rename the extension to sample3 and clean up the redundant code, leaving only the skeleton code of the extension.

Evolutionary History

There are two key components in php5 object variables. The first is a numerical identifier, which is very similar to the numerical resource ID introduced in Chapter 9 "Resource Data Type", and plays a role used in The role of the key to find the object instance in the corresponding table. The elements in this instance table contain references to zend_class_entry and the internal attribute table.

The second element is the object variable A handle table that allows you to customize how the Zend Engine handles instances. You will see this handle table later in this chapter.

zend_class_entry

A class entry is an internal representation of a class you define in user space. As you saw in the previous chapter, this structure is initialized by calling INIT_CLASS_ENTRY() with the class name and its function table. Next Use zend_register_internal_class() to register in the MINIT phase.

zend_class_entry *php_sample3_sc_entry;
#define PHP_SAMPLE3_SC_NAME "Sample3_SecondClass"
static function_entry php_sample3_sc_functions[] = {
    { NULL, NULL, NULL }
};

PHP_MINIT_FUNCTION(sample3)
{
    zend_class_entry ce;
    INIT_CLASS_ENTRY(ce, PHP_SAMPLE3_SC_NAME,
                            php_sample3_sc_functions);
    php_sample3_sc_entry =
                zend_register_internal_class(&ce TSRMLS_CC);
    return SUCCESS;
}

Method

If you have read the previous chapter, You might be thinking "Looks pretty much the same so far?", and so far, you'd be right. Now let's start defining some object methods. You'll start to see some very definite and welcome differences.

PHP_METHOD(Sample3_SecondClass, helloWorld)
{
    php_printf("Hello World\n");
}

The PHP_METHOD() macro was introduced in Zend Engine 2. It is an encapsulation of the PHP_FUNCTION() macro. It combines the class name and method name without manually defining the method like in php4. By using this macro, the namespace resolution specification of your code in the extension is consistent with that of other maintainers' code.

Definition

Define the implementation of a method, just like other functions, except that it is connected to the function table of the class. In addition to the PHP_METHOD() macro used for implementation, there are some new Macros can be used in the definition of function lists.

  • PHP_ME(classname, methodname, arg_info, flags)

Compared with the PHP_FE() macro introduced in Chapter 5 "Your First Extension", PHP_ME() adds a classname parameter and a flags parameter at the end (used to provide public, protected , private, static and other access controls, as well as abstract and other options). For example, to define the helloWorld method, you can define it as follows:

PHP_ME(Sample3_SecondClass,helloWorld,NULL,ZEND_ACC_PUBLIC)

  • PHP_MALIAS(classname, name, alias, arg_info, flags)

is very similar to the PHP_FALIAS() macro. This macro allows you to give the method described by the alias parameter (in the same class ) implementation provides a new name specified by name. For example, to copy your helloWorld method, you can define it as follows

PHP_MALIAS(Sample3_SecondClass, sayHi, helloWorld,
                            NULL, ZEND_ACC_PUBLIC)

  • PHP_ABSTRACT_ME(classname, methodname, arg_info)

内部类中的抽象方法很像用户空间的抽象方法. 在父类中它只是一个占位符, 期望它的子类提供真正的实现. 你将在接口一节中使用这个宏, 接口是一种特殊的class_entry.

  • PHP_ME_MAPPING(methodname, functionname, arg_info)

最后一种方法定义的宏是针对同时暴露OOP和非OOP接口的扩展(比如mysqli既有过程化的mysqli_query(), 也有面向对象的MySQLite::query(), 它们都使用了相同的实现.)的. 假定你已经有了一个过程化函数, 比如第5章写的sample_hello_world(), 你就可以使用这个宏以下面的方式将它附加为一个类的方法(要注意, 映射的方法总是public, 非static, 非final的):

PHP_ME_MAPPING(hello, sample_hello_world, NULL)

现在为止, 你看到的方法定义都使用了ZEND_ACC_PUBLIC作为它的flags参数. 实际上, 这个值可以是下面两张表的任意值的位域运算组合, 并且它还可以和本章后面"特殊方法"一节中要介绍的一个特殊方法标记使用位域运算组合.


类型标记

含义

ZEND_ACC_STATIC

方法可以静态调用.实际上,这就表示,方法如果通过实例调用, $this或者更确切的说this_ptr,并不会被设置到实例作用域中

ZEND_ACC_ABSTRACT

方法并不是真正的实现.当前方法应该在被直接调用之前被子类覆写.

ZEND_ACC_FINAL

##Methods cannot be overridden by subclasses



##ZEND_ACC_PROTECTEDCan only be called in a class or its subclassZEND_ACC_PRIVATEcan only be called in a class


For example, since the Sample3_SecondClass::helloWorld() method you defined earlier does not require an object instance, you can change its definition from simple Change ZEND_ACC_PUBLIC to ZEND_ACC_PUBLIC | ZEND_ACC_STATIC, so that the engine will not provide (instance) after knowing it.

Magic method

In addition to the magic methods of ZE1, ZE2 has added many new magic methods, as shown in the following table (or can be found at http://www.php.cn/)


#Visibility tag

Meaning

ZEND_ACC_PUBLIC

Can be called in any scope outside the object

.This is the same as the visibility of the php4 method

##__call($fname, $args)When calling undefined methods of an object, you can achieve beautiful results by using the __isset($varname )__unset($varname)__isset(), php 5.1 .0

Method

Usage

__construct(...)

Optional automatically called object constructor(The method previously defined is the same as the class name).If __construct() and classname()Both implementations exist,In the instantiation During the process, , will give priority to calling __construct()

# #__destruct()

When the instance leaves the scope,or requests the entire termination , will cause the __destruct() method of the instance to be implicitly called to handle some cleanup work, For example, closing a file or network handle.

__clone()

##By default,All instances are true pass-by-reference.In php5, If you want to truly copy an object instance, you must useclonekeyword.When the clone keyword is called on an object instance, The __clone() method will be executed implicitly,It allows the object to copy some required internal resource data.

##__toString()

When using text to represent an object

,For example, when using the echo or print statement directly on the object, The __toString() method will be automatically called by the engine.If the class implements this magic method,should return A string containing a description of the object's current state .

__get($var)

If the script requests an invisible attribute of an object(does not exist or is invisible due to access control )hour, __get()The magic method will be called,The only parameter is the requested attribute name.implementation You can use its own internal logic to determine the most reasonable return value to return .

__set($var , $value)

is very similar to __get(), __set()Provides the opposite ability,It is used to handle the logic when assigning invisible properties to objects. Implementations of __set() may choose to implicitly create these variables in standard attribute tables,set values using other storage mechanisms, Or throw an error directly and discard the value.

__call() magic method Processing.This method accepts two parameters:The name of the called method,Array containing the numeric indices of all arguments passed when calling .

php5.1.0After, the call of isset($obj->prop) is not only a check# Is there prop in ##$obj? , will also call __isset()method,Dynamic evaluation try to use dynamic Whether the __get() and __set() methods can successfully read and write properties

## Similar to

Introduces a simple OOP interface for the unset() function ,It can be used for object properties,Although this property may not exist in the object's standard property table, But it might make sense for the dynamic property space of __get() and __set(),Therefore, __unset() is introduced to solve this problem. ##


还有其他的魔术方法功能, 它们可以通过某些接口来使用, 比如ArrayAccess接口以及一些SPL接口.

在一个内部对象的实现中, 每个这样的"魔术方法"都可以和其他方法一样实现, 只要在对象的方法列表中正确的定义PHP_ME()以及PUBLIC访问修饰符即可.对于 __get(), __set(), __call(), __isset()以及__unset(), 它们要求传递参数, 你必须定义恰当的arg_info结构来指出方法需要一个或两个参数. 下面的代码片段展示了这些木梳函数的arg_info和它们对应的PHP_ME()条目:

static
    ZEND_BEGIN_ARG_INFO_EX(php_sample3_one_arg, 0, 0, 1)
    ZEND_END_ARG_INFO()
static
    ZEND_BEGIN_ARG_INFO_EX(php_sample3_two_args, 0, 0, 2)
    ZEND_END_ARG_INFO()
static function_entry php_sample3_sc_functions[] = {
    PHP_ME(Sample3_SecondClass, __construct, NULL,
                        ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
    PHP_ME(Sample3_SecondClass, __destruct, NULL,
                        ZEND_ACC_PUBLIC|ZEND_ACC_DTOR)
    PHP_ME(Sample3_SecondClass, __clone, NULL,
                        ZEND_ACC_PUBLIC|ZEND_ACC_CLONE)
    PHP_ME(Sample3_SecondClass, __toString, NULL,
                        ZEND_ACC_PUBLIC)
    PHP_ME(Sample3_SecondClass, __get, php_sample3_one_arg,
                        ZEND_ACC_PUBLIC)
    PHP_ME(Sample3_SecondClass, __set, php_sample3_two_args,
                        ZEND_ACC_PUBLIC)
    PHP_ME(Sample3_SecondClass, __call, php_sample3_two_args,
                        ZEND_ACC_PUBLIC)
    PHP_ME(Sample3_SecondClass, __isset, php_sample3_one_arg,
                        ZEND_ACC_PUBLIC)
    PHP_ME(Sample3_SecondClass, __unset, php_sample3_one_arg,
                        ZEND_ACC_PUBLIC)
    { NULL, NULL, NULL }
};

要注意__construct, __destruct, __clone使用位域运算符增加了额外的常量. 这三个访问修饰符对于方法而言是特殊的, 它们不能被用于其他地方.

属性

php5中对象属性的访问控制与方法的可见性有所不同. 在标准属性表中定义一个公开属性时, 就像你通常期望的, 你可以使用zend_hash_add()或add_property_*()族函数.

对于受保护的和私有的属性, 则需要使用新的ZEND_API函数:

void zend_mangle_property_name(char **dest, int *dest_length,
                        char *class, int class_length,
                        char *prop, int prop_length,
                        int persistent)

这个函数会分配一块新的内存, 构造一个"\0classname\0propname"格式的字符串. 如果类名是特定的类名, 比如Sample3_SecondClass, 则属性的可见性为private, 只能在Sample3_SecondClass对象实例内部可见.

如果类名指定为*, 则属性的可见性是protected, 它可以被对象实例所属类的所有祖先和后辈访问. 实际上, 属性可以以下面方式增加到对象上:

void php_sample3_addprops(zval *objvar)
{
    char *propname;
    int propname_len;
    /* public */
    add_property_long(objvar, "Chapter", 11);
    /* protected */
    zend_mangle_property_name(&propname, &propname_len,
        "*", 1, "Title", sizeof("Title")-1, 0);
    add_property_string_ex(objvar, propname, propname_len,
        "PHP5 Objects", 1 TSRMLS_CC);
    efree(propname);
    /* Private */
    zend_mangle_property_name(&propname, &propname_len,
        "Sample3_SecondClass",sizeof("Sample3_SecondClass")-1,
        "Section", sizeof("Section")-1, 0);
    add_property_string_ex(objvar, propname, propname_len,
        "Properties", 1 TSRMLS_CC);
    efree(propname);
}

通过_ex()版的add_property_*()族函数, 可以明确标记属性名的长度. 这是需要的, 因为在protected和private属性名中会包含NULL字节, 而strlen()认为NULL字节是字符串终止标记, 这样将导致属性名被认为是空. 要注意的是_ex()版本的add_property_*()函数还要求显式的传递TSRMLS_CC. 而通常它是通过宏扩展隐式的传递的.

定义类常量和定义类属性非常相似. 两者的关键不同点在于它们的持久性, 因为属性的生命周期是伴随的实例的, 它发生在请求中, 而常量是和类定义在一起的, 只能在MINIT阶段定义.

由于标准的zval *维护宏的函数假定了非持久性, 所以你需要手动写不少代码. 考虑下面的函数:

void php_sample3_register_constants(zend_class_entry *ce)
{
    zval *constval;


    /* 基本的标量值可以使用Z_*()去设置它们的值 */
    constval = pemalloc(sizeof(zval), 1);
    INIT_PZVAL(constval);
    ZVAL_DOUBLE(constval, 2.7182818284);
    zend_hash_add(&ce->constants_table, "E", sizeof("E"),
                    (void*)&constval, sizeof(zval*), NULL);

    /* 字符串需要额外的空间分配 */
    constval = pemalloc(sizeof(zval), 1);
    INIT_PZVAL(constval);
    Z_TYPE_P(constval) = IS_STRING;
    Z_STRLEN_P(constval) = sizeof("Hello World") - 1;
    Z_STRVAL_P(constval) = pemalloc(Z_STRLEN_P(constval)+1, 1);
    memcpy(Z_STRVAL_P(constval), "Hello World",
                            Z_STRLEN_P(constval) + 1);
    zend_hash_add(&ce->constants_table,
                    "GREETING", sizeof("GREETING"),
                    (void*)&constval, sizeof(zval*), NULL);


    /* Objects, Arrays, and Resources can't be constants */
}
PHP_MINIT_FUNCTION(sample3)
{
    zend_class_entry ce;
    INIT_CLASS_ENTRY(ce, PHP_SAMPLE3_SC_NAME,
                            php_sample3_sc_functions);
    php_sample3_sc_entry =
                zend_register_internal_class(&ce TSRMLS_CC);
    php_sample3_register_constants(php_sample3_sc_entry);
    return SUCCESS;
}

在这之下, 这些类常量就可以访问了, 分别是: Sample3_SecondClass::E和Sample3_SecondClass::GREETING.

接口

接口的定义和类的定义除了几个差异外基本一致. 首先是所有的方法都定义为抽象的, 这可以通过PHP_ABSTRACT_ME()宏来完成.

static function_entry php_sample3_iface_methods[] = {
    PHP_ABSTRACT_ME(Sample3_Interface, workerOne, NULL)
    PHP_ABSTRACT_ME(Sample3_Interface, workerTwo, NULL)
    PHP_ABSTRACT_ME(Sample3_Interface, workerThree, NULL)
    { NULL, NULL, NULL }
};

由于这些方法是抽象的, 所以不需要实现. 接下来的第二个差异就是注册. 和一个实际的类注册类似, 首先调用INIT_CLASS_ENTRY和zend_register_internal_class.

当类(zend_class_entry)可用时, 最后一部就是标记这个类是接口, 实现方法如下:

zend_class_entry *php_sample3_iface_entry;
PHP_MINIT_FUNCTION(sample3)
{
    zend_class_entry ce;
    INIT_CLASS_ENTRY(ce, "Sample3_Interface",
                        php_sample3_iface_methods);
    php_sample3_iface_entry =
                zend_register_internal_class(&ce TSRMLS_CC);
    php_sample3_iface_entry->ce_flags|= ZEND_ACC_INTERFACE;

实现接口

假设你想让Sample3_SecondClass这个类实现Sample3_Interface这个接口, 就需要实现这个接口定义的所有抽象方法:

PHP_METHOD(Sample3_SecondClass,workerOne)
{
    php_printf("Working Hard.\n");
}
PHP_METHOD(Sample3_SecondClass,workerTwo)
{
    php_printf("Hardly Working.\n");
}
PHP_METHOD(Sample3_SecondClass,workerThree)
{
    php_printf("Going wee-wee-wee all the way home.\n");
}

接着在php_sample3_sc_functions列表中定义它们:

PHP_ME(Sample3_SecondClass,workerOne,NULL,ZEND_ACC_PUBLIC)
PHP_ME(Sample3_SecondClass,workerTwo,NULL,ZEND_ACC_PUBLIC)
PHP_ME(Sample3_SecondClass,workerThree,NULL,ZEND_ACC_PUBLIC)

最后, 定义你新注册的类实现php_sample3_iface_entry接口:

PHP_MINIT_FUNCTION(sample3)
{
    zend_class_entry ce;
    /* 注册接口 */
    INIT_CLASS_ENTRY(ce, "Sample3_Interface",
                        php_sample3_iface_methods);

    php_sample3_iface_entry =
                zend_register_internal_class(&ce TSRMLS_CC);
    php_sample3_iface_entry->ce_flags|= ZEND_ACC_INTERFACE;
    /* 注册实现接口的类 */
    INIT_CLASS_ENTRY(ce, PHP_SAMPLE3_SC_NAME,
                            php_sample3_sc_functions);
    php_sample3_sc_entry =
                zend_register_internal_class(&ce TSRMLS_CC);
    php_sample3_register_constants(php_sample3_sc_entry);
    /* 声明实现关系 */
    zend_class_implements(php_sample3_sc_entry TSRMLS_CC,
                1, php_sample3_iface_entry);
    return SUCCESS;
}

如果Sample3_SecondClass实现了其他接口, 比如ArrayAccess, 就需要将对应的类(zend_class_entry)作为附加参数增加到zend_class_implements()调用中, 并将现在传递为数字1的参数值相应的增大为2:

zend_class_implements(php_sample3_sc_entry TSRMLS_CC,
            2, php_sample3_iface_entry, php_other_interface_entry);

句柄

ZE2并没有把所有的对象实例看做是相同的, 它为每个对象实例关联了句柄表. 当在一个对象上执行特定的操作时, 引擎调用执行对象的句柄表中自定义的行为.

标准句柄

默认情况下, 每个对象都被赋予了std_object_handlers这个内建句柄表. std_object_handlers中对应的句柄方法以及它们的行为定义如下:

  • void add_ref(zval *object TSRMLS_DC)

当对象值的refcount增加时被调用, 比如, 当一个对象变量赋值给新的变量时. add_ref和del_ref函数的默认行为都是调整内部对象存储的refcount.

  • void del_ref(zval *object TSRMLS_DC)

和add_ref类似, 这个方法也在修改refcount时调用, 通常是在unset()对象变量时发生的.

  • zend_object_value clone_obj(zval *object TSRMLS_DC)

用于利用已有的对象实例创建一个新的实例. 默认行为是创建一个新的对象实例, 将它和原来的句柄表关联, 拷贝属性表, 如果该对象的类定义了__clone()方法, 则调用它让新的对象执行一些附加的复制工作.

  • zval *read_property(zval *obj, zval *prop, int type TSRMLS_DC)

  • void write_property(zval *obj, zval *prop, zval *value TSRMLS_DC)

在用户空间尝试以$obj->prop方式访问, 去读写对象的属性时, read_property/write_property对应的被调用. 默认的处理是首先在标准属性表中查找属性. 如果属性没有定义, 则检查是否存在__get()或__set()魔术方法, 如果有则调用该方法.

  • zval **get_property_ptr_ptr(zval *obj, zval *value TSRMLS_DC)

get_property_ptr_ptr() is a variant of read_property(), which allows the calling scope to directly replace the current zval * with a new one. The default behavior is to return the standard property table The pointer address of the attribute in . If it does not exist, and there is no __get()/__set() magic method, the pointer is implicitly created and returned. If the __get() or __set() method exists, it will cause this handle to fail , causing the engine to instead rely on separate read_property and write_property calls.

  • ##zval *read_dimension(zval *obj, zval *idx, int type TSRMLS_DC)

  • void write_dimension(zval *obj, zval *idx, zval *value TSRMLS_DC)

read_dimension () and write_dimension() are similar to the corresponding read_property() and write_property(); but they are triggered when the object is accessed as an array using the $obj['idx'] method. If the object's class does not implement the ArrayAccess interface, the default The behavior is to trigger an error; otherwise it calls the magic methods offsetget($idx) or offsetset($idx, $value).

  • ##zval *get( zval *obj TSRMLS_DC)

  • void set(zval *obj, zval *value TSRMLS_DC)

When setting or retrieving the value of an object, the get() or set() method will be called on the object. The object itself is passed as the first parameter. For set, the new value is passed as the second parameter. ; Actually, these methods are used in arithmetic operations. There is no default handler for these operations.

    ##int has_property(zval *obj, zval *prop, int chk_type TSRMLS_DC)
  • #This handler is called when isset() is called on an object property. By default the standard processor will check the prop specified Attribute name. If this attribute is not found in PHP 5.1.0 and the __isset() method is defined, this method will be called. If the value of the chk_type parameter is 2, then only the attribute needs to exist. If the value of chk_type is 0, then Must exist and must not be a value of IS_NULL. If the chk_type value is 1, the attribute must exist and must be a value other than FALSE. Note: In PHP 5.0.x, The meaning of chk_type is consistent with the chk_type of has_dimension.

##int has_dimension(zval *obj, zval *idx, int chk_type TSRMLS_DC)
  • When calling isset() when treating an object as an array (such as isset($obj['idx'])), this processor is used. The default standard processor will check whether the object is implemented If the ArrayAccess interface is implemented, the offsetexists($idx) method is called. If not found (referring to calling offsetexists()), it is the same as if the offsetexists() method is not implemented, and 0 is returned. Otherwise, if chk_type is 0, return directly true(1). A chk_type of 1 indicates that it must call the offsetget($idx) method of the object and test the return value, Check whether the value is FALSE before returning TRUE(1).

void unset_property(zval *obj, zval *prop TSRMLS_DC)
  • void unset_dimension(zval *obj, zval *idx TSRMLS_DC)
  • These two methods when trying to unload object attributes ( or when applying unset() to an object in an array). The unset_property() handler either removes the property from the standard property table (if it exists), or attempts to call the implemented __unset($prop) method (php 5.1. 0), unset_dimension() calls the offsetunset($idx) method when the class implements ArrayAccess.

##HashTable *get_properties(zval *obj TSRMLS_DC)

  • This processor is actually called when the internal function uses the Z_OBJPROP() macro to read properties from the standard property table. The default processing of PHP objects The processor unpacks and returns Z_OBJ_P(object)->properties, which is the real standard property table.

##union _zend_function *get_method(zval **obj_ptr char *method_name, int methodname_len TSRMLS_DC)

This handler is called when parsing an object method in the class's function_table. If the method does not exist in the main function_table, the default handler returns a pointer to the object. _call($name, $args) method wrapped zend_function * pointer.

  • ##int call_method(char *method, INTERNAL_FUNCTION_PARAMETERS)

Function defined as ZEND_OVERLOADED_FUNCTION type will be executed as call_method processor. By default, this processor is undefined.

  • union _zend_function *get_constructor(zval *obj TSRMLS_DC)

Similar to the get_method() processor, this processor returns a corresponding object method Reference. The constructor in the zend_class_entry of the class is stored in a special way, which makes it special. Overriding of this method is very rare.

  • zend_class_entry * get_class_entry(zval *obj TSRMLS_DC)

Similar to get_constructor(), this processor is rarely overridden. Its purpose is to map an object instance Return to its original class definition.

  • ##int get_class_name(zval *object, char **name zend_uint *len, int parent TSRMLS_DC)

get_class_entry() is one step of get_class_name(). After getting the zend_object of the object, it will use the object’s class name or its parent class name (this depends on the value of the parameter parent ) returns a copy. The returned copy of the class name must use non-persistent storage (emalloc()).

  • int compare_objects(zval *obj1, zval * obj2 TSRMLS_DC)

When comparison operators (for example: ==, !=, 6ccbb8b0f4c668de47d964671fff91b7, >=) are used in When there are two objects, calling compare_objects() on the operands (the two objects participating in the comparison) is the first part of this work. Its return value is usually 1, 0, -1, which represents greater than, equal to, and less than respectively. By default, objects are compared based on their standard property tables, using the same comparison rules as the array comparison rules learned in Chapter 8, "Working with Arrays and HashTables".

  • int cast_object(zval *src, zval *dst, int type, int should_free TSRMLS_DC)

When trying to cast the object to other data type, this processor will be triggered. If should_free is set to a non-zero value, zval_dtor() will be called on dst, first releasing the internal resources. In short, the processor should try to represent the object in src as dst to Out of the type of zval *. This handler is undefined by default, but when it is present, it should return SUCCESS or FAILURE.

  • int count_elements (zval *obj, long *count TSRMLS_DC)

Objects that implement array access should define this processor, which will set the current number of elements to count and returns SUCCESS. If the current instance does not implement array access, it should return FAILURE to cause the engine to go back and check the standard attribute table.

Translation Note: The above handle table and The php-5.4.9 used by the translator is no longer completely consistent. When studying this part, readers can refer to the standard processor handle table at the bottom in Zend/zend_object_handlers.c.

Magic Method Part 2

使用前面看到的对象句柄表的自定义版本, 可以让内部类提供与在用户空间基于对象或类的__xxx()魔术方法相比, 相同或更多的能力.将这些自定义的句柄设置到对象实例上首先要求创建一个新的句柄表. 因为你通常不会覆写所有的句柄, 因此首先将标准句柄表拷贝到你的自定义句柄表中再去覆写你想要修改的句柄就很有意义了:

static zend_object_handlers php_sample3_obj_handlers;
int php_sample3_has_dimension(zval *obj, zval *idx,
                        int chk_type TSRMLS_DC)
{
    /* 仅在php版本>=1.0时使用 */
    if (chk_type == 0) {
       /* 重新映射chk_type的值 */
       chk_type = 2;
    }
    /* 当chk_type值为1时保持不变. 接着使用标准的hash_property方法执行逻辑 */
    return php_sample3_obj_handlers.has_property(obj,
                            idx, chk_type TSRMLS_CC);
}
PHP_MINIT_FUNCTION(sample3)
{
    zend_class_entry ce;
    zend_object_handlers *h = &php_sample3_obj_handlers;


    /* 注册接口 */
    INIT_CLASS_ENTRY(ce, "Sample3_Interface",
                        php_sample3_iface_methods);
    php_sample3_iface_entry =
                zend_register_internal_class(&ce TSRMLS_CC);
    php_sample3_iface_entry->ce_flags = ZEND_ACC_INTERFACE;
    /* 注册SecondClass类 */
    INIT_CLASS_ENTRY(ce, PHP_SAMPLE3_SC_NAME,
                            php_sample3_sc_functions);
    php_sample3_sc_entry =
                zend_register_internal_class(&ce TSRMLS_CC);
    php_sample3_register_constants(php_sample3_sc_entry);


    /* 实现AbstractClass接口 */
    zend_class_implements(php_sample3_sc_entry TSRMLS_CC,
                1, php_sample3_iface_entry);


    /* 创建自定义句柄表 */
    php_sample3_obj_handlers = *zend_get_std_object_handlers();


    /* 这个句柄表的目的是让$obj['foo']的行为等价于$obj->foo */
    h->read_dimension = h->read_property;
    h->write_dimension = h->write_property;
    h->unset_dimension = h->unset_property;
#if PHP_MAJOR_VERSION > 5 || \
            (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION > 0)
    /* php-5.1.0中, has_property和has_dimension的chk_type含义不同, 为使它们行为一致, 自己包装一个函数 */
    h->has_dimension = php_sample3_has_dimension;

#else
    /* php 5.0.x的has_property和has_dimension行为一致 */
    h->has_dimension = h->has_property;
#endif


    return SUCCESS;
}

要将这个句柄表应用到对象上, 你有两种选择. 最简单也是最具代表性的就是实现一个构造器方法, 并在其中重新赋值变量的句柄表.

PHP_METHOD(Sample3_SecondClass,__construct)
{
    zval *objptr = getThis();


    if (!objptr) {
        php_error_docref(NULL TSRMLS_CC, E_WARNING,
                    "Constructor called statically!");
        RETURN_FALSE;
    }
    /* 执行正常的构造器任务... */
    /* 覆写句柄表 */
    Z_OBJ_HT_P(objptr) = &php_sample3_obj_handlers;
}

当构造器返回时, 对象就有了新的句柄表以及对应的自定义行为. 还有一种更加受欢迎的方法是覆写类的对象创建函数.

zend_object_value php_sample3_sc_create(zend_class_entry *ce
                                        TSRMLS_DC)
{
    zend_object *object;
    zend_object_value retval;


    /* 返回Zend创建的对象 */
    retval = zend_objects_new(&object, ce TSRMLS_CC);
    /* 覆写create_object时, 属性表必须手动初始化 */
    ALLOC_HASHTABLE(object->properties);
    zend_hash_init(object->properties, 0, NULL,
                                    ZVAL_PTR_DTOR, 0);
    /* 覆写默认句柄表 */
    retval.handlers = &php_sample3_obj_handlers;
    /* 这里可能会执行其他对象初始化工作 */
    return retval;
}

这样就可以在MINIT阶段注册类(zend_class_entry)之后直接将自定义句柄表附加上去.

INIT_CLASS_ENTRY(ce, PHP_SAMPLE3_SC_NAME,
                        php_sample3_sc_functions);
php_sample3_sc_entry =
            zend_register_internal_class(&ce TSRMLS_CC);
php_sample3_sc_entry->create_object= php_sample3_sc_create;
php_sample3_register_constants(php_sample3_sc_entry);
zend_class_implements(php_sample3_sc_entry TSRMLS_CC,
            1, php_sample3_iface_entry);

这两种方法唯一可预见的不同是它们发生的时机不同. 引擎在碰到new Sample3_SecondClass后会在处理构造器及它的参数之前调用create_object. 通常, 你计划覆盖的各个点使用的方法(create_object Vs. __construct)应该一致.

译注: php-5.4.9中, xxx_property/xxx_dimension这一组句柄的原型是不一致的, 因此, 按照原著中的示例, 直接将xxx_property/xxx_dimension进行映射已经不能工作, 要完成上面的功能, 需要对4个句柄均包装一个函数去映射. 由于译者没有详细跟踪具体在哪一个版本发生了这些改变, 因此这里不给出译者测试的示例(没有做兼容性处理检查), 如果读者碰到这个问题, 请检查自己所使用php版本中两组句柄原型的差异并进行相应修正.

小结

毋庸置疑, php5/ZE2的对象模型比它的前辈php4/ZE1中的对象模型更加复杂. 在看完本章中介绍的所有特性和实现细节后, 你可能已经被它的所包含的信息量搞得手足无措. 幸运的是, php中在OOP之上有一层可以让你选择你的任务所需的部分而不关心其他部分. 找到复杂性之上一个舒适的层级开始工作, 剩下的都会顺起来的.

现在已经看完了所有的php内部数据类型, 是时候回到之前的主题了: 请求生命周期. 接下来的两章, 将在你的扩展中使用线程安全全局变量增加内部状态, 定义自定义的ini设置, 定义常量, 以及向使用你扩展的用户空间脚本提供超级全局变量.

以上就是[翻译][php扩展开发和嵌入式]第11章-php5对象的内容,更多相关内容请关注PHP中文网(www.php.cn)!


Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn