与属性一样,子类可以继承父类的公有、受保护的方法,方法的继承比较复杂,因为会有访问控制、抽象类、接口、Trait等多种限制条件。实现上与前面几种相同,即父类的function_table合并到子类的function_table中。
首先是将子类function_table扩大,以容纳父子类全部方法,然后遍历父类function_table,逐个判断是否可被子类继承,如果可被继承则插入到子类function_table中。
if (zend_hash_num_elements(&parent_ce->function_table)) {
//扩展子类的function_table哈希表大小
zend_hash_extend(&ce->function_table,
zend_hash_num_elements(&ce->function_table) +
zend_hash_num_elements(&parent_ce->function_table), 0);
//遍历父类function_table,检查是否可被子类继承
ZEND_HASH_FOREACH_STR_KEY_PTR(&parent_ce->function_table, key, func) {
zend_function *new_func = do_inherit_method(key, func, ce);
if (new_func) {
_zend_hash_append_ptr(&ce->function_table, key, new_func);
}
} ZEND_HASH_FOREACH_END();
}
在合并的过程中需要对父类的方法进行一系列检查,最简单的情况就是父类中定义的方法在子类中不存在,这种情况比较简单,直接将父类的zend_function复制一份给子类。
static zend_function *do_inherit_method(zend_string *key, zend_function *parent, zend_class_entry *ce)
{
zval *child = zend_hash_find(&ce->function_table, key);
if(child){
//方法与子类冲突
...
}
//父子类方法不冲突,直接复制
return zend_duplicate_function(parent, ce);
}
当然这里不完全是复制:如果继承的父类是内部类则会硬拷贝一份zend_function结构(此结构的指针成员不复制);如果父类是用户自定义的类,且继承的方法没有静态变量则不会硬拷贝,而是增加zend_function的引用计数(zend_op_array.refcount)。
//func是父类成员方法,ce是子类
static zend_function *zend_duplicate_function(zend_function *func, zend_class_entry *ce)
{
zend_function *new_function;
if (UNEXPECTED(func->type == ZEND_INTERNAL_FUNCTION)) {
//内部函数
//如果子类也是内部类则会调用malloc分配内存(不会被回收),否则在zend内存池分配
...
}else{
if (func->op_array.refcount) {
(*func->op_array.refcount)++;
}
if (EXPECTED(!func->op_array.static_variables)) {
return func;
}
//硬拷贝
new_function = zend_arena_alloc(&CG(arena), sizeof(zend_op_array));
memcpy(new_function, func, sizeof(zend_op_array));
}
}
合并时另外一个比较复杂的情况是父类与子类中的方法冲突了,即子类重写了父类的方法,这种情况需要对父子类以及要合并的方法进行一系列检查,这一步在do_inheritance_check_on_method()
中完成,具体情况如下:
static void do_inheritance_check_on_method(zend_function *child, zend_function *parent)
{
uint32_t child_flags;
uint32_t parent_flags = parent->common.fn_flags;
...
}
(1)抽象子类的抽象方法与抽象父类的抽象方法冲突: 无法重写,Fatal错误。
abstract class B extends A {
abstract function test();
}
abstract class A
{
abstract function test();
}
============================
PHP Fatal error: Can't inherit abstract function A::test() (previously declared abstract in B)
判断逻辑:
//do_inheritance_check_on_method():
if ((parent->common.scope->ce_flags & ZEND_ACC_INTERFACE) == 0 //父类非接口
&& parent->common.fn_flags & ZEND_ACC_ABSTRACT //父类方法为抽象方法
&& parent->common.scope != (child->common.prototype ? child->common.prototype->common.scope : child->common.scope)
&& child->common.fn_flags & (ZEND_ACC_ABSTRACT|ZEND_ACC_IMPLEMENTED_ABSTRACT) //子类方法为抽象或实现了抽象方法
) {
zend_error_noreturn(E_COMPILE_ERROR, "Can't inherit abstract function %s::%s() (previously declared abstract in %s)",...);
}
(2)父类方法为final: Fatal错误,final成员方法不得被重写。 判断逻辑:
//do_inheritance_check_on_method():
if (UNEXPECTED(parent_flags & ZEND_ACC_FINAL)) {
zend_error_noreturn(E_COMPILE_ERROR, "Cannot override final method %s::%s()", ...);
}
(3)父子类方法静态属性不一致: 父类方法为非静态而子类的是静态(或相反),Fatal错误。
class A {
public function test(){}
}
class B extends A {
static public function test(){}
}
============================
PHP Fatal error: Cannot make non static method A::test() static in class B
判断逻辑:
//do_inheritance_check_on_method():
if (UNEXPECTED((child_flags & ZEND_ACC_STATIC) != (parent_flags & ZEND_ACC_STATIC))) {
zend_error_noreturn(E_COMPILE_ERROR,...);
}
(4)抽象子类的抽象方法覆盖父类非抽象方法: Fatal错误。
class A {
public function test(){}
}
abstract class B extends A {
abstract public function test();
}
============================
PHP Fatal error: Cannot make non abstract method A::test() abstract in class B
判断逻辑:
//do_inheritance_check_on_method():
if (UNEXPECTED((child_flags & ZEND_ACC_ABSTRACT) > (parent_flags & ZEND_ACC_ABSTRACT))) {
zend_error_noreturn(E_COMPILE_ERROR, "Cannot make non abstract method %s::%s() abstract in class %s",...);
}
(5)子类方法限制父类方法访问权限: Fatal错误,不允许派生类限制父类方法的访问权限,如父类方法为public,而子类试图重写为protected/private。
class A {
public function test(){}
}
class B extends A {
protected function test(){}
}
============================
PHP Fatal error: Access level to B::test() must be public (as in class A)
判断逻辑:
//do_inheritance_check_on_method():
//ZEND_ACC_PPP_MASK = (ZEND_ACC_PUBLIC | ZEND_ACC_PROTECTED | ZEND_ACC_PRIVATE)
if (UNEXPECTED((child_flags & ZEND_ACC_PPP_MASK) > (parent_flags & ZEND_ACC_PPP_MASK))) {
zend_error_noreturn(E_COMPILE_ERROR, "Access level to %s::%s() must be %s (as in class %s)%s", ...);
} else if (((child_flags & ZEND_ACC_PPP_MASK) < (parent_flags & ZEND_ACC_PPP_MASK))
&& ((parent_flags & ZEND_ACC_PPP_MASK) & ZEND_ACC_PRIVATE)) {
child->common.fn_flags |= ZEND_ACC_CHANGED;
}
(6)剩余检查情况: 除了上面5中情形下无法重写方法,剩下还有一步对函数参数的检查,这个过程我们整体看一下。
//do_inheritance_check_on_method():
if (UNEXPECTED(!zend_do_perform_implementation_check(child, parent))) {
...
zend_error(error_level, "Declaration of %s %s be compatible with %s", ZSTR_VAL(child_prototype), error_verb, ZSTR_VAL(method_prototype));
zend_string_free(child_prototype);
zend_string_free(method_prototype);
}
实际上zend_do_perform_implementation_check()
这个函数是用来检查一个方法是否实现了某抽象方法的,继承的时候遵循的也是这个规则,所以这里可以将父类方法理解为抽象方法,只有子类方法实现了该"抽象方法"才能重写父类方法。
static zend_bool zend_do_perform_implementation_check(const zend_function *fe, const zend_function *proto)
{
...
//如果检查的方法是__construct且父类方法不是interface和abstract则子类__construct覆盖父类的
if ((fe->common.fn_flags & ZEND_ACC_CTOR)
&& ((proto->common.scope->ce_flags & ZEND_ACC_INTERFACE) == 0
&& (proto->common.fn_flags & ZEND_ACC_ABSTRACT) == 0)) {
return 1;
}
//如果父类方法为私有方法则子类方法可以覆盖
if (proto->common.fn_flags & ZEND_ACC_PRIVATE) {
return 1;
}
//如果父类方法必传参数小于子类的或者父类的总参数大于子类的则不能覆盖
//如:
// 父类 public function test($a, $b = 3){}
// 子类 public function test($a, $b){}
if (proto->common.required_num_args < fe->common.required_num_args
|| proto->common.num_args > fe->common.num_args) {
return 0;
}
//可变函数,暂未理解这里的可变函数指哪类,忽略
...
//如果有定义的参数检查参数类型是否匹配,如果显式声明了参数类型则父子类方法必须匹配
for (i = 0; i < num_args; i++) {
zend_arg_info *fe_arg_info = &fe->common.arg_info[i];
if (!zend_do_perform_type_hint_check(fe, fe_arg_info, proto, proto_arg_info)) {
return 0;
}
//是否引用也必须一致
if (fe_arg_info->pass_by_reference != proto_arg_info->pass_by_reference) {
return 0;
}
}
//如果父类方法声明了返回值类型则子类方法必须声明且类型一致,相反如果子类声明了而父类无要求则可以
if (proto->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
if (!(fe->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE)) {
return 0;
}
if (!zend_do_perform_type_hint_check(fe, fe->common.arg_info - 1, proto, proto->common.arg_info - 1)) {
return 0;
}
}
}
这个判断过程还是比较复杂的,有些地方很难理解为什么设计,想了解完整过程的可以自行翻下代码。