后期静态绑定(Late Static Bindings)

从PHP 5.3.0开始,PHP实现了一个叫做后期静态绑定(late static bindings)的功能,用于引用在静态继承上下文中调用的类 。

更准确地说,后期静态绑定通过存储最后一个 "非转发调用"的类名工作.  对于静态方法调用, 这是显式命名的类(通常是在::运算符左侧的一个);对于非静态方法调用,是该对象的类。 一个“转发调用”是静态的通过self::,parent::,static::调用的,或者,如果上升到类层次结构,则是由forward_static_call()调用的. 函数 get_called_class()可以用来检索调用类的名称的字符串 并且 static:: 介绍了这个类的范围。

该功能从语言内部角度考虑被命名为”后期静态绑定“。”后期绑定“的意思是说,static::不再被解析为定义当前方法所在的类,而是在实际运行时计算的。也可以称之为”静态绑定“,因为它可以用于(但不限于)静态方法的调用。  

Late Static Bindings是在PHP5.3中加入的新特性,拼音来说,就是把本来在定义阶段固定下来的表达式或变量,改在执行阶段才决定。

php5.3中提出static::作用域, 希望指向最终的实现功能的类时,就用static::,这个限定符会在代码执行前立即计算出继承层中最后那个类的成员,这一过程叫做延迟绑定。

PHP的继承模型中有一个存在已久的问题,那就是在父类中引用子类的最终状态比较困难。该特性允许一个静态继承的上下文中对一个被调用类的引用。父类可以使用子类重载的静态方法。

static 类似于 self ,但它指的是被调用的类而不是包含类.

self 对类起的作用与 $this 对对象所其的作用并不完全相同,self 指的不是调用上下文,是指解析上下文.

static 关键字会在可能的最近时刻强迫 PHP 绑定到代码实现。没有 LSB, self::$class 会引用所找到的第一块代码:父类的版本。

self::的限制

使用self:: 或者 __CLASS__对当前类的静态引用,取决于定义当前方法所在的类:

Example #1 self:: 用法

<?php
class A {
    public static function who() {
        echo __CLASS__;
    }
    public static function test() {
        self::who();
    }
}

class B extends A {
    public static function who() {
        echo __CLASS__;
    }
}

B::test();
?>

上面示例输出:

A

后期静态绑定的用法

后期静态绑定试图通过引入一个关键字表示运行时最初调用的类来绕过限制。简单地说,这个关键字能够让你在上述例子中调用test()时引用的类是B而不是A。最终决定不引入新的关键字,而是使用已经预留的static关键字。

Example #2 static:: 简单用法

<?php
class A {
    public static function who() {
        echo __CLASS__;
    }
    public static function test() {
        static::who(); // 后期静态绑定从这里开始
    }
}

class B extends A {
    public static function who() {
        echo __CLASS__;
    }
}

B::test();
?>

上面示例输出:

B

Note:

在非静态上下文中,调用的类是这个类的对象实例。 因为$this-> 将试图从相同的作用域调用私有方法 ,使用static::可能会有不同的结果。另一个区别是static:: 只能引用静态属性。

Example #3  static::用法在一个非静态上下文

<?php
class A {
    private function foo() {
        echo "success!\n";
    }
    public function test() {
        $this->foo();
        static::foo();
    }
}

class B extends A {
   /* foo() 将被复制到B, 因此它的范围仍然是A
    * 并调用成功 */
}

class C extends A {
    private function foo() {
        /* 原始方法被替换; 新的范围是C */
    }
}

$b = new B();
$b->test();
$c = new C();
$c->test();   //失败
?>

上面示例输出:

success!
success!
success!

Fatal error:  Call to private method C::foo() from context 'A' in /tmp/test.php on line 9

Note:

 后期静态绑定的处理方式解决了以往完全没有办法解决的静态调用(Late static bindings' resolution will stop at a fully resolved static call with no fallback.)。另外一方面,如果静态调用使用 parent:: 或者 self:: 将转发调用信息。

Example #4 Forwarding and non-forwarding calls   转发和非转发调用

<?php
class A {
    public static function foo() {
        static::who();
    }

    public static function who() {
        echo __CLASS__."\n";
    }
}

class B extends A {
    public static function test() {
        A::foo();
        parent::foo();
        self::foo();
    }

    public static function who() {
        echo __CLASS__."\n";
    }
}
class C extends B {
    public static function who() {
        echo __CLASS__."\n";
    }
}

C::test();
?>

上面示例输出:

A
C
C

LSB, parent::/self:: forwarding

术语与概念

LSB 通过传递原始的调用信息给函数工作,然后使用static:: 或get_called_class获得使用的原始类名称。 如果一个fallback发生,当你调用一个只存在于一个父类中的静态方法,此信息才会有用。

<?php class A {
     public static function who() {
        return get_called_class();
    } }
  class B extends A {   }
  class C extends B {   }
  echo C::who(); // C ?>

现在,让我们说,您想要覆盖who在 B 中,但仍然依赖于 A 的 who():

<?php
  class A {
     public static function who() {
        return get_called_class();
  } }
  class B extends A {
     public static function who() {
         /* do stuff.. */   /* call A's who() */
   } }
  class C extends B {   }
  echo C::who(); // ??
?>

现在,这取决于你如何从B::who()调用A的who(), 调用 C::who(),你可能会得到不同的结果 :

  1. C, 如果调用是转发的
  2. A, 如果调用不是转发的

改变

以前, A::who()parent::who()将是非转发. 唯一的方法用转发去调用A的who()是用一个函数: forward_static_call().随着变化, 一个parent::A::之间的区别被引入, 那就是: parent:: 是转发的 而 A:: 不是. 基本上, 显式调用是非转发, 但是使用关键词调用是。

注意 self:: 也将被用于转发, 而 <nameoftheclass>:: 不会.

后期静态绑定(Late Static Bindings)
标签: