首頁 後端開發 php教程 [翻譯][php擴展開發與嵌入式]第10章-php4的對象

[翻譯][php擴展開發與嵌入式]第10章-php4的對象

Feb 09, 2017 pm 01:17 PM

php4的物件

曾幾何時, 在很早的版本中, php還不支援任何的物件導向程式設計語法. 在php4中引入了Zend引擎(ZE1), 出現了幾個新的特性, 其中就包括物件數據類型.

php物件類型的演化

第一次的物件導向程式設計(OOP)支援僅實現了物件關聯的語義. 用一個php核心開發者的話來說就是"php4的物件只是將一個陣列和一些方法綁定到了一起". 它就是現在你要研究的php對象.

Zend引擎(ZE2)的第二個大版本發布是在php5中, 在php的OOP實現中引入了一些新的特性. 例如, 屬性和方法可以使用訪問修飾符標記它們在你的類定義外面的可見性, 函數的重載可以用來定義內部語言結構的自定義行為, 在多個類的調用鏈之間可以使用接口實施API標準化. 在你學習到第11章"php5物件"時, 你將透過在php5的類別定義中實現這些特性來建立對這些知識的認知.

實現類

在進入OOP的世界之前,我們需要輕裝上陣. 因此, 請將你的擴展恢復到第5章"你的第一個擴展"中剛剛搭建好的骨架形態.

為了和你原有的習作獨立, 你可以將這個版本命名為sample2. 將下面的三個檔案放入到你php原始碼的ext/sample2目錄下:

config.m4

PHP_ARG_ENABLE(sample2,  
  [Whether to enable the "sample2" extension],  
  [  enable-sample2       Enable "sample2" extension support])  
  
if test $PHP_SAMPLE2 != "no"; then  
  PHP_SUBST(SAMPLE2_SHARED_LIBADD)  
  PHP_NEW_EXTENSION(sample2, sample2.c, $ext_shared)  
fi
登入後複製

php_saple2.h

#ifndef PHP_SAMPLE2_H  
/* Prevent double inclusion */  
#define PHP_SAMPLE2_H  
  
/* Define Extension Properties */  
#define PHP_SAMPLE2_EXTNAME    "sample2"  
#define PHP_SAMPLE2_EXTVER    "1.0"  
  
/* Import configure options 
   when building outside of 
   the PHP source tree */  
#ifdef HAVE_CONFIG_H  
#include "config.h"  
#endif  
  
/* Include PHP Standard Header */  
#include "php.h"  
  
/* Define the entry point symbol 
 * Zend will use when loading this module 
 */  
extern zend_module_entry sample2_module_entry;  
#define phpext_sample2_ptr &sample2_module_entry  
  
#endif /* PHP_SAMPLE2_H */
登入後複製

sample2.c

#include "php_sample2.h"  
  
static function_entry php_sample2_functions[] = {  
    { NULL, NULL, NULL }  
};  
  
PHP_MINIT_FUNCTION(sample2)  
{  
    return SUCCESS;  
}  
  
zend_module_entry sample2_module_entry = {  
#if ZEND_MODULE_API_NO >= 20010901  
    STANDARD_MODULE_HEADER,  
#endif  
    PHP_SAMPLE2_EXTNAME,  
    php_sample2_functions,  
    PHP_MINIT(sample2),  
    NULL, /* MSHUTDOWN */  
    NULL, /* RINIT */  
    NULL, /* RSHUTDOWN */  
    NULL, /* MINFO */  
#if ZEND_MODULE_API_NO >= 20010901  
    PHP_SAMPLE2_EXTVER,  
#endif  
    STANDARD_MODULE_PROPERTIES  
};  
  
#ifdef COMPILE_DL_SAMPLE2  
ZEND_GET_MODULE(sample2)  
#endif
登入後複製

現在,像第55章時一樣, 你可以執行phpize, ./configure, make去構建你的sample2.so擴展模組.

你之前的config.w32做與這裡給出的config.m4一樣的修改也可以正常工作.

定義類別條目

在用戶空間中, 定義一個類別如下:

<?php  
class Sample2_FirstClass {  
}  
?>
登入後複製

