33

これに何度も出くわしましたが、理由がわからないので、興味をそそられました。宣言される前に動作するクラスもあれば、動作しないクラスもあります。

例 1

$test = new TestClass(); // top of class
class TestClass {
    function __construct() {
        var_dump(__METHOD__);
    }
}

出力

 string 'TestClass::__construct' (length=22)

例 2

クラスが別のクラスを拡張するか、インターフェイスを実装する場合

$test = new TestClass(); // top of class
class TestClass implements JsonSerializable {

    function __construct() {
        var_dump(__METHOD__);
    }

    public function jsonSerialize() {
        return json_encode(rand(1, 10));
    }
}

出力

Fatal error: Class 'TestClass' not found 

例 3

上記の同じクラスを試してみましょうが、位置を変更します

class TestClass implements JsonSerializable {

    function __construct() {
        var_dump(__METHOD__);
    }

    public function jsonSerialize() {
        return json_encode(rand(1, 10));
    }
}

$test = new TestClass(); // move this from top to bottom 

出力

 string 'TestClass::__construct' (length=22)

例 4 ( class_exists でもテストしました)

var_dump(class_exists("TestClass")); //true
class TestClass {

    function __construct() {
        var_dump(__METHOD__);
    }

    public function jsonSerialize() {
        return null;
    }
}

var_dump(class_exists("TestClass")); //true

実装するとすぐにJsonSerializable(またはその他)

var_dump(class_exists("TestClass")); //false
class TestClass implements JsonSerializable {

    function __construct() {
        var_dump(__METHOD__);
    }

    public function jsonSerialize() {
        return null;
    }
}

var_dump(class_exists("TestClass")); //true

オペコードもチェックwithout済み

line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
   3     0  >   SEND_VAL                                                 'TestClass'
         1      DO_FCALL                                      1  $0      'class_exists'
         2      SEND_VAR_NO_REF                               6          $0
         3      DO_FCALL                                      1          'var_dump'
   4     4      NOP                                                      
  14     5    > RETURN                                                   1

オペコードもチェックwith済み

line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
   3     0  >   SEND_VAL                                                 'TestClass'
         1      DO_FCALL                                      1  $0      'class_exists'
         2      SEND_VAR_NO_REF                               6          $0
         3      DO_FCALL                                      1          'var_dump'
   4     4      ZEND_DECLARE_CLASS                               $2      '%00testclass%2Fin%2FaDRGC0x7f563932f041', 'testclass'
         5      ZEND_ADD_INTERFACE                                       $2, 'JsonSerializable'
  13     6      ZEND_VERIFY_ABSTRACT_CLASS                               $2
  14     7    > RETURN                                                   1

質問

  • Example 3クラスが開始される前に宣言されたために機能したことはわかっていますがExample 1、そもそもなぜ機能するのでしょうか?
  • この拡張またはインターフェースのプロセス全体が PHP でどのように機能し、一方を有効にし、他方を無効にするのでしょうか?
  • 例 4 では正確に何が起こっているのでしょうか?
  • Opcodesclass_existsは物事を明確にするはずでしたが、以前に呼び出されたため、より複雑にTestClassしましたが、その逆です。
4

2 に答える 2

18

PHP クラス定義に関する記述が見つかりません。ただし、実験が示すユーザー定義関数とまったく同じだと思います。

以下の 2 つの例に示すように、関数が条件付きで定義されている場合を除き、関数を参照する前に定義する必要はありません。関数が条件付きで定義されている場合。その定義は、呼び出される前に処理する必要があります。

<?php

$makefoo = true;

/* We can't call foo() from here 
   since it doesn't exist yet,
   but we can call bar() */

bar();

if ($makefoo) {
  function foo()
  {
    echo "I don't exist until program execution reaches me.\n";
  }
}

/* Now we can safely call foo()
   since $makefoo evaluated to true */

if ($makefoo) foo();

function bar() 
{
  echo "I exist immediately upon program start.\n";
}

?>

これはクラスにも当てはまります。

  • 例 1が機能するのは、クラスが他の条件に依存していないためです。
  • 例 2は、クラスが を条件としているために失敗しますJsonSerializable
  • 例 3が機能するのは、クラスが呼び出される前に正しく定義されているためです。
  • 例 4は、クラスが条件付きであるため、最初は false になりますが、クラスがロードされているため、後で成功します。

クラスは、インターフェイスを実装するか、別のファイルから別のクラスを拡張することによって条件付きになります ( require)。定義が別の定義に依存するようになったため、条件付きと呼んでいます。

PHP インタープリターがこのファイル内のコードを最初に確認するとします。非条件クラスおよび/または関数を認識するため、先に進み、それらをメモリにロードします。いくつかの条件付きのものを見て、それらをスキップします。

