开发环境: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.