PHPにおけるself,static,$thisの違い&後期静的バインドの詳細

33113 ワード

self、staticと$thisの違い
self、static、および$thisの違いをよりよく理解するために、まず例を見てみましょう.

class A {

    protected $name = 'A';
    static $alias = 'a';
    const HASH = 'md5';

    public function dd() {
        echo $this->name; echo '--';
        echo static::$alias; echo '--';     //       
        echo static::HASH; echo '--';     //       
        echo self::$alias; echo '--';
        echo self::HASH; echo '--';

        var_dump(new self); echo '--';
        var_dump($this); echo '--';
        var_dump(new static); echo '
'
; // } public static function who() { echo __CLASS__; echo ' [ This is A ]'; echo '
'
; } public static function test() { self::who(); } public static function test2() { static::who(); // } public static function getInstance() { var_dump(new self); echo '--'; var_dump(new static); echo '
'
; // } } class B extends A { protected $name = 'B'; static $alias = 'b'; const HASH = 'sha1'; public static function who() { echo __CLASS__; echo ' [ This is B ]'; echo '
'
; } } class C extends B { public static function who() { echo __CLASS__; echo ' [ This is C]'; echo '
'
; } } (new A)->dd(); // A--a--md5--a--md5--object(A)#2 (1) { ["name":protected]=> string(1) "A" } --object(A)#1 (1) { ["name":protected]=> string(1) "A" } --object(A)#2 (1) { ["name":protected]=> string(1) "A" } (new B)->dd(); // B--b--sha1--a--md5--object(A)#2 (1) { ["name":protected]=> string(1) "A" } --object(B)#1 (1) { ["name":protected]=> string(1) "B" } --object(B)#2 (1) { ["name":protected]=> string(1) "B" } A::who(); // A [ This is A ] B::who(); // B [ This is B ] A::test(); // A [ This is A ] B::test(); // A [ This is A ] A::test2(); // A [ This is A ] B::test2(); // B [ This is B ] C::test2(); // C [ This is C] A::getInstance(); // object(A)#1 (1) { ["name":protected]=> string(1) "A" } --object(A)#1 (1) { ["name":protected]=> string(1) "A" } B::getInstance(); // object(A)#1 (1) { ["name":protected]=> string(1) "A" } --object(B)#1 (1) { ["name":protected]=> string(1) "B" }

