前面已经了解了函数和参数,今天来了解一下类
例子
定义了一个 study_ext_class 类,里面只有一个 print 方法
类使用 PHP_ME和PHP_METHOD 宏,与方法最大的不同的地方是类需要注册
这里我写了一个 init_class 方法,PHP_MINIT_FUNCTION中调用,主要是需要注册类
/* {{{ PHP_MINIT_FUNCTION
*/
PHP_MINIT_FUNCTION(study)
{
/* If you have INI entries, uncomment these lines
REGISTER_INI_ENTRIES();
*/
init_class();
return SUCCESS;
}
/* }}} */
PHP_METHOD(study_ext_class,print)
{
php_printf("你调用了study_ext_class的print方法\n");
}
/* }}} */
const zend_function_entry study_class_method[]={
PHP_ME(study_ext_class,print,NULL,ZEND_ACC_PUBLIC)/* study_ext_class的print方法 */
PHP_FE_END
};
zend_class_entry *study_ce;
void init_class(){
zend_class_entry ce;
INIT_CLASS_ENTRY(ce, "study_ext_class" , study_class_method);
study_ce = zend_register_internal_class(&ce);
}
PHP_MINIT_FUNCTION
这是我们扩展启动时会执行的一个函数,所以在这里注册类
#define ZEND_MODULE_STARTUP_N(module) zm_startup_##module
#define INIT_FUNC_ARGS int type, int module_number
#define ZEND_MODULE_STARTUP_D(module) int ZEND_MODULE_STARTUP_N(module)(INIT_FUNC_ARGS)
//之后
int zm_startup_study(int type, int module_number);
PHP_METHOD
这两个宏和我们前面函数哪里的 PHP_FE ,PHP_FUNCTION 差不多
#define ZEND_MN(name) zim_##name
#define ZEND_METHOD(classname, name) ZEND_NAMED_FUNCTION(ZEND_MN(classname##_##name))
//PHP_METHOD 最终定义成了这样
void zim_study_ext_class_print(zend_execute_data *execute_data, zval *return_value);
PHP_ME
PHP_ME 相比原来的 PHP_FE 多了几个参数,主要是方法的属性和类名
#define ZEND_ME(classname, name, arg_info, flags) ZEND_FENTRY(name, ZEND_MN(classname##_##name), arg_info, flags)
flags是方法的属性,我们可以用 | 连接它们
/* method flags (types)方法类型 */
#define ZEND_ACC_STATIC 0x01
#define ZEND_ACC_ABSTRACT 0x02
#define ZEND_ACC_FINAL 0x04
#define ZEND_ACC_IMPLEMENTED_ABSTRACT 0x08
/* method flags (visibility)访问属性 */
/* The order of those must be kept - public < protected < private */
#define ZEND_ACC_PUBLIC 0x100
#define ZEND_ACC_PROTECTED 0x200
#define ZEND_ACC_PRIVATE 0x400
#define ZEND_ACC_PPP_MASK (ZEND_ACC_PUBLIC | ZEND_ACC_PROTECTED | ZEND_ACC_PRIVATE)
#define ZEND_ACC_CHANGED 0x800
#define ZEND_ACC_IMPLICIT_PUBLIC 0x1000
/* method flags (special method detection)构造函数和析构 */
#define ZEND_ACC_CTOR 0x2000
#define ZEND_ACC_DTOR 0x4000
/* method flag used by Closure::__invoke() */
#define ZEND_ACC_USER_ARG_INFO 0x80
/* method flag (bc only), any method that has this flag can be used statically and non statically. */
#define ZEND_ACC_ALLOW_STATIC 0x10000
类注册
完成了上面的,编译&安装,实例化我们的类是是会报错的,因为php不知道你有那些类
方法的话,在最后面通过 ZEND_GET_MODULE 生成了一个 get_module 的接口将方法进行了返回
zend_class_entry *study_ce;
void init_class(){
zend_class_entry ce;
INIT_CLASS_ENTRY(ce, "study_ext_class" , study_class_method);
study_ce = zend_register_internal_class(&ce);
}
INIT_CLASS_ENTRY
这一个宏的作用是生成这个类的结构,包括类的名称,方法,然后返回在ce这个变量中
//太长了,就只贴了一部分
#define INIT_OVERLOADED_CLASS_ENTRY(class_container, class_name, functions, handle_fcall, handle_propget, handle_propset) \
INIT_OVERLOADED_CLASS_ENTRY_EX(class_container, class_name, sizeof(class_name)-1, functions, handle_fcall, handle_propget, handle_propset, NULL, NULL)
#define INIT_CLASS_ENTRY(class_container, class_name, functions) \
INIT_OVERLOADED_CLASS_ENTRY(class_container, class_name, functions, NULL, NULL, NULL)
zend_register_internal_class
显而易见的是将我们的类的信息告诉php,注册进去,还有一个 zend_register_internal_class_ex 的函数,可以指定父类,然后这个函数返回我们的这个类的指针
构造和析构
函数列表哪里标记一下 ZEND_ACC_CTOR 或者 ZEND_ACC_DTOR 就好了
PHP_METHOD(study_ext_class,__construct)
{
php_printf("study_ext_class构造函数\n");
}
PHP_METHOD(study_ext_class,__destruct)
{
php_printf("study_ext_class析构函数\n");
}
const zend_function_entry study_class_method[]={
PHP_ME(study_ext_class,__construct,NULL,ZEND_ACC_CTOR)/* 构造 */
PHP_ME(study_ext_class,__destruct,NULL,ZEND_ACC_DTOR)/* 析构 */
PHP_ME(study_ext_class,print,NULL,ZEND_ACC_PUBLIC)/* study_ext_class的print方法 */
PHP_FE_END
};
类属性
在初始化注册类的时候,使用 zend_declare_property_* 给我们的类添加属性,还可以给他们赋予默认值
void init_class(){
zend_class_entry ce;
INIT_CLASS_ENTRY(ce, "study_ext_class" , study_class_method);
study_ce = zend_register_internal_class(&ce);
zend_declare_property_null(study_ce,"attr",sizeof("attr")-1,ZEND_ACC_PUBLIC);
zend_declare_property_long(study_ce,"num",sizeof("num")-1,100,ZEND_ACC_STATIC|ZEND_ACC_PUBLIC);
}
$a->attr="2333s";
echo '静态属性:study_ext_class::$num:'.study_ext_class::$num."\n";
echo '属性:study_ext_class::$attr:'.$a->attr."\n";
输出
静态属性:study_ext_class::$num:100
属性:study_ext_class::$attr:2333s
类指针和属性读取
上面写了怎么去定义属性,但是如果是在类里面要怎么使用属性呢?我们需要用一个 getThis 的宏来获取当前这个类的指针,我就偷懒直接在原来的 print 中添加了
zval *attr;
attr=zend_read_property(Z_OBJCE_P(getThis()),getThis(),"attr",sizeof("attr")-1,0,NULL);
php_var_dump(attr, 1);
if(Z_TYPE_P(attr)==IS_STRING){
php_printf("attr的值为:%s\n",attr->value.str->val);
}
zend_read_property
这个函数用于获取属性,还有zend_read_static_property
,用法相同,不过这个是获取静态的属性,关于更新属性可以使用zend_update_property
ZEND_API zval *zend_read_property(zend_class_entry *scope, zval *object, const char *name, size_t name_length, zend_bool silent, zval *rv);
第一个参数 scope 是这个类的指针,在之前的study_ce = zend_register_internal_class(&ce);
获取,不过也可以这样获取Z_OBJCE_P(getThis())
第二个参数 object 是当前的对象,我们可以用getThis
这个宏获取
第三个参数和第四个参数分别是 属性的名称和属性的长度
第五个参数 silent 用于是假设属性不存在的情况下是否报错
最后一个参数 rv 为魔术方法所返回的,如果不是魔术方法所返回的是一个NULL值,可以看我下面这个例子
zval *attr,*rv=NULL;
attr=zend_read_property(study_ce,getThis(),"attr",sizeof("attr")-1,0,rv);
if(Z_TYPE_P(attr)==IS_STRING){
php_printf("attr的值为:%s,%d\n",attr->value.str->val,rv);
}
getThis
获取对象指针,不多说了
#define EX(element) ((execute_data)->element)
#define getThis() ((Z_TYPE(EX(This)) == IS_OBJECT) ? &EX(This) : NULL)
类参数
其实和函数的参数一样,还有一个类似的zend_parse_method_parameters
我用的时候总是错误,还没明白这个函数是干什么的,而且找不到说明的资料=_=,后面附上两个源码的区别再看看
PHP_METHOD(study_ext_class,sum)
{
zend_long parma_num=0;
zval* this=getThis();
zval* static_num=zend_read_static_property(Z_OBJCE_P(this),"num",sizeof("num")-1,0);
if(zend_parse_parameters(ZEND_NUM_ARGS(),"l",&parma_num)==FAILURE){
RETURN_LONG(-1)
}
if(Z_TYPE_P(static_num)==IS_LONG){
RETURN_LONG(static_num->value.lval+parma_num)
}
RETURN_LONG(-1)
}
ZEND_BEGIN_ARG_INFO(sum_arg,0)
ZEND_ARG_INFO(0,num)
ZEND_END_ARG_INFO()
探究
如果我们的第二个参数this_ptr为NULL或者不是OBJECT类型的话,那么效果和zend_parse_parameters一样,我之前填的是this指针,所以跳到了else分支
else分之第一句就是p++;
表示字符串往后面移动一位,我填的参数是是一个单独的 l 然后一移动....没啦,后面还有两个va_arg
通过后面这两个得知,我们的两个参数,一个是 zval 的,一个是 zend_class_entry* 我们传入的 this_ptr 参数会赋值给 object 也就是我们后面的第四个参数,第五个是我们类的指针
object = va_arg(va, zval **);
ce = va_arg(va, zend_class_entry *);
*object = this_ptr;
看后面这一段,好像是校验类的,所以我觉得这个zend_parse_method_parameters
和zend_parse_parameters
的区别就在这里,method能够对类进行校验
if (ce && !instanceof_function(Z_OBJCE_P(this_ptr), ce)) {
zend_error_noreturn(E_CORE_ERROR, "%s::%s() must be derived from %s::%s",
ZSTR_VAL(Z_OBJCE_P(this_ptr)->name), get_active_function_name(), ZSTR_VAL(ce->name), get_active_function_name());
}
ZEND_API zend_bool ZEND_FASTCALL instanceof_function(const zend_class_entry *instance_ce, const zend_class_entry *ce) /* {{{ */
{
if (ce->ce_flags & ZEND_ACC_INTERFACE) {
return instanceof_interface(instance_ce, ce);
} else {
return instanceof_class(instance_ce, ce);
}
}
static zend_always_inline zend_bool instanceof_class(const zend_class_entry *instance_ce, const zend_class_entry *ce) /* {{{ */
{
while (instance_ce) {
if (instance_ce == ce) {//会循环校验父类是否相等
return 1;
}
instance_ce = instance_ce->parent;
}
return 0;
}
ZEND_API int zend_parse_parameters(int num_args, const char *type_spec, ...) /* {{{ */
{
va_list va;
int retval;
int flags = 0;
va_start(va, type_spec);
retval = zend_parse_va_args(num_args, type_spec, &va, flags);
va_end(va);
return retval;
}
/* }}} */
ZEND_API int zend_parse_method_parameters(int num_args, zval *this_ptr, const char *type_spec, ...) /* {{{ */
{
va_list va;
int retval;
int flags = 0;
const char *p = type_spec;
zval **object;
zend_class_entry *ce;
/* Just checking this_ptr is not enough, because fcall_common_helper does not set
* Z_OBJ(EG(This)) to NULL when calling an internal function with common.scope == NULL.
* In that case EG(This) would still be the $this from the calling code and we'd take the
* wrong branch here. */
zend_bool is_method = EG(current_execute_data)->func->common.scope != NULL;
if (!is_method || !this_ptr || Z_TYPE_P(this_ptr) != IS_OBJECT) {
va_start(va, type_spec);
retval = zend_parse_va_args(num_args, type_spec, &va, flags);
va_end(va);
} else {
p++;
va_start(va, type_spec);
object = va_arg(va, zval **);
ce = va_arg(va, zend_class_entry *);
*object = this_ptr;
if (ce && !instanceof_function(Z_OBJCE_P(this_ptr), ce)) {
zend_error_noreturn(E_CORE_ERROR, "%s::%s() must be derived from %s::%s",
ZSTR_VAL(Z_OBJCE_P(this_ptr)->name), get_active_function_name(), ZSTR_VAL(ce->name), get_active_function_name());
}
retval = zend_parse_va_args(num_args, p, &va, flags);
va_end(va);
}
return retval;
}
/* }}} */
使用
这里的type_spec我还加了一个O,因为在源码中,p++;
这里跳过了一个字符,那么我们后面retval = zend_parse_va_args(num_args, p, &va, flags);
的时候传入的就是 l 了, O 这里应该是可以乱填一个字符的
&this 又传回来了- -
PHP_METHOD(study_ext_class,sum)
{
zend_long parma_num=0;
zval* this=getThis();
zval* static_num=zend_read_static_property(Z_OBJCE_P(this),"num",sizeof("num")-1,0);
// zval
if(zend_parse_method_parameters(ZEND_NUM_ARGS(),this,"Ol",&this,study_ce,&parma_num)==FAILURE){
RETURN_LONG(-1)
}
if(Z_TYPE_P(static_num)==IS_LONG){
RETURN_LONG(static_num->value.lval+parma_num)
}
RETURN_LONG(-1)
}
文章评论