毫無疑問, 你會猜到, 在擴展中實現它還是有一點難度的. 首先, 你需要在你的源代碼文件中, 像上一章定義int le_sample_descriptor一樣, 定義一個zend_class_entry指標:

zend_class_entry *php_sample2_firstclass_entry;
登入後複製

現在, 就可以在MINIT函數中初始化並註冊類別了.

PHP_MINIT_FUNCTION(sample2)  
{  
    zend_class_entry ce; /* 临时变量 */  
  
    /* 注册类 */  
    INIT_CLASS_ENTRY(ce, "Sample2_FirstClass", NULL);  
    php_sample2_firstclass_entry =  
            zend_register_internal_class(&ce TSRMLS_CC);  
  
    return SUCCESS;  
}
登入後複製

將建置這個擴充功能, 測試get_deced_ 測試get_deced_ Sample2_FirstClass現在在用戶空間可用了.

定義方法的實現

此刻, 你實現的只是一個stdClass, 當然它是可用的. 但實際上你是希望你的類可以做一些事情的.

要達成這個目的, 你就需要回到第5章學到的另外一個知識點了. 將傳遞給INIT_CLASS_ENTRY()的NULL參數替換為php_sample2_firstclass_functions, 並直接在MINIT函數上面如下定義這個結構:

static function_entry php_sample2_firstclass_functions[] = {  
    { NULL, NULL, NULL }  
};
登入後複製

PHP_NAMED_FE(method1, PHP_FN(Sample2_FirstClass_method1), NULL)
登入後複製

看起來熟悉嗎? 當然. 這和你原來定義過程函數的結構相同. 甚至, 設定這個結構的方式也很相似:

PHP_FUNCTION(Sample2_FirstClass_countProps)  
{  
    RETURN_LONG(zend_hash_num_elements(Z_OBJPROP_P(getThis())));  
}
登入後複製

當然, 你也可以選用PHP_FE(method1, NULL). 不過回顧一下第5章, 這樣做期望找到的函數實現的名字是zif_method1, 它可能潛在的回合其他的method1()實現衝突. 為了函數的名字空間安全, 我們將類別名稱作為方法名的前綴.

PHP_FALIAS(method1, Sample2_FirstClass_method1, NPHP_FALIAS(method1, Sample2_FirstClass_method1, NULLPHP_FALIAS(method1, Sample2_FirstClass_method1, NULL )的格式也是可以的; 但它有點不直觀, 你以後回過頭來看代碼的時候可能會產生疑問"為什麼當時沒有使用PHP_FE()?"

現在, 你已經將一個函數列表附加到類的定義上了, 是時候定義一些方法了. 在php_sample2_firstclass_functions結構上面創建下面的函數:

static function_entry php_sample2_firstclass_functions[] = {  
    PHP_NAMED_FE(countprops,  
            PHP_FN(Sample2_FirstClass_countProps), NULL)  
    { NULL, NULL, NULL }  
};
登入後複製

相應的, 在它的函數列表中增加一條PHP_NAMED_FE()條目:

PHP_FUNCTION(Sample2_FirstClass_sayHello)  
{  
    char *name;  
    int name_len;  
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s",  
                        &name, &name_len) == FAILURE) {  
        RETURN_NULL();  
    }  
    php_printf("Hello");  
    PHPWRITE(name, name_len);  
    php_printf("!\nYou called an object method!\n");  
    RETURN_TRUE;  
}
登入後複製

使用者空間的函數名稱是全部小寫的.為了確保方法和函數名都是大小寫不敏感的, 就要求內部函數給出全部小寫的名字.

這裡唯一的新元素就是getThis(), 在所有的php版本中, 它都會被解析為一個宏, 展開是this_ptr. this_ptr從本質上來說就和用戶空間對象方法中的$this含義相同. 如果沒有可用的對象實例, 比如方法被靜態化調用, 則getThis ()回傳NULL.

物件方法的資料回傳語意和過程函式一致, 參數接受以及arg_info都是同一套東西.

