3

PHP扩展开发 - 类

 2 years ago
source link: https://y2k38.github.io/posts/php-extension-writing-classes/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

PHP扩展开发 - 类

发表于

2020-11-26 更新于 2020-11-28

以下简单介绍如何在C语言层面使用PHP类

创建一个PHP对象,对象类似关联数组,对象之上可关联任意多个函数

PHP_FUNCTION(makeObject) {
object_init(return_value);

// 添加属性
zend_update_property_string(NULL, return_value, "prop1", strlen("prop1"), "val1");
zend_update_property_long(NULL, return_value, "prop2", strlen("prop2"), 123);
}

调用函数并打印结果var_dump(makeObject());,输出如下

object(stdClass)#1 (2) {
["prop1"]=>
string(3) "val1"
["prop2"]=>
int(123)
}

创建一个类模板

// class定义存储于zend_class_entry
zend_class_entry *test_ce_myclass;

static const zend_function_entry test_methods[] = {
// 类方法使用宏指令PHP_ME
// public function hello()
PHP_ME(MyClass, hello, NULL, ZEND_ACC_PUBLIC)
// 与函数定义相同
PHP_FE_END
};

// 主菜,注册并加载类
static void test_init_myclass()
{
zend_class_entry ce;
INIT_CLASS_ENTRY(ce, "MyClass", test_methods);
// 注册MyClass类
test_ce_myclass = zend_register_internal_class(&ce);
// 添加属性/常量等
// public $success = true;
zend_declare_property_bool(test_ce_myclass, "success", sizeof("success")-1, 1, ZEND_ACC_PUBLIC);
}

// 类方法hello
PHP_METHOD(MyClass, hello)
{
RETURN_STRING("hello");
}

其中,函数test_init_myclass的最后一个参数ZEND_ACC_PUBLIC为访问控制标记之一公共访问,常用的访问控制标记还有以下几个

ZEND_ACC_STATIC
ZEND_ACC_PUBLIC
ZEND_ACC_PROTECTED
ZEND_ACC_PRIVATE
ZEND_ACC_CTOR
ZEND_ACC_DTOR
ZEND_ACC_DEPRECATED

一个class定义注册相关逻辑已经完成,要在PHP中使用类MyClass还需要在模块初始化MINIT中添加运行test_init_myclass以加载类MyClass

PHP_MINIT_FUNCTION(test)
{
test_init_myclass();
return SUCCESS;
}

编译test模块并开启后,运行var_dump(new MyClass());,将得到以下类似输出

object(MyClass)#1 (1) {
["success"]=>
bool(true)
}

我们也可以直接在C层面初始化并生成一个实例化类对象,增加一个工厂方法factory

static const zend_function_entry test_methods[] = {
PHP_ME(MyClass, hello, NULL, ZEND_ACC_PUBLIC)
PHP_ME(MyClass, factory, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
PHP_FE_END
};

PHP_METHOD(MyClass, factory)
{
object_init_ex(return_value, test_ce_myclass);
}

此时,我们可以使用MyClass::factory()获取一个新的MyClass对象

如果需要对MyClass进行一些操作,像在PHP使用构造方法,在test_methods里添加__construct,如此,PHP在new MyClass()将自动调用构造方法__construct

static const zend_function_entry test_methods[] = {
// ZEND_ACC_CTOR
PHP_ME(MyClass, __construct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
PHP_ME(MyClass, hello, NULL, ZEND_ACC_PUBLIC)
PHP_ME(MyClass, factory, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
PHP_FE_END
};

PHP_METHOD(MyClass, __construct)
{

}

调用类方法

上面,虽然对象在new时会自动调用__construct函数进行初始化,但factory不会自动调用构造函数,仅返回包含默认值的新对象,为此,我们需要在factory内调用构造方法

#include "zend_interfaces.h"

PHP_METHOD(MyClass, factory)
{
zval *myzval;

ZEND_PARSE_PARAMETERS_START(1,1)
Z_PARAM_ZVAL(myzval)
ZEND_PARSE_PARAMETERS_END();

object_init_ex(return_value, test_ce_myclass);

// zend_call_method,其他的还有
// zend_call_method_with_0_params
// zend_call_method_with_1_params
// zend_call_method_with_2_params
// ZEND_API zval* zend_call_method(zval *object_pp, zend_class_entry *obj_ce, zend_function **fn_proxy, const char *function_name, size_t function_name_len, zval *retval, int param_count, zval* arg1, zval* arg2);
zend_call_method(return_value, test_ce_myclass, NULL, "__construct", sizeof("__construct")-1, NULL, 1, myzval, NULL);
}

先前我们已经了解函数返回值return_value的使用,现在,我们来看如何在方法内访问$this,PHP提供getThis()函数

PHP_METHOD(MyClass, __construct)
{
char *msg;
size_t msg_len;

ZEND_PARSE_PARAMETERS_START(1,1)
Z_PARAM_STRING(msg, msg_len)
ZEND_PARSE_PARAMETERS_END();

zend_update_property_string(test_ce_myclass, getThis(), "msg", sizeof("msg")-1, msg);
}

关联结构体

我们构建一个结构体,这个结构体在PHP是无法访问的,但可以在扩展内访问对该结构体

// 还不是特别了解
typedef struct _test_struct {
zend_object std;
int unknown_id;
char *unknown_str;
} test_struct;

static zend_object *create_test_struct(zend_class_entry *class_type) {
test_struct *intern;

intern = ecalloc(1, sizeof(test_struct) + zend_object_properties_size(class_type));

zend_object_std_init(&intern->std, class_type);
object_properties_init(&intern->std, class_type);

intern->std.handlers = zend_get_std_object_handlers();

return &intern->std;
}

static void free_test_struct(void *object) {
test_struct *secrets = (test_struct*)object;
if (secrets->unknown_str) {
efree(secrets->unknown_str);
}
efree(secrets);
}

访问结构体test_struct

PHP_METHOD(MyClass, attachStruct) {
test_struct *secrets;

//
secrets = (test_struct*)getThis();

RETURN_LONG(secrets->unknown_id);
}

PHP的异常继承自Exception,所以下面除了新增一个异常类,还涉及了类的继承

#include <zend_exceptions.h>

zend_class_entry *test_ce_exception;

static void test_init_exception() {
zend_class_entry ce;
// 设置第三个参数为NULL,继承Exception所有方法,无自定义行为
INIT_CLASS_ENTRY(ce, "MyException", NULL);
//
test_ce_exception = zend_register_internal_class_ex(&ce, (zend_class_entry*)zend_exception_get_default());
}

PHP_MINIT_FUNCTION(test)
{
test_init_myclass();
//
test_init_exception();
return SUCCESS;
}

在方法/函数中抛出异常

PHP_METHOD(MyClass, throwExcept) {
zend_throw_exception(test_ce_exception, "custom exception throw", 1024);
}

php_ext_tutorial

PHP Extensions Made Eldrich: Classes


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK