Отказ от ответственности: я не претендую на понимание внутренней работы Zend. Ниже приводится моя интерпретация исходного кода PHP, в значительной степени основанная на обоснованных предположениях. Несмотря на то, что я полностью уверен в заключении, терминология или детали могут быть ошибочными. Я хотел бы услышать от любого, у кого есть опыт работы с Zend, по этому вопросу.
Расследование
Из синтаксического анализатора PHP мы видим, что когда Обнаружено объявление класса. называется. Вот код, который обрабатывает объявление производные классы:
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;
Похоже, он генерирует коды операций, которые инициируют вызов< /a> снова в do_bind_inherited_class
-- но на этот раз значение compile_time
будет 0
(ложь).
Наконец, как насчет реализации class_exists
PHP-функция? Глядя на источник показывает этот фрагмент:
found = zend_hash_find(EG(class_table), name, len+1, (void **) &ce);
Здорово! Эта переменная class_table
— это та же переменная class_table
, которая задействована в вызове do_bind_inherited_class
, который мы видели ранее! Таким образом, возвращаемое значение class_exists
зависит от того, была ли запись для класса уже вставлена в class_table
к do_bind_inherited_class
.
Выводы
Компилятор Zend не работает с директивами include
во время компиляции (даже если имя файла жестко задано).
Если бы это было так, то не было бы причин выдавать фатальную ошибку повторного объявления класса, основанную на неустановленном флаге compile_time
; ошибка может быть выдана безоговорочно.
Когда компилятор встречает объявление производного класса, в котором базовый класс не был объявлен в том же файле скрипта, он отправляет процесс регистрации класса в своих внутренних структурах данных во время выполнения.
Это видно из последнего фрагмента кода выше, который устанавливает код операции ZEND_DECLARE_INHERITED_CLASS_DELAYED
для регистрации класса при выполнении скрипта. В этот момент флаг compile_time
будет равен false
, и поведение будет немного другим.
Возвращаемое значение class_exists
зависит от того, был ли класс уже зарегистрирован.
Поскольку это происходит по-разному во время компиляции и во время выполнения, поведение class_exists
также отличается:
- классы, все предки которых включены в один и тот же исходный файл, регистрируются во время компиляции; они существуют и могут быть созданы в любой момент этого скрипта
- классы, предок которых определен в другом исходном файле, регистрируются во время выполнения; до того, как виртуальная машина выполнит коды операций, соответствующие определению класса в исходном коде, эти классы не существуют для всех практических целей (
class_exists
возвращает false
, создание экземпляра дает фатальную ошибку)
person
Jon
schedule
27.09.2012
A
определен в том же файле, то BA (до) также существует. - person Jon   schedule 27.09.2012