void php_sample2_inherit_from_class(zend_class_entry *ce,  
                        zend_class_entry *parent_ce) {  
    zend_hash_merge(&ce->function_table,  
            &parent_ce->function_table, (void (*)(void *))function_add_ref,  
            NULL, sizeof(zval*), 0);  
    ce->parent = parent_ce;  
    if (!ce->handle_property_get) {  
        ce->handle_property_get =  
                parent_ce->handle_property_get;  
    }  
    if (!ce->handle_property_set) {  
        ce->handle_property_set =  
                parent_ce->handle_property_set;  
    }  
    if (!ce->handle_function_call) {  
        ce->handle_function_call =  
                parent_ce->handle_function_call;  
    }  
    if (!zend_hash_exists(&ce->function_table,  
                ce->name, ce->name_length + 1)) {  
        zend_function *fe;  
        if (zend_hash_find(&parent_ce->function_table,  
                parent_ce->name, parent_ce->name_length + 1,  
                (void**)fe) == SUCCESS) {  
            zend_hash_update(&ce->function_table,  
                ce->name, ce->name_length + 1,  
                fe, sizeof(zend_function), NULL);  
            function_add_ref(fe);  
        }  
    }  
}
登入後複製

建構器

你的類別建構器可以和其他的普通類別方法一樣實作,它的命名遵循也遵循相同的規則. 特別之處在於你需要將構造器命名為類別名稱. 其他兩個ZE1魔術方法__sleep()和__wakeup()也可以以這種方式實現.

繼承

php4中, 內部物件之間的繼承是不完善的, 最好避免使用. 如果你確實必須繼承其他物件, 需要複製下面的ZE1程式碼:

INIT_CLASS_ENTRY(ce, "Sample2_FirstClass", NULL);  
/* 假定php_saple2_ancestor是一个已经注册的zend_class_entry */  
php_sample2_firstclass_entry =  
        zend_register_internal_class(&ce TSRMLS_CC);  
php_sample2_inherit_from_class(php_sample2_firstclass_entry  
                            ,php_sample2_ancestor);
登入後複製

定義這樣一個函數, 你就可以在MINIT中zend_register_internal_class下面對其進行調用:

PHP_FUNCTION(sample2_new)  
{  
    int argc = ZEND_NUM_ARGS();  
    zval ***argv = safe_emalloc(sizeof(zval**), argc, 0);  
    zend_class_entry *ce;  
    if (argc == 0 ||  
        zend_get_parameters_array_ex(argc, argv) == FAILURE) {  
        efree(argv);  
        WRONG_PARAM_COUNT;  
    }  
    /* 第一个参数是类名 */  
    SEPARATE_ZVAL(argv[0]);  
    convert_to_string(*argv[0]);  
    /* 类名存储为小写 */  
    php_strtolower(Z_STRVAL_PP(argv[0]), Z_STRLEN_PP(argv[0]));  
    if (zend_hash_find(EG(class_table),  
            Z_STRVAL_PP(argv[0]), Z_STRLEN_PP(argv[0]) + 1,  
            (void**)&ce) == FAILURE) {  
        php_error_docref(NULL TSRMLS_CC, E_WARNING,  
            "Class %s does not exist.",  
            Z_STRVAL_PP(argv[0]));  
        zval_ptr_dtor(argv[0]);  
        efree(argv);  
        RETURN_FALSE;  
    }  
    object_init_ex(return_value, ce);  
    /* 如果有构造器则调用, 额外的参数将传递给构造器 */  
    if (zend_hash_exists(&ce->function_table,  
            Z_STRVAL_PP(argv[0]),Z_STRLEN_PP(argv[0]) + 1)) {  
        /* 对象有构造器 */  
        zval *ctor, *dummy = NULL;  
  
        /* 构造器名字是类名 */  
        MAKE_STD_ZVAL(ctor);  
        array_init(ctor);  
        zval_add_ref(argv[0]);  
        add_next_index_zval(ctor, *argv[0]);  
        zval_add_ref(argv[0]);  
        add_next_index_zval(ctor, *argv[0]);  
        if (call_user_function_ex(&ce->function_table,  
                NULL, ctor,  
                &dummy, /* 不关心返回值 */  
                argc - 1, argv + 1, /* 参数 */  
                0, NULL TSRMLS_CC) == FAILURE) {  
            php_error_docref(NULL TSRMLS_CC, E_WARNING,  
                 "Unable to call constructor");  
        }  
        if (dummy) {  
            zval_ptr_dtor(&dummy);  
        }  
        zval_ptr_dtor(&ctor);  
    }  
    zval_ptr_dtor(argv[0]);  
    efree(argv);  
}
登入後複製
登入後複製

