PHP後期靜態綁定分析與應用

基礎知識

1. 範圍解析操做符 (::)

  • 能夠用於訪問靜態成員類常量,還能夠用於覆蓋類中的屬性和方法
  • self,parent 和 static 這三個特殊的關鍵字是用於在類定義的內部對其屬性或方法進行訪問的。
  • parent用於調用父類中被覆蓋的屬性或方法(出如今哪裏,就將解析爲相應類的父類)。
  • self用於調用本類中的方法或屬性(出如今哪裏,就將解析爲相應的類;注意與$this區別,$this指向當前實例化的對象)。
  • 當一個子類覆蓋其父類中的方法時,PHP 不會調用父類中已被覆蓋的方法。是否調用父類的方法取決於子類。

2. PHP內核將類的繼承實現放在了"編譯階段"

<?php
class A{
    const H = 'A';

    const J = 'A';

    static function testSelf(){
        echo self::H;  //在編譯階段就肯定了 self解析爲 A
    }
}

class B extends A{
    const H = "B";

    const J = 'B';

    static function testParent(){
        echo parent::J;  //在編譯階段就肯定了 parent解析爲A
    }

    /* 若重寫testSelf則能輸出「B」, 且C::testSelf()也是輸出「B」
    static function testSelf(){
        echo self::H;
    }
    */

}

class C extends B{
    const H = "C";

    const J = 'C';
}

B::testParent();
B::testSelf();

echo "\n";

C::testParent();
C::testSelf();

運行結果:php

AA
AA

結論:
self::和parent::出如今某個類X的定義中,則將被解析爲相應的類X,除非在子類中覆蓋父類的方法。數組

3.Static(靜態)關鍵字

做用:app

- 在函數體內的修飾變量的static關鍵字用於定義靜態局部變量。
- 用於修飾類成員函數和成員變量時用於聲明靜態成員。
- (PHP5.3以後)在做用域解析符(::)前又表示靜態延遲綁定的特殊類。

例子:函數

  1. 定義靜態局部變量(出現位置:局部函數中)
    特徵:靜態變量僅在局部函數域中存在,但當程序執行離開此做用域時,其值並不丟失。ui

    <?php
    function test()
    {
        static $count = 0;
    
        $count++;
        echo $count;
        if ($count < 10) {
            test();
        }
        $count--;
    }
  2. 定義靜態方法,靜態屬性
    a)聲明類屬性或方法爲靜態,就能夠不實例化類而直接訪問。
    b)靜態屬性不能經過一個類已實例化的對象來訪問(但靜態方法能夠)
    c)若是沒有指定訪問控制,屬性和方法默認爲公有。
    d)因爲靜態方法不須要經過對象便可調用,因此僞變量 $this 在靜態方法中不可用。
    e)靜態屬性不能夠由對象經過 -> 操做符來訪問。
    f)用靜態方式調用一個非靜態方法會致使一個 E_STRICT 級別的錯誤。
    g)就像其它全部的 PHP 靜態變量同樣,靜態屬性只能被初始化爲文字或常量,不能使用表達式。因此能夠把靜態屬性初始化爲整數或數組,但不能初始化爲另外一個變量或函數返回值,也不能指向一個對象。this

    -------------------------------------------->

    a.靜態方法例子(出現位置: 類的方法定義)code

    <?php
    class Foo {
        public static function aStaticMethod() {
            // ...
        }
    }
    
    Foo::aStaticMethod();
    $classname = 'Foo';
    $classname::aStaticMethod(); // 自PHP 5.3.0後,能夠經過變量引用類
    ?>
    -------------------------------------------->

    b.靜態屬性例子(出現位置:類的屬性定義)對象

    <?php
    class Foo
    {
        public static $my_static = 'foo';
    
        public function staticValue() {
            return self::$my_static;    //self 即 FOO類
        }
    }
    
    class Bar extends Foo
    {
        public function fooStatic() {
            return parent::$my_static; //parent 即 FOO類
        }
    }
    
    print Foo::$my_static . "\n";
    
    $foo = new Foo();
    print $foo->staticValue() . "\n";
    print $foo->my_static . "\n";      // Undefined "Property" my_static 
    
    print $foo::$my_static . "\n";
    $classname = 'Foo';
    print $classname::$my_static . "\n"; // As of PHP 5.3.0
    
    print Bar::$my_static . "\n";
    $bar = new Bar();
    print $bar->fooStatic() . "\n";
    ?>
    -------------------------------------------->

    c.用於後期靜態綁定(出現位置: 類的方法中,用於修飾變量或方法)
    下面詳細分析繼承

後期靜態綁定(late static binding)

自 PHP 5.3.0 起,PHP 增長了一個叫作後期靜態綁定的功能,用於在繼承範圍內引用靜態調用的類。作用域

1.轉發調用與非轉發調用

轉發調用 :

  • 指的是經過如下幾種方式進行的靜態調用:self::,parent::,static:: 以及 forward_static_call()。

非轉發調用 :

  • 明確指定類名的靜態調用(例如Foo::foo())
  • 非靜態調用(例如$foo->foo())

2.後期靜態綁定工做原理

原理:存儲了在上一個「非轉發調用」(non-forwarding call)中的類名。意思是當咱們調用一個轉發調用的靜態調用時,實際調用的類是上一個非轉發調用的類。

例子分析:

