Предположим, у нас есть два php-файла, a.php и b.php. Вот содержимое файла a.php:
<?php // content of a.php class A { }
И вот содержимое файла b.php
<?php // content of b.php include dirname(__FILE__) . "/a.php"; echo "A: ", class_exists("A") ? "exists" : "doesn't exist", "\n"; echo "B: ", class_exists("B") ? "exists" : "doesn't exist", "\n"; echo "BA (before): ", class_exists("BA") ? "exists" : "doesn't exist", "\n"; echo "BB: ", class_exists("BB") ? "exists" : "doesn't exist", "\n"; class B { } class BA extends A { } class BB extends B { } echo "BA (after): ", class_exists("BA") ? "exists" : "doesn't exist", "\n";
Если вы запустите скрипт b.php, вы получите следующий результат:
A: exists B: exists BA (before): doesn't exist BB: exists BA (after): exists
Почему класс BA существует только после определения класса? И почему другие классы существуют еще до их определения? В чем разница? Я бы ожидал иметь общее поведение в обоих случаях … Есть ли способ, которым я мог бы использовать класс BA еще до его определения?
спасибо
Michele
Отказ от ответственности: я не утверждаю, что понимаю внутреннюю работу Zend. Ниже приводится моя интерпретация источника PHP, которая в значительной степени подпитывается образованными догадками. Хотя я полностью уверен в заключении, терминология или детали могут быть отключены. Я бы хотел услышать от кого-нибудь, кто имеет опыт работы в этой области.
Из парсера PHP мы видим, что когда встречается объявление класса, zend_do_early_binding
функция zend_do_early_binding
. Вот код, который обрабатывает объявление производных классов:
case ZEND_DECLARE_INHERITED_CLASS: { zend_op *fetch_class_opline = opline-1; zval *parent_name; zend_class_entry **pce; parent_name = &CONSTANT(fetch_class_opline->op2.constant); if ((zend_lookup_class(Z_STRVAL_P(parent_name), Z_STRLEN_P(parent_name), &pce TSRMLS_CC) == FAILURE) || ((CG(compiler_options) & ZEND_COMPILE_IGNORE_INTERNAL_CLASSES) && ((*pce)->type == ZEND_INTERNAL_CLASS))) { if (CG(compiler_options) & ZEND_COMPILE_DELAYED_BINDING) { zend_uint *opline_num = &CG(active_op_array)->early_binding; while (*opline_num != -1) { opline_num = &CG(active_op_array)->opcodes[*opline_num].result.opline_num; } *opline_num = opline - CG(active_op_array)->opcodes; opline->opcode = ZEND_DECLARE_INHERITED_CLASS_DELAYED; opline->result_type = IS_UNUSED; opline->result.opline_num = -1; } return; } if (do_bind_inherited_class(CG(active_op_array), opline, CG(class_table), *pce, 1 TSRMLS_CC) == NULL) { return; } /* clear unnecessary ZEND_FETCH_CLASS opcode */ zend_del_literal(CG(active_op_array), fetch_class_opline->op2.constant); MAKE_NOP(fetch_class_opline); table = CG(class_table); break; }
Этот код сразу вызывает zend_lookup_class
чтобы узнать, существует ли родительский класс в таблице символов … и затем расходится в зависимости от того, найден ли родитель или нет.
Давайте сначала посмотрим, что он делает, если найден родительский класс:
if (do_bind_inherited_class(CG(active_op_array), opline, CG(class_table), *pce, 1 TSRMLS_CC) == NULL) { return; }
Переходя к do_bind_inherited_class
, мы видим, что последний аргумент (который в этом вызове равен 1
) называется compile_time
. Это звучит интересно. Что он делает с этим аргументом?
if (compile_time) { op1 = &CONSTANT_EX(op_array, opline->op1.constant); op2 = &CONSTANT_EX(op_array, opline->op2.constant); } else { op1 = opline->op1.zv; op2 = opline->op2.zv; } found_ce = zend_hash_quick_find(class_table, Z_STRVAL_P(op1), Z_STRLEN_P(op1), Z_HASH_P(op1), (void **) &pce); if (found_ce == FAILURE) { if (!compile_time) { /* If we're in compile time, in practice, it's quite possible * that we'll never reach this class declaration at runtime, * so we shut up about it. This allows the if (!defined('FOO')) { return; } * approach to work. */ zend_error(E_COMPILE_ERROR, "Cannot redeclare class %s", Z_STRVAL_P(op2)); } return NULL; } else { ce = *pce; }
Хорошо … поэтому он считывает имена родительских и производных классов либо из статического (с точки зрения PHP-пользователя), либо динамического контекста, в зависимости от состояния compile_time
. Затем он пытается найти запись класса («ce») в таблице классов, а если он не найден, то … он возвращается, не делая ничего во время компиляции, но испускает фатальную ошибку во время выполнения .
Это звучит чрезвычайно важно. Вернемся к zend_do_early_binding
. Что он делает, если родительский класс не найден?
if (CG(compiler_options) & ZEND_COMPILE_DELAYED_BINDING) { zend_uint *opline_num = &CG(active_op_array)->early_binding; while (*opline_num != -1) { opline_num = &CG(active_op_array)->opcodes[*opline_num].result.opline_num; } *opline_num = opline - CG(active_op_array)->opcodes; opline->opcode = ZEND_DECLARE_INHERITED_CLASS_DELAYED; opline->result_type = IS_UNUSED; opline->result.opline_num = -1; } return;
Кажется, что он генерирует коды операций, которые снова вызовут do_bind_inherited_class
но на этот раз значение compile_time
будет равно 0
(false).
Наконец, что касается реализации class_exists
PHP class_exists
? Глядя на источник, вы видите этот фрагмент:
found = zend_hash_find(EG(class_table), name, len+1, (void **) &ce);
Большой! Эта переменная class_table
является той же самой class_table
которая участвует в вызове do_bind_inherited_class
мы видели ранее! Таким образом, возвращаемое значение class_exists
зависит от того, была ли запись для класса уже вставлена в class_table
by do_bind_inherited_class
.
Компилятор Zend не выполняет директивы include
во время компиляции (даже если имя файла жестко запрограммировано).
Если бы это было так, то не было бы причин испускать факультативную ошибку повторной compile_time
класса, основанную на не установленном флаге compile_time
; ошибка может быть испущена безоговорочно.
Когда компилятор встречает объявление производного класса, где базовый класс не был объявлен в том же файле сценария, он толкает акт регистрации класса во внутренних структурах данных во время выполнения.
Это видно из последнего фрагмента кода выше, который устанавливает код операции ZEND_DECLARE_INHERITED_CLASS_DELAYED
для регистрации класса при выполнении сценария. В этот момент флаг compile_time
будет false
а поведение будет немного отличаться.
Возвращаемое значение class_exists
зависит от того, был ли класс уже зарегистрирован.
Поскольку это происходит по-разному во время компиляции и во время выполнения, поведение class_exists
также отличается:
class_exists
возвращает false
, экземпляр дает фатальную ошибку) Это просто, как для класса PHP handle во включенных файлах, include dirname(__FILE__) . "/a.php";
include dirname(__FILE__) . "/a.php";
BB
существует, потому что он расширяет B
который был определен в том же файле.
BA
не существует, потому что PHP не анализировал он-лайн он называется
Оба будут работать, возвращая тот же результат
Использование class BA extends B
include dirname(__FILE__) . "/a.php"; echo "BA (before): ", class_exists("BA") ? "exists" : "doesn't exist", "\n"; echo "BB: ", class_exists("BB") ? "exists" : "doesn't exist", "\n"; class B { } class BA extends B { } class BB extends B { } echo "BA (after): ", class_exists("BA") ? "exists" : "doesn't exist", "\n";
Или Определение class A
и использование class BA extends A
class A { } echo "<pre>"; echo "BA (before): ", class_exists("BA") ? "exists" : "doesn't exist", "\n"; echo "BB: ", class_exists("BB") ? "exists" : "doesn't exist", "\n"; class B { } class BA extends A { } class BB extends B { } echo "BA (after): ", class_exists("BA") ? "exists" : "doesn't exist", "\n";
Вывод
BA (before): exists BB: exists BA (after): exists
Вывод
FORM PHP DOC
Когда файл включен, код, который он содержит, наследует область изменения строки, в которой происходит включение. Любые переменные, доступные в этой строке в вызывающем файле, будут доступны в вызываемом файле с этой точки вперед. Однако все функции и классы, определенные во включенном файле, имеют глобальную область видимости.
Я думаю, что расширенные классы охватываются тем, что говорит PHP-документ, это можно рассматривать как BUG, который нужно исправить, но в основное время включите ваш класс, прежде чем вы вызовете или используете их