儘管這種方式的繼承可以工作, 但還是應該避免ZE1中的繼承, 因為它並沒有設計內部對象的繼承處理. 對於php中的多數OOP實踐, ZE2和它修訂的物件模型是健壯的, 鼓勵所有的OOP相關任務都直接使用它來處理.

使用實例工作🎜

和其它用户空间变量一样, 对象存储在zval *容器中. 在ZE1中, zval *包含了一个HashTable *用于保存属性, 以及一个zend_class_entry *指针, 指向类的定义. 在ZE2中, 这些值被一个句柄表替代, 增加了一个数值的对象ID, 它和资源ID的用法类似.

很幸运, ZE1和ZE2的这些差异被第2章"变量的里里外外"中介绍的Z_*()族宏隐藏了, 因此在你的扩展中不需要关心这些. 下表10.1列出了两个ZE1的宏, 与非OOP的相关宏一致, 它们也有对应的_P和_PP版本, 用来处理一级或两级间访.

[翻譯][php擴展開發與嵌入式]第10章-php4的對象

创建实例

大部分时间, 你的扩展都不需要自己创建实例. 而是用户空间调用new关键字创建实例并调用你的类构造器.

但你还是有可能需要创建实例, 比如在工厂方法中, ZEND_API中的object_init_ex(zval *val, zend_class_entry *ce)函数可以用于将对象实例初始化到变量中.

要注意, object_init_ex()函数并不会调用构造器. 当在内部函数中实例化对象时, 构造器必须手动调用. 下面的过程函数重演了new关键字的功能逻辑:

PHP_FUNCTION(sample2_new)  
{  
    int argc = ZEND_NUM_ARGS();  
    zval ***argv = safe_emalloc(sizeof(zval**), argc, 0);  
    zend_class_entry *ce;  
    if (argc == 0 ||  
        zend_get_parameters_array_ex(argc, argv) == FAILURE) {  
        efree(argv);  
        WRONG_PARAM_COUNT;  
    }  
    /* 第一个参数是类名 */  
    SEPARATE_ZVAL(argv[0]);  
    convert_to_string(*argv[0]);  
    /* 类名存储为小写 */  
    php_strtolower(Z_STRVAL_PP(argv[0]), Z_STRLEN_PP(argv[0]));  
    if (zend_hash_find(EG(class_table),  
            Z_STRVAL_PP(argv[0]), Z_STRLEN_PP(argv[0]) + 1,  
            (void**)&ce) == FAILURE) {  
        php_error_docref(NULL TSRMLS_CC, E_WARNING,  
            "Class %s does not exist.",  
            Z_STRVAL_PP(argv[0]));  
        zval_ptr_dtor(argv[0]);  
        efree(argv);  
        RETURN_FALSE;  
    }  
    object_init_ex(return_value, ce);  
    /* 如果有构造器则调用, 额外的参数将传递给构造器 */  
    if (zend_hash_exists(&ce->function_table,  
            Z_STRVAL_PP(argv[0]),Z_STRLEN_PP(argv[0]) + 1)) {  
        /* 对象有构造器 */  
        zval *ctor, *dummy = NULL;  
  
        /* 构造器名字是类名 */  
        MAKE_STD_ZVAL(ctor);  
        array_init(ctor);  
        zval_add_ref(argv[0]);  
        add_next_index_zval(ctor, *argv[0]);  
        zval_add_ref(argv[0]);  
        add_next_index_zval(ctor, *argv[0]);  
        if (call_user_function_ex(&ce->function_table,  
                NULL, ctor,  
                &dummy, /* 不关心返回值 */  
                argc - 1, argv + 1, /* 参数 */  
                0, NULL TSRMLS_CC) == FAILURE) {  
            php_error_docref(NULL TSRMLS_CC, E_WARNING,  
                 "Unable to call constructor");  
        }  
        if (dummy) {  
            zval_ptr_dtor(&dummy);  
        }  
        zval_ptr_dtor(&ctor);  
    }  
    zval_ptr_dtor(argv[0]);  
    efree(argv);  
}
登入後複製
登入後複製

