0%

PHP扩展开发 - 类

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

对象

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

1
2
3
4
5
6
7
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());,输出如下

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

创建一个类模板

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
// 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为访问控制标记之一公共访问,常用的访问控制标记还有以下几个

1
2
3
4
5
6
7
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

1
2
3
4
5
PHP_MINIT_FUNCTION(test)
{
test_init_myclass();
return SUCCESS;
}

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

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

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

1
2
3
4
5
6
7
8
9
10
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

1
2
3
4
5
6
7
8
9
10
11
12
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内调用构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#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);
}

this

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

1
2
3
4
5
6
7
8
9
10
11
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是无法访问的,但可以在扩展内访问对该结构体

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
// 还不是特别了解
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

1
2
3
4
5
6
7
8
PHP_METHOD(MyClass, attachStruct) {
test_struct *secrets;

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

RETURN_LONG(secrets->unknown_id);
}

异常

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#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;
}

在方法/函数中抛出异常

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

完整代码

php_ext_tutorial

参考文档

PHP Extensions Made Eldrich: Classes