Порядок расширений классов PHP в одном файле

#php #subclass

#php #подкласс

Вопрос:

В некоторых случаях неправильное определение расширений классов PHP приводит к фатальной ошибке, а в некоторых случаях — нет. Я пытаюсь понять базовое поведение.

Например, оба

 <?php
class BaseClass {}
class FirstExt extends BaseClass {}
  

и

 <?php
class FirstExt extends BaseClass {}
class BaseClass {}
  

все в порядке, так что это не тот случай, когда простое определение подклассов не по порядку вызывает проблему.

Однако ошибки возникают, когда задействованы три класса, но только в одном конкретном случае, а именно, когда цепочка классов определяется в обратном порядке. То есть следующий код приводит к фатальной ошибке:

 <?php
class SecondExt extends FirstExt {}
class FirstExt extends BaseClass {}
class BaseClass {}
  

Если вы попытаетесь запустить это из командной строки (скажем, как main.php ), вы получите

 PHP Fatal error:  Class 'FirstExt' not found in /path/to/main.php on line 2
  

Однако любой из других пяти порядков трех классов выполняется без ошибок. Я был весьма удивлен, что даже

 <?php
class SecondExt extends FirstExt {}
class BaseClass {}
class FirstExt extends BaseClass {}
  

работает нормально. Отличительным фактором является то, что все три возможные пары классов не в порядке в случае, который выдает ошибку, тогда как во всех остальных случаях не более двух из трех пар не в порядке.

Что происходит под капотом, чтобы вызвать такое поведение?

Комментарии:

1. На случай, если это может иметь значение, с какой версией PHP вы тестировали?

2. @Dave Это было с PHP 7.0.33 в Ubuntu 16.04.

3. @Dave PHP с 5.5.x по 7.3.x на x86 и x64 выдает эту фатальную ошибку.

4. Предполагая, что об этом еще не сообщалось, я бы сообщил об этом как об ошибке в PHP bugs.php.net

Ответ №1:

Поведение не интуитивно понятно, но я не думаю, что это ошибка, это просто эффект того, как PHP загружает классы.

Чтобы класс мог расширить родительский класс, родительский класс должен быть уже определен при определении дочернего класса.

По моим наблюдениям, похоже, что после анализа файла и начала выполнения определяются следующие классы:

  • встроенные классы
  • все пользовательские классы, определенные до анализа файла
  • пользовательские базовые классы в этом файле
  • пользовательские классы в этом файле, которые расширяют другой класс, уже определенный либо ранее в этом файле, либо до того, как этот файл был проанализирован

В принципе, любой класс, который может быть определен во время компиляции, будет определен, и любые другие классы, не определенные в этот момент, будут (попытаются быть) определены во время выполнения.

Итак, в этом примере:

 <?php
echo class_exists('A') ? "Yes" : "No";   // No
echo class_exists('B') ? "Yes" : "No";   // Yes
echo class_exists('C') ? "Yes" : "No";   // Yes

class A extends B {}
class C {}
class B extends C {}
  

класс B определяется, когда класс A пытается его расширить, потому что он был определен при анализе файла, поскольку класс C был определен перед ним в файле.

Но в этом примере:

 <?php
echo class_exists('A') ? "Yes" : "No";   // No
echo class_exists('B') ? "Yes" : "No";   // No
echo class_exists('C') ? "Yes" : "No";   // Yes

class A extends B {}
class B extends C {}
class C {}
  

класс B не определяется, когда класс A пытается его расширить, потому что он не был определен при анализе файла, поскольку класс C не был определен до него в файле.

PHP пытается найти его, но он не собирается повторно проверять тот же файл, он попытается загрузить его автоматически. Вот когда вы получаете «не найдено».

Добавьте четвертый класс, и вы увидите, что это происходит не только тогда, когда классы определены в обратном порядке:

 echo class_exists('A') ? "Yes" : "No";   // No
echo class_exists('B') ? "Yes" : "No";   // No
echo class_exists('C') ? "Yes" : "No";   // Yes
echo class_exists('D') ? "Yes" : "No";   // Yes

class A extends B {}
class D {}
class B extends C {}
class C extends D {}
  

Комментарии:

1. Спасибо за подробный ответ. Я бы просто указал, что «имеет объяснение» — это не то же самое, что «имеет смысл» 🙂

2. Ха-ха, да, это, вероятно, была не лучшая формулировка. Это определенно не интуитивно понятно. Я отредактирую ответ

3. Это имеет смысл.