免責事項: Zend の内部動作を理解しているとは言いません。以下は、PHP ソースの私の解釈です。大部分は経験に基づいた推測に基づいています。私は結論に完全に自信を持っていますが、専門用語や詳細が間違っている可能性があります。この件について、Zend 内部の経験のある方からのご連絡をお待ちしております。
調査
PHP パーサーから、クラス宣言に遭遇すると関数が呼び出されることがわかります。派生クラスの宣言を処理するコードは次のとおりです。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;
}
わかりました...compile_time
ステータスに応じて、静的コンテキスト (PHP ユーザーの観点から) または動的コンテキストのいずれかから、親クラスと派生クラスの名前を読み取ります。次に、クラス テーブルでクラス エントリ ("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
が、今回は、の値は(false)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
。
結論
include
Zend コンパイラは、コンパイル時に (ファイル名がハードコードされていても) ディレクティブを処理しません。
compile_time
もしそうなら、フラグが設定されていないことに基づいてクラス再宣言の致命的なエラーを発生させる理由はありません。エラーは無条件に発生する可能性があります。
コンパイラは、基本クラスが同じスクリプト ファイルで宣言されていない派生クラス宣言を検出すると、内部データ構造にクラスを登録する操作をランタイムにプッシュします。
ZEND_DECLARE_INHERITED_CLASS_DELAYED
これは、スクリプトの実行時にクラスを登録するオペコードを設定する上記の最後のコード スニペットから明らかです。その時点でcompile_time
フラグは になりfalse
、動作は微妙に異なります。
の戻り値はclass_exists
、クラスがすでに登録されているかどうかによって異なります。
これはコンパイル時と実行時に異なる方法で発生するため、 の動作class_exists
も異なります。
- 祖先がすべて同じソース ファイルに含まれているクラスは、コンパイル時に登録されます。それらは存在し、そのスクリプトの任意の時点でインスタンス化できます
- 別のソース ファイルで祖先が定義されているクラスは、実行時に登録されます。VM がソース内のクラス定義に対応するオペコードを実行する前に、これらのクラスはすべての実際的な目的のために存在しません (
class_exists
戻りますfalse
、インスタンス化すると致命的なエラーが発生します)