不要忘了在php_sample2_functions中增加一个引用. 它是你的扩展的过程函数列表, 而不是类方法的列表. 为了使用php_strtolower()函数, 还需要增加#include "ext/standard/php_string.h".

这个函数是目前你实现的最复杂的一个, 其中有几个全新的特性. 首先就是SEPARATE_ZVAL(), 实际上它的功能你已经实现过很多次, 利用zval_copy_ctor()赋值值到一个临时的结构体, 避免修改原始的内容. 不过它是一个宏版本的封装.

php_strtolower()用于将类名转换为小写, 这样做是为了达到php类名和函数名不区分大小写的目的. 这只是附录B中列出的众多PHPAPI工具函数的其中一个.

EG(class_table)是一个全局变量, 所有的zend_class_entry定义都注册到它里面. 要注意的是在ZE1(php4)中这个HashTable存储了一级间访的zend_class_entry *结构体. 而在ZE2(php5)中, 它被存储为两级间访. 这应该不会是一个问题, 因为对这个HashTable的直接访问并不常见, 但知道这一点总归是有好处的.

call_user_function_ex()是你将在第20章"高级嵌入式"中看到的ZENDAPI调用的一部分. 这里你将从zend_get_parameters_ex()接收到的zval **参数栈第一个元素拿走, 这样做就是为了原封不动的将剩余的参数传递给构造器.

译注: 原著中的代码在译者的环境(php-5.4.9)中不能运行, 需要将zend_class_entry *ce修改为二级间访. 下面给出译者测试通过的代码.

PHP_FUNCTION(sample_new)  
{  
    int                 argc    = ZEND_NUM_ARGS();  
    zval                ***argv = safe_emalloc(sizeof(zval **), argc, 0);   
    zend_class_entry    **ce;       /* 译注: 这里在译者的环境(php-5.4.9)是二级间访 */  
  
    /* 数组方式读取所有传入参数 */  
    if ( argc == 0 ||    
            zend_get_parameters_array_ex(argc, argv) == FAILURE ) {   
        efree(argv);  
        WRONG_PARAM_COUNT;  
    }     
  
    /* 隔离第一个参数(隔离为了使下面的类型转换不影响原始数据) */  
    SEPARATE_ZVAL(argv[0]);  
    /* 将第一个参数转换为字符串类型, 并转为小写(因为php的类名是不区分大小写的) */  
    convert_to_string(*argv[0]);  
    php_strtolower(Z_STRVAL_PP(argv[0]), Z_STRLEN_PP(argv[0]));  
    /* 在类的HashTable中查找提供的类是否存在, 如果存在, ce中就得到了对应的zend_class_entry * */  
    if ( zend_hash_find(EG(class_table), Z_STRVAL_PP(argv[0]), Z_STRLEN_PP(argv[0]) + 1, (void **)&ce) == FAILURE ) {   
        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Class %s does not exist.", Z_STRVAL_PP(argv[0]));  
        zval_ptr_dtor(argv[0]);  
        efree(argv);  
        RETURN_FALSE;  
    }     
  
    /* 将返回值初始化为查找到的类的对象 */  
    object_init_ex(return_value, *ce);  
    /* 检查类是否有构造器 */  
    if ( zend_hash_exists(&(*ce)->function_table, Z_STRVAL_PP(argv[0]), Z_STRLEN_PP(argv[0]) + 1) ) {   
        zval    *ctor, *dummy = NULL;  
  
        /* 将ctor构造为一个数组, 对应的用户空间形式为: array(argv[0], argv[0]),  
         * 实际上对应于用户空间调用类的静态方法时$funcname的参数形式: 
         * array(类名, 方法名) 
         */  
        MAKE_STD_ZVAL(ctor);  
        array_init(ctor);  
        zval_add_ref(argv[0]);  
        add_next_index_zval(ctor, *argv[0]);  
        zval_add_ref(argv[0]);  
        add_next_index_zval(ctor, *argv[0]);  
        /* 调用函数 */  
        if ( call_user_function_ex(&(*ce)->function_table, NULL, ctor, &dummy, argc - 1, argv + 1, 0, NULL TSRMLS_CC) == FAILURE ) {   
            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to call constructor");  
        }     
        /* 如果有返回值直接析构丢弃 */  
        if ( dummy ) {  
            zval_ptr_dtor(&dummy);  
        }  
        /* 析构掉临时使用(用来描述所调用方法名)的数组 */  
        zval_ptr_dtor(&ctor);  
    }  
    /* 析构临时隔离出来的第一个参数(类名) */  
    zval_ptr_dtor(argv[0]);  
    /* 释放实参列表空间 */  
    efree(argv);  
}
登入後複製

