just do it

基于PHP7的PHP扩展开发之六(定义常量)

目的:熟悉在PHP扩展里面如何定义常量

要实现的PHP代码如下:通常情况下,PHP中定义的常量在请求结束后,就会被释放。一个新的请求到来后,还得从新注册常量,影响性能。通过扩展的方式定义进程内共享的常量。即,请求结束后,不释放。新请求可以复用之前的常量。

1
2
3
4
5
6
7
8
<?php
    define("__ARR__", array('110', 'site'=>"www.lostphp.com"));  
    define("__SITE__", "www.lostphp.com", true);
    define("myecho\__SITE__", "lostphp blog");
    var_dump(__ARR__);
    var_dump(__site__);
    var_dump(myecho\__SITE__);
?>

基础代码

在myecho扩展的 PHP_MINIT_FUNCTION(myecho) 方法上增加相应的代码。

增加的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//增加两个方法
//释放hash
static void myecho_hash_destroy(HashTable *ht)
{    zend_string *key;
    zval *element;
    if (((ht)->u.flags & HASH_FLAG_INITIALIZED)) {
        ZEND_HASH_FOREACH_STR_KEY_VAL(ht, key, element) {
            if (key) {
                free(key);
            }
            switch (Z_TYPE_P(element)) {
                case IS_STRING:
                    free(Z_PTR_P(element));
                    break;
                case IS_ARRAY:
                    myecho_hash_destroy(Z_ARRVAL_P(element));
                    break;
            }
        } ZEND_HASH_FOREACH_END();
        free(HT_GET_DATA_ADDR(ht));
    }   
    free(ht);
}
//释放数组和字符串
static void myecho_entry_dtor_persistent(zval *zvalue)
{    if (Z_TYPE_P(zvalue) == IS_ARRAY) {
        myecho_hash_destroy(Z_ARRVAL_P(zvalue));
    } else if (Z_TYPE_P(zvalue) == IS_STRING) {
        zend_string_release(Z_STR_P(zvalue));
    }
}
//PHP_MINIT_FUNCTION(myecho)方法的PHP扩展源码: 扩展初始化的调用此方法
PHP_MINIT_FUNCTION(myecho)
{
    zend_constant c;
    zend_string *key;
    zval value;
    ZVAL_NEW_PERSISTENT_ARR(&c.value);
    zend_hash_init(Z_ARRVAL(c.value), 0, NULL,(dtor_func_t)myecho_entry_dtor_persistent, 1);
    add_index_long(&c.value, 0, 110);
    key = zend_string_init("site", 4, 1);
    ZVAL_STR(&value, zend_string_init("www.lostphp.com", 15, 1));
    zend_hash_update(Z_ARRVAL(c.value), key, &value);
    c.flags = CONST_CS|CONST_PERSISTENT;
    c.name = zend_string_init("__ARR__", 7, 1);
    c.module_number = module_number;
    zend_register_constant(&c);
 
    REGISTER_STRINGL_CONSTANT("__SITE__", "www.lostphp.com", 15, CONST_PERSISTENT);
    REGISTER_NS_STRINGL_CONSTANT("myecho", "__SITE__", "lostphp blog", 12, CONST_CS|CONST_PERSISTENT);
 
}
//扩展卸载的时候调用此方法
PHP_MSHUTDOWN_FUNCTION(myecho)
{
    zval *val;
    val = zend_get_constant_str("__ARR__", 7);
    myecho_hash_destroy(Z_ARRVAL_P(val));
    ZVAL_NULL(val);
    return SUCCESS;
}

代码说明

一般情况下,在扩展中只建议定义null,bool,long,double,string几种类型的常量。因为内核只提供了这几种类型的宏方法。 常量定义的宏方法在Zend/zend_constants.h文件中。想定义一个常量,很简单,只要调用对应的宏方法即可。如:

1
REGISTER_STRINGL_CONSTANT("__SITE__", "www.lostphp.com",15, CONST_PERSISTENT);

宏方法的最后一个参数是一些标识符。 CONST_PERSISTENT 表示为持久的。常驻内存。 CONST_CS 表示为区分大小写。 注意我们上面定义常量时使用的是SITE,但是调用的时候使用的是site。

还有一套可以指定命名空间的宏方法。宏方法中带NS。如:

1
REGISTER_NS_STRINGL_CONSTANT("myecho", "__SITE__", "我的博客",12, CONST_CS|CONST_PERSISTENT);

第一个参数就是命名空间。

为了展示常量定义的一些细节。我们定义了一个ARR常量。 ZVAL_NEW_PERSISTENT_ARR(&c.value);我们想让ARR为持久的。所以使用ZVAL_NEW_PERSISTENT_ARR创建一个数组。 数组创建完后,我们需要初始化。初始化的代码就是

1
zend_hash_init(Z_ARRVAL(c.value), 0, NULL,(dtor_func_t)myecho_entry_dtor_persistent, 1);

参数中的myecho_entry_dtor_persistent是一个析构函数,用于释放数组的元素。

到这里,如果编译运行。当程序执行结束的时候,你会发现一个致命错误。错误信息如下:

Fatal error: Internal zval's can't be arrays, objects or resources in Unknown on line 0

因为在程序执行完毕,内部zval释放的时候,会进行类型检测。如果发现是array object或者resources,则会报错。可以查看Zend/zend_variables.c文件中_zval_internal_dtor方法。 为了解决这个问题,我们需要手动释放我们创建的ARR相关的数组。 模块卸载时执行的方法,是优先Zend内部zval释放方法之前调用的。因此,我们只要在PHP_MSHUTDOWN_FUNCTION(myecho)方法中手动释放。不再让Zend去释放就可以解决了。

编译生成扩展 参考《基于PHP7的PHP扩展开发之一(hello word)》

测试

1
2
3
4
5
<?php
    var_dump(__ARR__);
    var_dump(__site__);
    var_dump(myecho\__SITE__);
?>

输出

array(2) {
[0]=>
int(110)
[“site”]=>
string(15) “www.lostphp.com”
}
string(15) “www.lostphp.com”
string(12) “lostphp blog”

点赞