Когда PHP должен видеть класс до его создания?

PHP не нуждается в форвардных объявлениях.

$core = new Core();
class Core {

Работает нормально

$test = new ParamSet(new IntParam(1));
echo($test->asString());

interface ParamType {
    /*snipped*/
}

class IntParam implements ParamType {
    /*snipped*/
}

class ParamSet implements ParamType {
    /*snipped*/
}

не получается, я получаю:

Fatal error: Class 'ParamSet' not found in

Не знаю, почему, я включил иерархию, потому что это важно. Перемещение теста $под определением заставляет его работать.

Ответ 1

Он рассматривался как bug (был ошибкой, когда этот пост был отправлен, но позже отмечен как не ошибка), кстати, благодаря этому вопросу, что "ошибка" была закрыта).

Вещь - даже class_exists() (т.е. функция стандартного индикатора) покажет, что ее на самом деле не существует:

//false, true
var_dump(class_exists('ParamSet'), class_exists('IntParam'));

interface ParamType {
    /*snipped*/
}

class IntParam  {
    /*snipped*/
}

class ParamSet implements ParamType {
    /*snipped*/
}

Решение. Да, создайте примеры экземпляров класса после в интерфейсе - и - даже - после определения класса.

Важное обновление

Собственно, в то время как bugs.php.net сказал, что это ошибка, после общения с внутренними структурами PHP - я понял, что это не ошибка (ну, определенно, не в смысле "ошибка" ). Это своего рода предполагаемое поведение.

И фактическая причина такова: реализация интерфейса превращает объявление класса в условное объявление. Таким образом, это будет оцениваться во время выполнения, а не во время интерпретации. Вот почему class_exists() и другие вещи не будут определять определение класса в таком случае. Поэтому, хотя решение для этого останется таким же (т.е. Создать экземпляр после объявления) - важно обновить, что я не был прав по всем причинам такого поведения.

Ответ 2

Alec, суть вашего вопроса связана с тем, как PHP реализует привязку констант класса, свойств и методов и т.д. вместе с наследованием. Источник PHP компилируется по требованию в исходном файле (или в случае с инструкциями класса eval) квантом в промежуточный код, известный как op_arrays, и именно этот op_array выполняется во время выполнения интерпретатором PHP.

Проблема в том, где и когда PHP выполняет эту привязку для данного класса или функции. (Связывание функций работает одинаково, поэтому, когда я отношусь к классу ниже, прочитайте это как класс или функцию взаимозаменяемо). По существу есть два варианта:

  • Первый заключается в том, чтобы связать класс в точке компиляции, и в этом случае он будет глобально отображаться сразу после инструкции include (или эквивалентной), которая запросила компиляцию - или, более конкретно, после выполнения код операции ZEND_INCLUDE_OR_EVAL, который этот оператор компилирует.

  • Вторая - знайте, что поздняя привязка - находится в точке последовательности выполнения инструкции класса.

В этом втором случае нужно немного больше объяснений. Если это необходимо, интерпретатор PHP компилирует класс с использованием специального искаженного имени и регистрирует это искаженное имя; измененное имя по дизайну выходит за пределы допустимых имен классов символов и, следовательно, не может быть напрямую вызвано и, следовательно, невидимо на уровне исходного приложения. Затем компилятор генерирует код операции DECLARE_CLASS в местоположении оператора класса в исходном потоке, чтобы переустановить измененное имя на имя истинного класса. Как только этот код операции DECLARE_CLASS будет выполнен, класс будет доступен для приложения.

Некоторая вещь всегда будет задерживать позднюю привязку, например, если вы загрузили и включили OPcache, это расширение устанавливает внутренний флаг Zend для принудительного позднего связывания для всех классов. Другой пример: если вы имеете объявление в условно исполняемом выражении, например, внутри функции или в блоке if, например:

if (!function_exists('fred') {
    function fred ($arg) {
       ...
    }
 }

Если вы думаете о классах, реализующих интерфейсы, интерфейс определяет набор методов, которые должен реализовать любой класс, реализующий интерфейс. Система времени выполнения PHP не может обеспечить выполнение этого требования, если интерфейс не связан перед любыми классами, которые его реализуют.


Теперь к приведенным ниже примерам: Core привязан во время компиляции в этом случае, поэтому класс уже доступен для следующего new. Однако ParamSet является поздним, а в качестве DECLARE_CLASS для ParamSet является точкой выполнения оператора new, класс по-прежнему остается несвязанным в последнем, тем самым поднимая ошибку. Это особенность языка и ясна как грязь, если вы не понимаете эти правила. Мое простое предложение:

Всегда использовать код, предполагающий, что классы и функции ограничены по срокам.

Это соглашение не наносит вреда, если PHP связывается во время компиляции, но обратное - то есть предполагать привязку времени компиляции - может приводить к ошибкам в зависимости от конфигурации времени выполнения.