36

I am extending one of the SPL (Standard PHP Library) classes and I am unable to call the parent's constructor. Here is the error I am getting:

Fatal error: Cannot call constructor

Here is a link to the SplQueue's documentation: http://www.php.net/manual/en/class.splqueue.php

Here is my code:

$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;

What could prevent me from calling the parent's constructor?

hakre
  • 193,403
  • 52
  • 435
  • 836
Tonto McGee
  • 495
  • 1
  • 4
  • 6
  • 1
    Just out of curiosity, why are you extending the queue class? What do you need to do that decorating won't? – ircmaxell Jan 10 '11 at 19:26

5 Answers5

56

SplQueue inherits from SplDoublyLinkedList. Neither of these classes defines a constructor of its own. Therefore there's no explicit parent constructor to call, and you get such an error. The documentation is a little misleading on this one (as it is for many SPL classes).

To solve the error, don't call the parent constructor.


Now, in most object-oriented languages, you'll expect the default constructor to be called if there isn't an explicit constructor declared in a class. But here's the catch: PHP classes don't have default constructors! A class has a constructor if and only if one is defined.

In fact, using reflection to analyze the stdClass class, we see even that lacks a constructor:

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

Attempting to reflect the constructors of SplQueue and SplDoublyLinkedList both yield NULL as well.

My guess is that when you tell PHP to instantiate a class, it performs all the internal memory allocation it needs for the new object, then looks for a constructor definition and calls it only if a definition of __construct() or <class name>() is found. I went to take a look at the source code, and it seems that PHP just freaks out and dies when it can't find a constructor to call because you told it explicitly to in a subclass (see zend_vm_def.h).

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
  • 6
    I love PHP. It will be so easy to go through all sub classes of parent class when I decide to add constructor to it... – matt Nov 05 '13 at 21:12
  • 1
    @matt, you have to do it any way, even if there would be an optional default constructor. Because it is OPTIONAL, your developers would call it in some cases and they would not do it in other ones... Or do you suggest a mandatory "default constructor"? Mandatory constructor is a huge can of worms... – Yevgeniy Afanasyev Nov 26 '20 at 00:56
28

This error gets thrown, usually, when the parent class being referenced in parent::__construct() actually has no __construct() function.

Kristian
  • 21,204
  • 19
  • 101
  • 176
3

If you want to call the constructor of the nearest ancestor, you can loop through the ancestors with class_parents and check with method_exists if it has a constructor. If so, call the constructor; if not, continue your search with the next nearest ancestor. Not only do you prevent overriding the parent's constructor, but also that of other ancestors (in case the parent doesn't have a constructor):

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';
  }

}

For code reuse, you could also write this code as a function that returns the PHP code to be evaled:

// 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));
  }
}
Alexander Jank
  • 2,440
  • 2
  • 18
  • 19
2

You may hack it like this:

if (in_array('__construct', get_class_methods(get_parent_class($this)))) {
    parent::__construct();
}

but it's helpless.

just declare constructor explicitly for every class. it's the right behavior.

simonkuang
  • 21
  • 2
2

I got the same error. I have solved it by defining an empty constructor in the parent class. That way other classes don't have to define it. I think it's cleaner approach.

If you still need to call the constructor you can do this.

if (is_callable('parent::__construct')) {
    parent::__construct();
}
Svetoslav Marinov
  • 1,498
  • 14
  • 11