扩展开发教程·类

开发环境:PHP-8.2
 
 
 
下载PHP源码并解压,注意选择和当前环境相同的版本
[root@localhost ~]# wget https://www.php.net/distributions/php-*.*.*.tar.gz
 
 
 
创建扩展骨架,执行完命令后会在PHP源码的ext目录下生成该扩展的目录
[root@localhost ~]# /program/php/bin/php /program/php/src/ext/ext_skel.php --ext demo_profile
Copying config scripts... done
Copying sources... done
Copying tests... done
 
Success. The extension is now ready to be compiled. To do so, use the
following steps:
 
cd /program/php/src/ext/demo_profile
phpize
./configure
make
 
Don't forget to run tests once the compilation is done:
make test
 
Thank you for using PHP!
[root@localhost ~]# 
 
 
 
在扩展目录下有一个demo_profile.stub.php文件,在该文件里实现类的原型即可
[root@localhost ~]# vim /program/php/src/ext/demo_profile/demo_profile.stub.php
<?php
 
/**
 * @generate-class-entries
 * @undocumentable
 */
 
function test1(): void
{
}
 
function test2(string $str = ''): string
{
}
 
/**
 * Profile类原型
 */
class Profile
{
    private string $name; // 姓名
    private string $gender; // 性别
    private int $birth; // 出生年份
 
    /**
     * 构造方法
     *
     * @param string $name 姓名
     * @param string $gender 性别
     * @param int $birth 出生年份
     */
    public function __construct(string $name = '匿名', string $gender = '保密', int $birth = 1970)
    {
    }
 
    /**
     * 析构方法
     */
    public function __destruct()
    {
    }
 
    /**
     * 自我介绍
     *
     * @return string 自我介绍文本
     */
    public function intro(): string
    {
    }
}
[root@localhost ~]#
 
 
 
使用工具解析demo_profile.stub.php文件,并根据解析内容更新demo_profile_arginfo.h文件
[root@localhost ~]# cd /program/php/src/ext/demo_profile
[root@localhost demo_profile]# /program/php/bin/php ../../build/gen_stub.php demo_profile.stub.php
[root@localhost demo_profile]# cat demo_profile_arginfo.h
/* This is a generated file, edit the .stub.php file instead.
 * Stub hash: **************************************** */
 
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_test1, 0, 0, IS_VOID, 0)
ZEND_END_ARG_INFO()
 
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_test2, 0, 0, IS_STRING, 0)
        ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, str, IS_STRING, 0, "\'\'")
ZEND_END_ARG_INFO()
 
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Profile___construct, 0, 0, 0)
        ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, name, IS_STRING, 0, "\'匿名\'")
        ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, gender, IS_STRING, 0, "\'保密\'")
        ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, birth, IS_LONG, 0, "1970")
ZEND_END_ARG_INFO()
 
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Profile___destruct, 0, 0, 0)
ZEND_END_ARG_INFO()
 
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Profile_intro, 0, 0, IS_STRING, 0)
ZEND_END_ARG_INFO()
 
 
ZEND_FUNCTION(test1);
ZEND_FUNCTION(test2);
ZEND_METHOD(Profile, __construct);
ZEND_METHOD(Profile, __destruct);
ZEND_METHOD(Profile, intro);
 
 
static const zend_function_entry ext_functions[] = {
        ZEND_FE(test1, arginfo_test1)
        ZEND_FE(test2, arginfo_test2)
        ZEND_FE_END
};
 
 
static const zend_function_entry class_Profile_methods[] = {
        ZEND_ME(Profile, __construct, arginfo_class_Profile___construct, ZEND_ACC_PUBLIC)
        ZEND_ME(Profile, __destruct, arginfo_class_Profile___destruct, ZEND_ACC_PUBLIC)
        ZEND_ME(Profile, intro, arginfo_class_Profile_intro, ZEND_ACC_PUBLIC)
        ZEND_FE_END
};
 
static zend_class_entry *register_class_Profile(void)
{
        zend_class_entry ce, *class_entry;
 
        INIT_CLASS_ENTRY(ce, "Profile", class_Profile_methods);
        class_entry = zend_register_internal_class_ex(&ce, NULL);
 
        zval property_name_default_value;
        ZVAL_UNDEF(&property_name_default_value);
        zend_string *property_name_name = zend_string_init("name", sizeof("name") - 1, 1);
        zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING));
        zend_string_release(property_name_name);
 
        zval property_gender_default_value;
        ZVAL_UNDEF(&property_gender_default_value);
        zend_string *property_gender_name = zend_string_init("gender", sizeof("gender") - 1, 1);
        zend_declare_typed_property(class_entry, property_gender_name, &property_gender_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING));
        zend_string_release(property_gender_name);
 
        zval property_birth_default_value;
        ZVAL_UNDEF(&property_birth_default_value);
        zend_string *property_birth_name = zend_string_init("birth", sizeof("birth") - 1, 1);
        zend_declare_typed_property(class_entry, property_birth_name, &property_birth_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG));
        zend_string_release(property_birth_name);
 
        return class_entry;
}
[root@localhost demo_profile]#
 
 
 
