为什么调用父类构造函数时会出现致命错误?

36

我正在扩展SPL(标准PHP库)类之一,但无法调用父类的构造函数。以下是我收到的错误:

致命错误:无法调用构造函数

这里是 SplQueue 文档的链接:http://www.php.net/manual/en/class.splqueue.php

这是我的代码:

$queue = new Queue();

class Queue extends SplQueue {

    public function __construct() {
        echo 'before';
        parent::__construct();
        echo 'I have made it after the parent constructor call';
    }

}

exit;

什么情况下会阻止我调用父类的构造函数?


1
只是出于好奇,你为什么要扩展队列类?装饰不能做到的事情是什么? - ircmaxell
5个回答

56

SplQueue继承自SplDoublyLinkedList,这两个类都没有定义自己的构造函数。因此,没有显式的父级构造函数可调用,从而导致错误发生。这个问题在文档中有点误导(就像许多SPL类一样)。

要解决这个错误,不要调用父级构造函数。


在大多数面向对象的语言中,如果一个类中没有声明显式的构造函数,那么通常会调用默认构造函数。但是,PHP类没有默认构造函数!只有在定义了构造函数时,类才会拥有构造函数。

事实上,使用反射分析stdClass类,我们甚至可以看到它也没有构造函数:

$c = new ReflectionClass('stdClass');
var_dump($c->getConstructor()); // NULL

尝试反射SplQueueSplDoublyLinkedList的构造函数都返回NULL

我猜测当你要求PHP实例化一个类时,它会执行所需的所有内部内存分配,然后查找构造函数定义并调用它,但只有在找到__construct()<class name>()的定义时才会这样做。我去看了一下源代码,似乎当它无法找到要调用的构造函数时,PHP就会出现问题并停止运行,因为你在子类中明确地告诉它这样做(请参见zend_vm_def.h)。


6
我喜欢 PHP。如果我决定给它添加构造函数,那么遍历父类的所有子类就会变得非常容易... - matt
1
@matt,无论如何你都必须这样做,即使有一个可选的默认构造函数。因为它是可选的,你的开发人员在某些情况下会调用它,在其他情况下则不会...或者你建议使用强制性的“默认构造函数”吗?强制性构造函数是一个巨大的问题... - Yevgeniy Afanasyev

29

通常情况下,当在 parent::__construct() 中引用的parent类实际上没有__construct()函数时,会抛出此错误。


我遇到了相同的错误,但是我的父类中确实有构造函数,你能告诉我可能的原因是什么吗? - Rohit Khatri
你确定你已经在父类中正确实现了带有2个下划线的__construct()函数吗? - Kristian
是的,它之前一直运行良好,但不知道突然发生了什么,它停止工作并持续抛出致命错误“无法调用构造函数”。 - Rohit Khatri
你确定这两个文件仍然被正确地加载为文件吗? - Kristian
是的,我非常确定。 - Rohit Khatri

3
如果你想调用最近祖先的构造函数,你可以使用class_parents循环遍历祖先,并使用method_exists检查其是否有构造函数。如果有,则调用构造函数;如果没有,则继续搜索下一个最近的祖先。这不仅可以防止覆盖父类的构造函数,还可以防止其他祖先的构造函数被覆盖(在父类没有构造函数的情况下)。
class Queue extends SplQueue {

  public function __construct() {
    echo 'before';

    // loops through all ancestors
    foreach(class_parents($this) as $ancestor) {

      // check if constructor has been defined
      if(method_exists($ancestor, "__construct")) {

        // execute constructor of ancestor
        eval($ancestor."::__construct();");

        // exit loop if constructor is defined
        // this avoids calling the same constructor twice
        // e.g. when the parent's constructor already
        // calls the grandparent's constructor
        break;
      }
    }
    echo 'I have made it after the parent constructor call';
  }

}

为了代码重用,您还可以将此代码编写为一个函数,该函数返回要eval的PHP代码:

// define function to be used within various classes
function get_parent_construct($obj) {

  // loop through all ancestors
  foreach(class_parents($obj) as $ancestor) {

    // check if constructor has been defined
    if(method_exists($ancestor, "__construct")) {

      // return PHP code (call of ancestor's constructor)
      // this will automatically break the loop
      return $ancestor."::__construct();";
    }
  }
}

class Queue extends SplQueue {

  public function __construct() {
    echo 'before';

    // execute the string returned by the function
    // eval doesn't throw errors if nothing is returned
    eval(get_parent_construct($this));
    echo 'I have made it after the parent constructor call';
  }
}

// another class to show code reuse
class AnotherChildClass extends AnotherParentClass {

  public function __construct() {
    eval(get_parent_construct($this));
  }
}

2
你可以像这样进行黑客攻击:
if (in_array('__construct', get_class_methods(get_parent_class($this)))) {
    parent::__construct();
}

但这也是无济于事。

每个类都要显式声明构造函数,这才是正确的行为。


2
我遇到了同样的错误。我通过在父类中定义一个空构造函数来解决它。这样其他类就不必定义它了。我认为这是更清晰的方法。
如果你仍然需要调用构造函数,可以这样做。
"最初的回答"
if (is_callable('parent::__construct')) {
    parent::__construct();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接