まとめ:
  • selfおよび_CLASS__,いずれも、現在のメソッドが定義されているクラスに応じて、現在のクラスの静的参照です.つまりselfはどのクラスに書かれているのか、それは誰を引用しているのかということです.
  • $thisは、実際の呼び出し時のオブジェクトを指します.つまり、実際の実行中にクラスのプロパティまたはメソッドを呼び出した人、$thisはどのオブジェクトを指しますか.ただし、$thisはクラスの静的プロパティと定数にアクセスできません.$thisは静的メソッドには存在しません.
  • staticキーワードは、クラスの静的メンバー(属性およびメソッド)を宣言できるほか、後期の静的バインドとして非常に重要な役割を果たします.
  • selfは、クラスの静的プロパティ、静的メソッド、定数にアクセスするために使用できますが、selfは、selfの制限である現在定義されているクラスを指します.
  • $thisが指すオブジェクトが属するクラスはstaticが指すクラスと同じです.
  • staticは、静的または非静的メソッドに使用してもよいし、クラスの静的プロパティ、静的メソッド、定数および非静的メソッドにアクセスしてもよいが、非静的プロパティにはアクセスできない.
  • 静的呼び出し時、staticは実際の呼び出し時のクラスを指す.静的呼び出しでない場合、staticは実際の呼び出し時のオブジェクトが属するクラスを指します.

  • ポストスタティックバインド
    後続の静的バインディング(遅延静的バインディングとも呼ばれる)は、継承範囲内で静的呼び出しのクラス、すなわちコード実行時に最初に呼び出されたクラスを参照するために使用できます.
    後期静的バインドは,新しいキーワードを導入することによって表現しようとしたが,最終的にはstaticキーワードに沿った.
    さぎょうげんり
    正確には、static後期静的バインドの動作原理は、前の非転送呼び出し(non−forwarding call)のクラス名を格納することである.
    静的メソッド呼び出しを行う場合、クラス名(staticが指すクラス名)は、明示的に指定された(通常:演算子の左側部分)、すなわち実際に呼び出されたクラスである.
    上記の例のように、
    A::test2(); 
    B::test2();

    staticとselfの違い:
  • selfは、クラスの静的プロパティ、静的メソッド、定数にアクセスするために使用できますが、selfは、selfの制限である現在定義されているクラスを指します.
  • staticは、実際の呼び出し時のクラスを指すクラスの静的プロパティ、静的メソッド、定数にアクセスするためにも使用できます.

  • 非静的メソッド呼び出しを行う場合、クラス名(staticが指すクラス名)は、オブジェクトが属するクラス、すなわち実際に呼び出されたときのオブジェクトが属するクラスである.
    上記の例のように、
    (new A)->dd(); 
    (new B)->dd();

    staticと$thisは似ていますが、違いがあります.
  • $thisが指すオブジェクトが属するクラスはstaticが指すクラスと同じです.
  • $thisは静的メソッドには使用できません.クラスの静的プロパティと定数にもアクセスできません.
  • $thisは、実際に呼び出されたオブジェクトを指します.
  • staticは、静的または非静的メソッドに使用してもよいし、クラスの静的プロパティ、静的メソッド、定数および非静的メソッドにアクセスしてもよいが、非静的プロパティにはアクセスできない.
  • staticは、実際に呼び出されたときのオブジェクトが属するクラスを指します.

  • 転送コール(forwarding call)
    転送呼び出し(forwarding call)とは、self::,parent::,static::およびforward_static_call()のいくつかの方法で行われる静的呼び出しを指す.
    利用可能get_called_class()関数は、呼び出されたメソッドが存在するクラス名を取得します.
    次の4つの形式の呼び出しは、転送呼び出しです.
  • self::
  • parent::
  • static::
  • forward_static_call()

  • それ以外の呼び出しは、非転送呼び出しです.
    非転送呼び出し(non-forwarding call)
    後期静的バインドの動作原理は、前の非転送呼び出し(non-forwarding call)のクラス名を格納することである.
    特定のクラス名または特定のオブジェクトによる呼び出しは、非転送呼び出しです.
    例:
    A::test2(); 
    B::test2(); 
    
    (new A)->dd(); 
    (new B)->dd();

    注意事項
    非静的環境でのプライベートメソッドの検索順序
    非静的環境では、クラスの非静的メソッドで$thisとstaticを使用してクラスのプライベートメソッドを呼び出す場合、実行方法が異なります.
  • $thisは、定義された範囲(親)内のプライベートメソッドを優先的に探し、存在する場合は呼び出します.
  • staticは、先に指向するクラス(サブクラス)にプライベートメソッドを探します.見つかったらエラーが発生します.プライベートメソッドは定義されたクラスの内部でしか呼び出されないためです.見つからない場合は、定義された範囲(親)でプライベートメソッドを探して、存在する場合は呼び出します.
  • 具体的には、$thisは、定義された範囲内でプライベートメソッドを探し、その指定されたオブジェクトが属するクラスでプライベートメソッドを探し、次にパブリックメソッドを探し、最後に定義された範囲内でパブリックメソッドを探します.一致するメソッドが見つかったら呼び出し、検索を停止します.
    staticはまずその指すクラスの中で私有の方法を探して、それから共有の方法を探します;次に、定義された範囲内でプライベートメソッドを探し、共有メソッドを探します.一致するメソッドが見つかったら呼び出し、検索を停止します.
    次に例を示します.
    
     class  A  {
        private function  foo () {
            var_dump($this); echo '--';
            var_dump(new static); echo '--';
    
            echo __CLASS__; echo '--';
            echo get_called_class();
            echo '
    '
    ; } public function test () { $this -> foo (); static:: foo (); echo '
    '
    ; } } class B extends A { } class C extends A { private function foo () { echo 'this is C'; } } (new B())->test(); (new C())->test();

    出力結果:
    object(B)#1 (0) { } --object(B)#2 (0) { } --A--B
    object(B)#1 (0) { } --object(B)#2 (0) { } --A--B
    
    object(C)#1 (0) { } --object(C)#2 (0) { } --A--C
    
    Fatal error: Uncaught Error: Call to private method C::foo() from context 'A'

    後期静的バインドの解析について
    後期静的バインドの解析は,完全に解析された静的呼び出しを取得するまで行われる.静的呼び出しがparent::またはself::などの転送呼び出しの形式を使用すると、呼び出し情報が転送されます.
    
    class  A  {
        public static function  foo () {
            static:: who ();
        }
    
        public static function  who () {
            echo  __CLASS__ . "
    "
    ; } } class B extends A { public static function test () { A :: foo (); parent :: foo (); self :: foo (); static::foo(); forward_static_call(['A', 'foo']); echo '
    '
    ; } public static function who () { echo __CLASS__ . "
    "
    ; } } class C extends B { public static function who () { echo __CLASS__ . "
    "
    ; } public static function test2() { self::test(); } } class D extends C { public static function who () { echo __CLASS__ . "
    "
    ; } } B::foo(); B::test(); C::foo(); C::test(); D::foo(); D::test2();

    以上の出力結果は、
    B A B B B B 
    C A C C C C 
    D A D D D D

    static後期静的バインドの動作原理は、前の非転送呼び出し(non-forwarding call)のクラス名を格納することです.この文を覚えておいてください.
    次の例は非転送呼び出しです.
    A::foo();  //    A
    
    B::foo();   //    B
    
    C::foo();   //    C

    後期静的バインドstaticは,foo()メソッドでどのクラスが非転送呼び出しの形式でfoo()メソッドを呼び出すか,foo()メソッドでのstaticがどのクラスを指すかを定義する.
    ただし、呼び出しを転送する形式でfoo()メソッドを呼び出すと、次のようになります.
    parent :: foo ();
    self :: foo ();
    static::foo();
    forward_static_call(['A', 'foo']);

    では,転送呼び出しコードが存在するメソッドtest()に準じて,どのクラスが非転送呼び出しの形式でtest()メソッドを呼び出し,foo()メソッドのstaticがどのクラスを指すかを示す.
    test()メソッドを呼び出す場合、転送呼び出しの形式も採用されます.
    public static function test2() {
        self::test();
    }

    ではtest 2()メソッドに準じて...順次類推する.
    すなわち,後期静的バインディングを用いたベースクラスでは,後期静的バインディングが存在するメソッドが呼び出しを転送されるとstaticの指向は,非転送呼び出しの形式に遭遇するまで遡上される.