接受实例

有时你的函数或方法需要接受用户空间的对象参数. 对于这种目的, zend_parse_parameters()提供了两种格式的修饰符. 第一种是o(小写字母o), 它将验证传递的参数是否是对象, 并将它设置到传递的zval **中. 下面是这种方式的一个简单的用户空间函数示例, 它返回传入对象的类名.

PHP_FUNCTION(sample2_class_getname)  
{  
    zval *objvar;  
    zend_class_entry *objce;  
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o",  
                                &objvar) == FAILURE) {  
        RETURN_NULL();  
    }  
    objce = Z_OBJCE_P(objvar);  
    RETURN_STRINGL(objce->name, objce->name_length, 1);  
}
登入後複製

第二种修饰符是O(大写字母O), 它不仅允许zend_parse_parameters()验证zval *的类型, 还可以验证所传递对象的类. 要做到这一点, 就需要传递一个zval **容易以及一个zend_class_entry *用来验证, 比如下面的实现就期望传入的是Sample2_FirstClass类的实例:

PHP_FUNCTION(sample2_reload)  
{  
    zval *objvar;  
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O",  
        &objvar, php_sample2_firstclass_entry) == FAILURE) {  
        RETURN_NULL();  
    }  
    /* 调用假想的"reload"函数 */  
    RETURN_BOOL(php_sample2_fc_reload(objvar TSRMLS_CC));  
}
登入後複製

访问属性

你已经看到了, 类方法可以通过getThis()获取到当前对象实例. 将这个宏的结果或其它包含对象实例的zval *与Z_OBJPROP_P()宏组合, 得到的HashTable *就包含了该对象的所有属性.

对象的属性列表是一个包含zval *的HashTable *, 它只是另外一种放在特殊位置的用户空间变量列表. 和使用zend_hash_find(EG(active_symbol_table), ...)从当前作用域获取变量一样, 你也可以使用第8章"在数组和HashTable上工作"中学习的zend_hash-API去获取或设置对象的属性.

例如, 假设在变量rcvdclass这个zval *中包含的是Sample2_FirstClass的实例, 下面的代码块就可以从它的标准属性HashTable中取到属性foo.

zval **fooval;  
if (zend_hash_find(Z_OBJPROP_P(rcvdclass),  
        "foo", sizeof("foo"), (void**)&fooval) == FAILURE) {  
    /* $rcvdclass->foo doesn&#39;t exist */  
    return;  
}
登入後複製

要向属性表中增加元素, 则是这个过程的逆向过程, 调用zend_hash_add()去增加元素, 或者也可以将第8章介绍数组时介绍的add_assoc_*()族函数的assoc替换为property来处理对象.

下面的构造器函数为Sample2_FirstClass的实例提供了一些预先设置的默认属性:

PHP_NAMED_FUNCTION(php_sample2_fc_ctor)  
{  
    /* 为了简洁, 同时演示函数名可以是任意的, 这里实现的函数名并不是类名 */  
    zval *objvar = getThis();  
  
    if (!objvar) {  
        php_error_docref(NULL TSRMLS_CC, E_WARNING,  
                        "Constructor called statically!");  
        RETURN_FALSE;  
    }  
  
    add_property_long(objvar, "life", 42);  
    add_property_double(objvar, "pi", 3.1415926535);  
    /* 构造器的返回值会被忽略(请回顾前面构造器的例子) */  
}
登入後複製

现在可以通过php_sample2_firstclass_functions列表将它连接到对象的构造器:

PHP_NAMED_FE(sample2_firstclass, php_sample2_fc_ctor, NULL)
登入後複製