在demo_profile.c文件里实现扩展功能
[root@localhost ~]# vim /program/php/src/ext/demo_profile/demo_profile.c
…………(此处省略内容若干)…………
 
//========== 注册Profile类并实现类方法·开始 ==========//
 
static zend_class_entry *profile_class_entry;
 
PHP_MINIT_FUNCTION(demo_profile)
{
    profile_class_entry = register_class_Profile(); // 注册Profile类
 
    return SUCCESS;
}
 
// 实现Profile::__construct()方法,即构造方法
PHP_METHOD(Profile, __construct)
{
    php_printf("[DEBUG][EXT] Profile::__construct() is called.\r\n");
 
    // 获取方法的实参
    zend_string *name = zend_string_init("匿名", sizeof("匿名") - 1, 0);
    zend_string *gender = zend_string_init("保密", sizeof("保密") - 1, 0);
    zend_long birth = 1970;
    if (zend_parse_parameters(ZEND_NUM_ARGS(), "|SSl", &name, &gender, &birth) == FAILURE)
    {
        php_printf("[DEBUG][EXT] 参数错误~~~\r\n"); // 如果实参的数据类型和形参不一致就会出错(即解析参数失败)
        RETURN_THROWS();
    }
    // 说明
    // --------------------------------------------------
    // "|SSl"表示有三个选填参数(三个字母分别对应三个参数的数据类型),其中|是必填和选填的分隔符。
 
    char *name_str = ZSTR_VAL(name);
    long name_str_len = strlen(name_str);
    char *gender_str = ZSTR_VAL(gender);
    long gender_str_len = strlen(gender_str);
    php_printf("[DEBUG][EXT] 姓名:%s(strlen:%ld)\r\n", name_str, name_str_len);
    php_printf("[DEBUG][EXT] 性别:%s(strlen:%ld)\r\n", gender_str, gender_str_len);
    php_printf("[DEBUG][EXT] 出生:%ld\r\n", birth);
 
    // 获取类实例(对象)并更新属性
    zend_object *this = Z_OBJ_P(getThis());
    zend_update_property_string(profile_class_entry, this, "name", sizeof("name") - 1, name_str);
    zend_update_property_string(profile_class_entry, this, "gender", sizeof("gender") - 1, gender_str);
    zend_update_property_long(profile_class_entry, this, "birth", sizeof("birth") - 1, birth);
 
    php_printf("[DEBUG][EXT] 创建Profile类实例成功~~~\r\n");
}
 
// 实现Profile::__destruct()方法,即析构方法
PHP_METHOD(Profile, __destruct)
{
    php_printf("[DEBUG][EXT] Profile::__destruct() is called.\r\n");
}
 
// 实现Profile::intro()方法
PHP_METHOD(Profile, intro)
{
    php_printf("[DEBUG][EXT] Profile::intro() is called.\r\n");
 
    // 获取类实例(对象)
    zend_object *this = Z_OBJ_P(getThis());
 
    zval rv;
 
    char *name_str;
    zval *name_property = zend_read_property(profile_class_entry, this, "name", sizeof("name") - 1, 1, &rv);
    if (Z_TYPE_P(name_property) == IS_STRING)
    {
        name_str = Z_STRVAL_P(name_property);
    }
 
    char *gender_str;
    zval *gender_property = zend_read_property(profile_class_entry, this, "gender", sizeof("gender") - 1, 1, &rv);
    if (Z_TYPE_P(gender_property) == IS_STRING)
    {
        gender_str = Z_STRVAL_P(gender_property);
    }
 
    long birth;
    zval *birth_property = zend_read_property(profile_class_entry, this, "birth", sizeof("birth") - 1, 1, &rv);
    if (Z_TYPE_P(birth_property) == IS_LONG)
    {
        birth = Z_LVAL_P(birth_property);
    }
 
    zend_string *retval;
    retval = strpprintf(0, "俺叫%s(%s),出生于%ld年。", name_str, gender_str, birth);
    RETURN_STR(retval);
}
 
//========== 注册Profile类并实现类方法·结束 ==========//
 
/* {{{ demo_profile_module_entry */
zend_module_entry demo_profile_module_entry = {
    STANDARD_MODULE_HEADER,
    "demo_profile",           /* Extension name */
    ext_functions,            /* zend_function_entry */
    PHP_MINIT(demo_profile)/* PHP_MINIT - Module initialization - 重点:执行PHP_MINIT_FUNCTION(demo_profile)注册Profile类 */
    NULL,                     /* PHP_MSHUTDOWN - Module shutdown */
    PHP_RINIT(demo_profile),  /* PHP_RINIT - Request initialization */
    NULL,                     /* PHP_RSHUTDOWN - Request shutdown */
    PHP_MINFO(demo_profile),  /* PHP_MINFO - Module info */
    PHP_DEMO_PROFILE_VERSION, /* Version */
    STANDARD_MODULE_PROPERTIES};
