Почему я получаю фатальную ошибку при вызове конструктора родителя?

Я расширяю один из классов SPL (Standard PHP Library), и я не могу вызвать конструктор родителя. Вот ошибка, которую я получаю:

Неустранимая ошибка: невозможно вызвать конструктор

Вот ссылка на документацию 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; 

Что может помешать мне вызвать конструктор родителя?

SplQueue наследуется от SplDoublyLinkedList . Ни один из этих классов не определяет собственный конструктор. Поэтому нет явного родительского конструктора для вызова, и вы получаете такую ​​ошибку. Документация немного вводит в заблуждение на этом (как и для многих классов SPL).

Чтобы решить эту ошибку, не вызывайте родительский конструктор.


Теперь, на большинстве объектно-ориентированных языков, вы ожидаете, что конструктор по умолчанию будет вызываться, если в классе нет явного конструктора. Но вот улов: классы PHP не имеют конструкторов по умолчанию! Класс имеет конструктор тогда и только тогда, когда он определен .

Фактически, используя рефлексию для анализа класса stdClass , мы видим даже, что у него отсутствует конструктор:

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

Попытка отразить конструкторы SplQueue и SplDoublyLinkedList также дает NULL .

Я предполагаю, что когда вы указываете PHP на создание экземпляра класса, он выполняет все выделение внутренней памяти для нового объекта, затем ищет определение конструктора и вызывает его только в том случае, если определение __construct() или <class name>() . Я пошел посмотреть на исходный код, и кажется, что PHP просто волнуется и умирает, когда он не может найти конструктор для вызова, потому что вы явно сказали его в подклассе (см. zend_vm_def.h ).

Эта ошибка возникает, как правило, когда parent класс, на который ссылаются в parent::__construct() фактически не имеет функции __construct() .

Вы можете взломать его так:

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

но это беспомощно.

просто объявляйте конструктор явно для каждого класса. это правильное поведение.

Если вы хотите вызвать конструктор ближайшего предка, вы можете прокрутить предков с помощью class_parents и проверить с помощью метода_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 // eg when the parent's constructor already // calls the grandparent's constructor break; } } echo 'I have made it after the parent constructor call'; } } 

Для повторного использования кода вы также можете написать этот код как функцию, которая возвращает код PHP, который будет eval :

 // 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)); } }