译注: 由于前面的sample_new()工厂函数在call_user_function_ex()调用构造器时使用的是静态方法的调用格式, 因此, 如果是使用这个工厂函数触发的构造器调用, getThis()就不会有期望的结果. 因此译者对例子进行了相应的修改, 读者如果在这块遇到问题可以参考译者的代码.

PHP_FUNCTION(sample_new)  
{  
    int                 argc    = ZEND_NUM_ARGS();  
    zval                ***argv = safe_emalloc(sizeof(zval **), argc, 0);   
    zend_class_entry    **ce;       /* 译注: 这里在译者的环境(php-5.4.9)是二级间访 */  
  
    /* 数组方式读取所有传入参数 */  
    if ( argc == 0 ||    
            zend_get_parameters_array_ex(argc, argv) == FAILURE ) {   
        efree(argv);  
        WRONG_PARAM_COUNT;  
    }     
  
    /* 隔离第一个参数(隔离为了使下面的类型转换不影响原始数据) */  
    SEPARATE_ZVAL(argv[0]);  
    /* 将第一个参数转换为字符串类型, 并转为小写(因为php的类名是不区分大小写的) */  
    convert_to_string(*argv[0]);  
    php_strtolower(Z_STRVAL_PP(argv[0]), Z_STRLEN_PP(argv[0]));  
    /* 在类的HashTable中查找提供的类是否存在, 如果存在, ce中就得到了对应的zend_class_entry * */  
    if ( zend_hash_find(EG(class_table), Z_STRVAL_PP(argv[0]), Z_STRLEN_PP(argv[0]) + 1, (void **)&ce) == FAILURE ) {   
        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Class %s does not exist.", Z_STRVAL_PP(argv[0]));  
        zval_ptr_dtor(argv[0]);  
        efree(argv);  
        RETURN_FALSE;  
    }     
  
    /* 将返回值初始化为查找到的类的对象 */  
    object_init_ex(return_value, *ce);  
    /* 检查类是否有构造器 */  
    if ( zend_hash_exists(&(*ce)->function_table, Z_STRVAL_PP(argv[0]), Z_STRLEN_PP(argv[0]) + 1) ) {   
#define DYNAMIC_CONSTRUCTOR  
#ifndef DYNAMIC_CONSTRUCTOR  
        zval    *ctor;  
#endif  
        zval    *dummy = NULL;  
  
#ifndef DYNAMIC_CONSTRUCTOR  
        /* 将ctor构造为一个数组, 对应的用户空间形式为: array(argv[0], argv[0]),  
         * 实际上对应于用户空间调用类的静态方法时$funcname的参数形式: 
         * array(类名, 方法名) 
         */  
        MAKE_STD_ZVAL(ctor);  
        array_init(ctor);  
        zval_add_ref(argv[0]);  
        add_next_index_zval(ctor, *argv[0]);  
        zval_add_ref(argv[0]);  
        add_next_index_zval(ctor, *argv[0]);  
#endif  
        /* 调用函数 */  
        if ( call_user_function_ex(&(*ce)->function_table,  
#ifndef DYNAMIC_CONSTRUCTOR  
                NULL, ctor,  
#else  
                &return_value, *argv[0],  
#endif  
                &dummy, argc - 1, argv + 1, 0, NULL TSRMLS_CC) == FAILURE ) {  
            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to call constructor");  
        }  
        /* 如果有返回值直接析构丢弃 */  
        if ( dummy ) {  
            zval_ptr_dtor(&dummy);  
        }  
#ifndef DYNAMIC_CONSTRUCTOR  
        /* 析构掉临时使用(用来描述所调用方法名)的数组 */  
        zval_ptr_dtor(&ctor);  
#endif  
    }  
    /* 析构临时隔离出来的第一个参数(类名) */  
    zval_ptr_dtor(argv[0]);  
    /* 释放实参列表空间 */  
    efree(argv);  
}
登入後複製

译注: 现在, 就可以用函数中是否定义DYNAMIC_CONSTRUCTOR这个宏来切换构造器的调用方式, 以方便读者理解.

小结

尽管ZE1/php4提供的类功能最好少用, 但是由于当前php4在产品环境下还是广泛使用的, 因此做这个兼容还是有好处的. 本章涉及的技术可以让你灵活的编写各种功能的代码, 它们现在可以编译运行, 并且未来也将继续可以工作.