/* }}} */
 
#ifdef COMPILE_DL_DEMO_PROFILE
#ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
#endif
ZEND_GET_MODULE(demo_profile)
#endif
[root@localhost ~]#
 
 
 
编译扩展就没什么需要特别说明的了,和编译官方扩展是一样的
[root@localhost ~]# cd /program/php/src/ext/demo_profile
[root@localhost demo_profile]# /program/php/bin/phpize
[root@localhost demo_profile]# ./configure --with-php-config=/program/php/bin/php-config
[root@localhost demo_profile]# make
[root@localhost demo_profile]# make install
 
 
 
启用扩展并重启PHP-FPM即可
[root@localhost ~]# vim /program/php/php.ini
extension=demo_profile
[root@localhost ~]# service php-fpm restart
 
 
 
重新编译命令
[root@localhost ~]# cd /program/php/src/ext/demo_profile && \
/program/php/bin/php ../../build/gen_stub.php demo_profile.stub.php && \
/program/php/bin/phpize && \
./configure --with-php-config=/program/php/bin/php-config && \
make clean && make && make install && \
service php-fpm restart
 
 
 
在PHP脚本中使用扩展的类
 
<?php
declare(strict_types=1);
ini_set('display_errors', 'On');
error_reporting(-1);


$ext = 'demo_profile';
$loaded = extension_loaded($ext);
if ($loaded) {
    echo "{$ext}扩展已加载" . PHP_EOL;
} else {
    exit("{$ext}扩展未加载");
}


echo test2('程序猿') . PHP_EOL;


$class = 'Profile';
$exists = class_exists($class);
if ($exists) {
    echo "{$class}类存在" . PHP_EOL;
} else {
    exit("{$class}类不存在");
}


echo PHP_EOL;


$profile0 = new Profile();
echo '0个参数 ---> 调用Profile::intro()方法:' . $profile0->intro() . PHP_EOL;
unset($profile0);


echo PHP_EOL;


$profile1 = new Profile('刘一');
echo '1个参数 ---> 调用Profile::intro()方法:' . $profile1->intro() . PHP_EOL;
unset($profile1);


echo PHP_EOL;


$profile2 = new Profile('陈二', '女');
echo '2个参数 ---> 调用Profile::intro()方法:' . $profile2->intro() . PHP_EOL;
unset($profile2);


echo PHP_EOL;


$profile3 = new Profile('张三', '男', 2003);
echo '3个参数 ---> 调用Profile::intro()方法:' . $profile3->intro() . PHP_EOL;
unset($profile3);


//========== 页面输出结果 ==========//


// demo_profile扩展已加载
// Hello 程序猿
// Profile类存在
//
// [DEBUG][EXT] Profile::__construct() is called.
// [DEBUG][EXT] 姓名:匿名(strlen:6)
// [DEBUG][EXT] 性别:保密(strlen:6)
// [DEBUG][EXT] 出生:1970
// [DEBUG][EXT] 创建Profile类实例成功~~~
// [DEBUG][EXT] Profile::intro() is called.
// 0个参数 ---> 调用Profile::intro()方法:俺叫匿名(保密),出生于1970年。
// [DEBUG][EXT] Profile::__destruct() is called.
//
// [DEBUG][EXT] Profile::__construct() is called.
// [DEBUG][EXT] 姓名:刘一(strlen:6)
// [DEBUG][EXT] 性别:保密(strlen:6)
// [DEBUG][EXT] 出生:1970
// [DEBUG][EXT] 创建Profile类实例成功~~~
// [DEBUG][EXT] Profile::intro() is called.
// 1个参数 ---> 调用Profile::intro()方法:俺叫刘一(保密),出生于1970年。
// [DEBUG][EXT] Profile::__destruct() is called.
//
// [DEBUG][EXT] Profile::__construct() is called.
// [DEBUG][EXT] 姓名:陈二(strlen:6)
// [DEBUG][EXT] 性别:女(strlen:3)
// [DEBUG][EXT] 出生:1970
// [DEBUG][EXT] 创建Profile类实例成功~~~
// [DEBUG][EXT] Profile::intro() is called.
// 2个参数 ---> 调用Profile::intro()方法:俺叫陈二(女),出生于1970年。
// [DEBUG][EXT] Profile::__destruct() is called.
//
// [DEBUG][EXT] Profile::__construct() is called.
// [DEBUG][EXT] 姓名:张三(strlen:6)
// [DEBUG][EXT] 性别:男(strlen:3)
// [DEBUG][EXT] 出生:2003
// [DEBUG][EXT] 创建Profile类实例成功~~~
// [DEBUG][EXT] Profile::intro() is called.
// 3个参数 ---> 调用Profile::intro()方法:俺叫张三(男),出生于2003年。
// [DEBUG][EXT] Profile::__destruct() is called.

Copyright © 2024 码农人生. All Rights Reserved