Я расширяю один из классов 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)); } }