18

a.php と b.php という 2 つの 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クラスを定義する前でも使用できる方法はありますか?

ありがとうございました

ミケーレ

4

2 に答える 2

7

免責事項: 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

結論

includeZend コンパイラは、コンパイル時に (ファイル名がハードコードされていても) ディレクティブを処理しません。

compile_timeもしそうなら、フラグが設定されていないことに基づいてクラス再宣言の致命的なエラーを発生させる理由はありません。エラーは無条件に発生する可能性があります。

コンパイラは、基本クラスが同じスクリプト ファイルで宣言されていない派生クラス宣言を検出すると、内部データ構造にクラスを登録する操作をランタイムにプッシュします。

ZEND_DECLARE_INHERITED_CLASS_DELAYEDこれは、スクリプトの実行時にクラスを登録するオペコードを設定する上記の最後のコード スニペットから明らかです。その時点でcompile_timeフラグは になりfalse、動作は微妙に異なります。

の戻り値はclass_exists、クラスがすでに登録されているかどうかによって異なります。

これはコンパイル時と実行時に異なる方法で発生するため、 の動作class_existsも異なります。

  • 祖先がすべ​​て同じソース ファイルに含まれているクラスは、コンパイル時に登録されます。それらは存在し、そのスクリプトの任意の時点でインスタンス化できます
  • 別のソース ファイルで祖先が定義されているクラスは、実行時に登録されます。VM がソース内のクラス定義に対応するオペコードを実行する前に、これらのクラスはすべての実際的な目的のために存在しません (class_exists戻りますfalse、インスタンス化すると致命的なエラーが発生します)
于 2012-09-27T09:37:48.620 に答える
1

これは、インクルードファイルのPHPハンドルクラスと同じです。include dirname(__FILE__) . "/a.php";

BBB同じファイルで定義されたものが 拡張されるために存在します。

BAAPHPがオンラインで解析しなかったため、存在しません。

どちらも同じ結果を返します

使用する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

結論

PHPドキュメントを作成する

ファイルがインクルードされると、そのファイルに含まれるコードは、インクルードが発生する行の可変スコープを継承します。呼び出し元ファイルのその行で使用可能な変数は、その時点以降、呼び出されたファイル内で使用可能になります。ただし、インクルードされたファイルで定義されているすべての関数とクラスはグローバルスコープを持っています。

拡張クラスはPHPのドキュメントに記載されている内容でカバーされていると思います。これは、修正が必要なBUGとして扱うことができますが、主に、呼び出したり使用したりする前にクラスを含めてください。

于 2012-09-27T09:17:14.103 に答える