<?php
class A {
    public static function foo() {
        echo __CLASS__."\n";
        static::who();
    }

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

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

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

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

C::test();

/*
 * C::test();   //非轉發調用 ,進入test()調用後,「上一次非轉發調用」存儲的類名爲C
 *
 * //當前的「上一次非轉發調用」存儲的類名爲C
 * public static function test() {
 *      A::foo();  //非轉發調用, 進入foo()調用後,「上一次非轉發調用」存儲的類名爲A,而後實際執行代碼A::foo(),  轉 0-0
 *      parent::foo();  //轉發調用, 進入foo()調用後,「上一次非轉發調用」存儲的類名爲C, 此處的parent解析爲A ,轉1-0
 *      self::foo(); //轉發調用, 進入foo()調用後,「上一次非轉發調用」存儲的類名爲C, 此處self解析爲B, 轉2-0
 *  }
 *
 *
 * 0-0
 * //當前的「上一次非轉發調用」存儲的類名爲A
 * public static function foo() {
 *      static::who(); //轉發調用, 由於當前的「上一次非轉發調用」存儲的類名爲A, 故實際執行代碼A::who(),即static表明A,進入who()調用後,「上一次非轉發調用」存儲的類名依然爲A,所以打印 「A」
 *  }
 *
 * 1-0
 * //當前的「上一次非轉發調用」存儲的類名爲C
 * public static function foo() {
 *      static::who(); //轉發調用, 由於當前的「上一次非轉發調用」存儲的類名爲C, 故實際執行代碼C::who(),即static表明C,進入who()調用後,「上一次非轉發調用」存儲的類名依然爲C,所以打印 「C」

 *  }
 *
 * 2-0
 * //當前的「上一次非轉發調用」存儲的類名爲C
 * public static function foo() {
 *      static::who(); //轉發調用, 由於當前的「上一次非轉發調用」存儲的類名爲C, 故實際執行代碼C::who(),即static表明C,進入who()調用後,「上一次非轉發調用」存儲的類名依然爲C,所以打印 「C」
 *  }
 */


故最終結果爲:
A::foo()
A
A
parent::foo()
A
C
self::foo()
A
C

3.更多靜態後期靜態綁定的例子

a)Self, Parent 和 Static的對比

<?php
class Mango {
    function classname(){
        return __CLASS__;
    }

    function selfname(){
        return self::classname();
    }

    function staticname(){
        return static::classname();
    }
}

class Orange extends Mango {
    function parentname(){
        return parent::classname();
    }

    function classname(){
        return __CLASS__;
    }
}

class Apple extends Orange {
    function parentname(){
        return parent::classname();
    }

    function classname(){
        return __CLASS__;
    }
}

$apple = new Apple();
echo $apple->selfname() . "\n";
echo $apple->parentname() . "\n";
echo $apple->staticname();

?>




運行結果:
Mango
Orange
Apple

b)使用forward_static_call()

<?php
class Mango
{
    const NAME = 'Mango is';
    public static function fruit() {
        $args = func_get_args();
        echo static::NAME, " " . join(' ', $args) . "\n";
    }
}

class Orange extends Mango
{
    const NAME = 'Orange is';

    public static function fruit() {
        echo self::NAME, "\n";

        forward_static_call(array('Mango', 'fruit'), 'my', 'favorite', 'fruit');
        forward_static_call('fruit', 'my', 'father\'s', 'favorite', 'fruit');
    }
}

Orange::fruit('NO');

function fruit() {
    $args = func_get_args();
    echo "Apple is " . join(' ', $args). "\n";
}

?>




運行結果:
Orange is
Orange is my favorite fruit
Apple is my father's favorite fruit

c)使用get_called_class()

<?php


class Mango {
    static public function fruit() {
        echo get_called_class() . "\n";
    }
}

class Orange extends Mango {
    //
}

Mango::fruit();
Orange::fruit();

?>



運行結果:
Mango
Orange

應用

前面已經提到過了,引入後期靜態綁定的目的是:用於在繼承範圍內引用靜態調用的類。
因此, 能夠用後期靜態綁定的辦法解決單例繼承問題。

先看一下使用self是一個什麼樣的狀況:

<?php
// new self 獲得的單例都爲A。
class A
{
    protected static $_instance = null;

    protected function __construct()
    {
        //disallow new instance
    }

    protected function __clone(){
        //disallow clone
    }

    static public function getInstance()
    {
        if (self::$_instance === null) {
            self::$_instance = new self();
        }
        return self::$_instance;
    }
}

class B extends A
{
    protected static $_instance = null;
}

class C extends A{
    protected static $_instance = null;
}

$a = A::getInstance();
$b = B::getInstance();
$c = C::getInstance();

var_dump($a);
var_dump($b);
var_dump($c);




運行結果:
E:\code\php_test\apply\self.php:37:
class A#1 (0) {
}
E:\code\php_test\apply\self.php:38:
class A#1 (0) {
}
E:\code\php_test\apply\self.php:39:
class A#1 (0) {
}

經過上面的例子能夠看到,使用self,實例化獲得的都是類A的同一個對象

再來看看使用static會獲得什麼樣的結果

<?php
// new static 獲得的單例分別爲D,E和F。
class D
{
    protected static $_instance = null;

    protected function __construct(){}
    protected function __clone()
    {
        //disallow clone
    }

    static public function getInstance()
    {
        if (static::$_instance === null) {
            static::$_instance = new static();
        }
        return static::$_instance;
    }
}

class E extends D
{
    protected static $_instance = null;
}

class F extends D{
    protected static $_instance = null;
}

$d = D::getInstance();
$e = E::getInstance();
$f = F::getInstance();

var_dump($d);
var_dump($e);
var_dump($f);




運行結果:
E:\code\php_test\apply\static.php:35:
class D#1 (0) {
}
E:\code\php_test\apply\static.php:36:
class E#2 (0) {
}
E:\code\php_test\apply\static.php:37:
class F#3 (0) {
}

能夠看到,使用static能夠解決self時出現的單例繼承問題。

相關文章
相關標籤/搜索