Why am I getting Fatal error when calling a parent's constructor?
I am extending one of开发者_JAVA百科 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?
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
).
This error gets thrown, usually, when the parent
class being referenced in parent::__construct()
actually has no __construct()
function.
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 eval
ed:
// 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));
}
}
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.
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();
}
精彩评论