下一章, 你将看到php5中真正的面向对象, 如果你想要OOP, 从中你就可以得到升级的理由, 并且, 升级后你肯定再也不愿回头.

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


本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1658
14
CakePHP 教程
1415
52
Laravel 教程
1309
25
PHP教程
1257
29
C# 教程
1231
24
您如何在PHP中解析和處理HTML/XML? 您如何在PHP中解析和處理HTML/XML? Feb 07, 2025 am 11:57 AM

本教程演示瞭如何使用PHP有效地處理XML文檔。 XML(可擴展的標記語言)是一種用於人類可讀性和機器解析的多功能文本標記語言。它通常用於數據存儲

在PHP API中說明JSON Web令牌(JWT)及其用例。 在PHP API中說明JSON Web令牌(JWT)及其用例。 Apr 05, 2025 am 12:04 AM

JWT是一種基於JSON的開放標準,用於在各方之間安全地傳輸信息,主要用於身份驗證和信息交換。 1.JWT由Header、Payload和Signature三部分組成。 2.JWT的工作原理包括生成JWT、驗證JWT和解析Payload三個步驟。 3.在PHP中使用JWT進行身份驗證時,可以生成和驗證JWT,並在高級用法中包含用戶角色和權限信息。 4.常見錯誤包括簽名驗證失敗、令牌過期和Payload過大,調試技巧包括使用調試工具和日誌記錄。 5.性能優化和最佳實踐包括使用合適的簽名算法、合理設置有效期、

解釋PHP中的晚期靜態綁定(靜態::)。 解釋PHP中的晚期靜態綁定(靜態::)。 Apr 03, 2025 am 12:04 AM

靜態綁定(static::)在PHP中實現晚期靜態綁定(LSB),允許在靜態上下文中引用調用類而非定義類。 1)解析過程在運行時進行,2)在繼承關係中向上查找調用類,3)可能帶來性能開銷。

php程序在字符串中計數元音 php程序在字符串中計數元音 Feb 07, 2025 pm 12:12 PM

字符串是由字符組成的序列,包括字母、數字和符號。本教程將學習如何使用不同的方法在PHP中計算給定字符串中元音的數量。英語中的元音是a、e、i、o、u,它們可以是大寫或小寫。 什麼是元音? 元音是代表特定語音的字母字符。英語中共有五個元音,包括大寫和小寫: a, e, i, o, u 示例 1 輸入:字符串 = "Tutorialspoint" 輸出:6 解釋 字符串 "Tutorialspoint" 中的元音是 u、o、i、a、o、i。總共有 6 個元

什麼是PHP魔術方法(__ -construct,__destruct,__call,__get,__ set等)並提供用例? 什麼是PHP魔術方法(__ -construct,__destruct,__call,__get,__ set等)並提供用例? Apr 03, 2025 am 12:03 AM

PHP的魔法方法有哪些? PHP的魔法方法包括:1.\_\_construct,用於初始化對象;2.\_\_destruct,用於清理資源;3.\_\_call,處理不存在的方法調用;4.\_\_get,實現動態屬性訪問;5.\_\_set,實現動態屬性設置。這些方法在特定情況下自動調用,提升代碼的靈活性和效率。

PHP和Python:比較兩種流行的編程語言 PHP和Python:比較兩種流行的編程語言 Apr 14, 2025 am 12:13 AM

PHP和Python各有優勢,選擇依據項目需求。 1.PHP適合web開發,尤其快速開發和維護網站。 2.Python適用於數據科學、機器學習和人工智能,語法簡潔,適合初學者。

PHP:網絡開發的關鍵語言 PHP:網絡開發的關鍵語言 Apr 13, 2025 am 12:08 AM

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

PHP行動:現實世界中的示例和應用程序 PHP行動:現實世界中的示例和應用程序 Apr 14, 2025 am 12:19 AM

PHP在電子商務、內容管理系統和API開發中廣泛應用。 1)電子商務:用於購物車功能和支付處理。 2)內容管理系統:用於動態內容生成和用戶管理。 3)API開發:用於RESTfulAPI開發和API安全性。通過性能優化和最佳實踐,PHP應用的效率和可維護性得以提升。

See all articles