月度归档:2010年11月

PHP扩展开发:简单类实现

PHP扩展开发:简单类实现
话说之前有写过如何在vs2008下开发PHP扩展,今天我们来实现一个简单类。
对于一个类,一般包括类定义,声明成员变量,声明成员函数,定义类常量,
对于类继承,重载,接口实现等不在这里说明。
首先我们看下这个简单类的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
<?php
/**
 * 简单类示例的PHP实现  2010-11-03 sz
 * @author phppan.p#gmail.com  http://www.phppan.com                               
 * 哥学社成员(http://www.blog-brother.com/)
 * @package test
 */
class Phppan {
 
    private $_name;
 
    CONST URL = 'http://www.phppan.com';
 
    public function __construct() {
    }
 
    public function getName() {
        return $this->_name;
    }
 
    public function setName($name) {
        $this->_name = $name;
    }
}

我们同样使用在之前创建的martin扩展
下面说明其实现过程:
1、声明类的成员函数
在php_martin.h文件中声明类的成员函数,如下所示代码:

1
2
3
4
5
 
PHP_METHOD(phppan, __construct);                                      
PHP_METHOD(phppan, __destruct);
PHP_METHOD(phppan, getName);
PHP_METHOD(phppan, setName);

2、定义类的成员函数
在martin.c文件中给出这4个函数的实现

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
 
 
/** {{{ 
*/
PHP_METHOD(phppan, __construct) {
 
}
/* }}} */
 
/** {{{ 
*/
PHP_METHOD(phppan, __destruct) {
}
/* }}} */
 
/** {{{ 
*/
PHP_METHOD(phppan, getName) {
	zval *self, *name;
 
	self = getThis();
 
	name = zend_read_property(Z_OBJCE_P(self), self, ZEND_STRL("_name"), 0 TSRMLS_CC);
 
	RETURN_STRING(Z_STRVAL_P(name), 0);
}
/* }}} */
 
/** {{{ 
*/
PHP_METHOD(phppan, setName) {
	char *arg = NULL;
	int arg_len;
	zval *value, *self;
 
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
 
	self = getThis();
 
	MAKE_STD_ZVAL(value);
	ZVAL_STRING(value, arg, arg_len, 0);
 
	SEPARATE_ZVAL_TO_MAKE_IS_REF(&value);
	zend_update_property(Z_OBJCE_P(self), self, ZEND_STRL("_name"), value TSRMLS_CC);
 
	RETURN_TRUE;
}
/* }}} */

3、注册这些函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
/* {{{ martin_functions[]
 *
 * Every user visible function must have an entry in martin_functions[].
 */
const zend_function_entry martin_functions[] = {
	PHP_FE(martin_echo,	NULL)		
	PHP_ME(phppan, __construct, 	NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR) 
	PHP_ME(phppan, __destruct,  	NULL, ZEND_ACC_PUBLIC|ZEND_ACC_DTOR) 
	PHP_ME(phppan, getName, 	 	 	NULL, ZEND_ACC_PUBLIC) 
	PHP_ME(phppan, setName, 	 	 	setName_args, ZEND_ACC_PUBLIC) 
	{NULL, NULL, NULL}	/* Must be the last line in martin_functions[] */
};
/* }}} */

4、在扩展模块初始化时初始化类,并声明成员变量和常量

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
zend_class_entry *phppan_ce;
 
/* 类方法的参数 */
ZEND_BEGIN_ARG_INFO(setName_args, 0)
	ZEND_ARG_INFO(0, name)
ZEND_END_ARG_INFO()
 
/* {{{ PHP_MINIT_FUNCTION
 */
PHP_MINIT_FUNCTION(martin)
{
	/* If you have INI entries, uncomment these lines 
	REGISTER_INI_ENTRIES();
	*/
	zend_class_entry phppan;
 
	INIT_CLASS_ENTRY(phppan, "Phppan", martin_functions);
	phppan_ce = zend_register_internal_class_ex(&phppan, NULL, NULL TSRMLS_CC);
 
 
	/* 声明常量URL */
	zend_declare_class_constant_stringl(phppan_ce, ZEND_STRL("URL"), ZEND_STRL("http://www.phppan.com") TSRMLS_CC);
 
	/* 声明私有成员变量 _name  */
	zend_declare_property_null(phppan_ce, ZEND_STRL("_name"), ZEND_ACC_PRIVATE TSRMLS_CC);
 
 
	return SUCCESS;
}
/* }}} */

以上为所有代码
在这些代码里面有一些东西需要说明一下:
1、类方法的定义与php的单个函数的定义一样,使用zend_function_entry结构数组,不同的是单个方法使用PHP_FE宏,
类方法的定义使用PHP_ME宏,在h文件中使用ZEND_METHOD声明,普通的函数使用ZEND_FUNCTION声明。
PHP_ME宏
定义在php.h中
#define PHP_ME ZEND_ME
#define ZEND_ME(classname, name, arg_info, flags) ZEND_FENTRY(name, ZEND_MN(classname##_##name), arg_info, flags)
2、在注册类时把该结构体作为参数交给相关的类注册方法即可
ZEND_BEGIN_ARG_INFO(setName_args, 0)
ZEND_ARG_INFO(0, name)
ZEND_END_ARG_INFO()
3、取this变量使用getThis();
4、使用zend_read_property读取类成员变量,返回的是zval 指针类型
5、使用zend_update_property更新类成员变量。
6、初始化类使用INIT_CLASS_ENTRY宏
7、注册类使用zend_register_internal_class_ex函数