次に、インタープリターは実行のためにページの解析を開始します。例 4 では、class_exists("TestClass")命令に到達し、メモリをチェックして、「いいえ、持っていません」と言います。それは条件付きだったのでそれを持っていない場合。命令の実行を継続し、条件付きクラスを参照して命令を実行し、クラスを実際にメモリにロードします。

次に、最後までドロップダウンしclass_exists("TestClass")、クラスが実際にメモリに存在することを確認します。

オペコードを読み取る際、TestClassは前に呼び出されませんclass_exist表示されるのは、値TestClassを送信しているSEND_VAL で、次の行のためにメモリ内にあり、実際に DO_FCALL を呼び出しますclass_exists

次に、クラス定義自体がどのように処理されているかを確認できます。

  1. ZEND_DECLARE_CLASS - これはクラス定義をロードしています
  2. ZEND_ADD_INTERFACE - これは JsonSerializable をフェッチし、それをクラス定義に追加します
  3. ZEND_VERIFY_ABSTRACT_CLASS - これは、すべてが正常であることを検証します。

PHP エンジンが最初のピーク時に単にクラスをロードするのを防いでいるように見えるのは、その 2 番目の部分であるZEND_ADD_INTERFACEです。

これらのシナリオで PHP インタープリターがコードをコンパイルおよび実行する方法についてより詳細な議論が必要な場合は、この質問に対する@StasMの 回答を参照することをお勧めします。彼は、この回答よりも詳細に優れた概要を提供しています。

私たちはあなたのすべての質問に答えたと思います。

ベスト プラクティス:各クラスを独自のファイルに配置し、必要に応じて自動ロードします。 @StasM回答で述べているように、賢明なファイル命名と自動ロード戦略 ( PSR-0など) を使用します。これを行うと、エンジンがそれらをロードする順序を気にする必要がなくなり、自動的に処理されます。

于 2013-03-28T22:47:31.670 に答える
5

基本的な前提は、クラスを使用するには定義する必要がある、つまりエンジンに認識させる必要があるということです。これは決して変更できません。あるクラスのオブジェクトが必要な場合、PHP エンジンはそのクラスが何であるかを知る必要があります。

ただし、エンジンがそのような知識を得る瞬間は異なる場合があります。まず、エンジンによる PHP コードの使用は、コンパイルと実行という 2 つの別個のプロセスで構成されます。コンパイル段階では、エンジンは PHP コードを一連のオペコード (既におなじみです) に変換します。第 2 段階では、プロセッサがメモリ内の命令を処理するようにエンジンがオペコードを処理し、それらを実行します。

オペコードの 1 つは新しいクラスを定義するオペコードで、通常はソース内のクラス定義と同じ場所に挿入されます。

ただし、コンパイラがクラス定義に遭遇すると、コードを実行する前に、エンジンに認識されているクラスのリストにクラスを入力できる場合があります。これを「早期バインディング」と呼びます。これは、クラス定義を作成するために必要なすべての情報が既にあるとコンパイラが判断した場合に発生する可能性があり、実際の実行時までクラスの作成を延期する理由はありません。現在、エンジンはクラスが次の場合にのみこれを行います:

  1. インターフェイスや特性が関連付けられていない
  2. 抽象的ではない
  3. クラスを拡張しないか、エンジンに既に認識されているクラスのみを拡張します。
  4. トップステートメントとして宣言されています(つまり、条件、関数などの内部ではありません)

この動作はコンパイラ オプションによって変更することもできますが、これらは APC などの拡張機能でのみ使用できるため、APC や同様の拡張機能を開発する予定がない限り、あまり気にする必要はありません。

これは、これで問題ないことも意味します。

 class B extends A {}
 class A { }

しかし、これはそうではありません:

 class C extends B {}
 class B extends A {}
 class A { }

A はアーリー バインドされるため、B の定義に使用できますが、B は 2 行目でのみ定義されるため、1 行目の C の定義には使用できません。

あなたの場合、クラスがインターフェースを実装したとき、それは事前にバインドされていなかったため、「クラス」ステートメントに到達した時点でエンジンに認識されました。インターフェースのない単純なクラスだった場合、事前にバインドされていたため、ファイルのコンパイルが完了するとすぐにエンジンに認識されました (この時点は、ファイルの最初のステートメントの前にあることがわかります)。

エンジンのこれらすべての奇妙な詳細に煩わされないようにするために、前の回答の推奨事項をサポートします。スクリプトが小さい場合は、使用する前にクラスを宣言するだけです。より大きなアプリケーションがある場合は、個々のファイルでクラスを定義し、賢明なファイル命名とオートロード戦略を用意してください。たとえば、PSR-0など、ケースに適したものです。

于 2013-04-05T21:29:39.810 に答える