function registration failed duplicate name.问题的解决方法:
这个由于我们在写这个简单类时偷了一下懒,将martin_functions作为模块的函数也将它作为了类的方法。
解决方法是替换上面的martin_functions数组,增加phppan_functions,并且在注册时使用phppan_functions,在模块的functions字段使用martin_functions

/* {{{ martin_functions[]
 *
 * Every user visible function must have an entry in martin_functions[].
 */
const zend_function_entry martin_functions[] = {
    {NULL, NULL, NULL}  /* Must be the last line in martin_functions[] */
};
 
const zend_function_entry phppan_functions[] = {
    PHP_ME(phppan, __construct,     NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
    PHP_ME(phppan, __destruct,      NULL, ZEND_ACC_PUBLIC|ZEND_ACC_DTOR)
    PHP_ME(phppan, getName,             NULL, ZEND_ACC_PUBLIC)
    PHP_ME(phppan, setName,             setName_args, ZEND_ACC_PUBLIC)
    {NULL, NULL, NULL}  /* Must be the last line in martin_functions[] */
};
/* }}} */
 
INIT_CLASS_ENTRY(phppan, "Phppan", phppan_functions);

PHP源码阅读笔记二十七:PHP对构造方法的识别

PHP源码阅读笔记二十七:PHP对构造方法的识别
众所周知,由于历史原因,PHP之前是使用类名作为构造函数,在PHP5中引入的新的构造函数__construct。为了实现向后兼容性,如果 PHP 5 在类中找不到 __construct() 函数,它就会尝试寻找旧式的构造函数,也就是和类同名的函数。因此唯一会产生兼容性问题的情况是:类中已有一个名为 __construct() 的方法,但它却又不是构造函数。
有如下一段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class Foo {
 
    public function Foo() {
 
    }
 
    private function __construct() {
 
    }
}
 
new Foo();
die();

此时,输出为:
Fatal error: Call to private Foo::__construct() from invalid context
此时,PHP识别出来的构造函数是__construct,因为是private,于是在外部调用出错。

好吧,我们从PHP的C源码中查找一下原因吧。
从spl的扩展类中直接查找类的定义开始:

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
spl_iterators.c 3228行 REGISTER_SPL_STD_CLASS_EX(IteratorIterator, spl_dual_it_new, spl_funcs_IteratorIterator);
///spl_functions.h 31行
#define REGISTER_SPL_STD_CLASS_EX(class_name, obj_ctor, funcs) \
	spl_register_std_class(&spl_ce_ ## class_name, # class_name, obj_ctor, funcs TSRMLS_CC);
//spl_functions.c 41行
PHPAPI void spl_register_std_class(zend_class_entry ** ppce, char * class_name, void * obj_ctor, const zend_function_entry * function_list TSRMLS_DC)
 
//spl_functions.c 2235行
ZEND_API zend_class_entry *zend_register_internal_class(zend_class_entry *orig_class_entry TSRMLS_DC) /* {{{ */
//调用do_register_internal_class函数
 
//zend_API.c 2169行
static zend_class_entry *do_register_internal_class(zend_class_entry *orig_class_entry, zend_uint ce_flags TSRMLS_DC) /* {{{ */
//调用
zend_register_functions(class_entry, class_entry->builtin_functions, &class_entry->function_table, MODULE_PERSISTENT TSRMLS_CC);
 
//zend_API.c 1795行
/* Look for ctor, dtor, clone
* If it's an old-style constructor, store it only if we don't have
* a constructor already.
*/
if ((fname_len == class_name_len) && !memcmp(lowercase_name, lc_class_name, class_name_len+1) && !ctor) {
	ctor = reg_function;
} else if ((fname_len == sizeof(ZEND_CONSTRUCTOR_FUNC_NAME)-1) && !memcmp(lowercase_name, ZEND_CONSTRUCTOR_FUNC_NAME, sizeof(ZEND_CONSTRUCTOR_FUNC_NAME))) {
	ctor = reg_function;
} 
 
scope->constructor = ctor;	//	在1961行 确认构造函数

以上代码为php5.3.0版本
从以上跟踪流程来看,程序在注册所有函数时,如果存在__construct(即ZEND_CONSTRUCTOR_FUNC_NAME)时,会覆盖class_name(类名)的构造函数,使其作为常规的成员函数存在。如下所示代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class Foo {
 
    public function Foo() {
        echo 'Foo';
    }
 
    public function __construct() {
        echo '__construct';
    }
}
 
$foo = new Foo();
$foo->Foo();

对于在前面的示例中的报错,我们可以在
zend/zend_object_handlers.c 1057行
ZEND_API union _zend_function *zend_std_get_constructor(zval *object TSRMLS_DC)
找到出处。

–EOF-