詳解PHP後期靜態繫結分析與應用,詳解php靜態繫結
基礎知識
1. 範圍解析操作符 (::)
- 可以用於訪問靜態成員,類常量,還可以用於覆蓋類中的屬性和方法。
- self,parent 和 static 這三個特殊的關鍵字是用於在類定義的內部對其屬性或方法進行訪問的。
- parent用於調用父類中被覆蓋的屬性或方法(出現在哪裡,就將解析為相應類的父類)。
- self用於調用本類中的方法或屬性(出現在哪裡,就將解析為相應的類;注意與$this區別,$this指向當前執行個體化的對象)。
- 當一個子類覆蓋其父類中的方法時,PHP 不會調用父類中已被覆蓋的方法。是否調用父類的方法取決於子類。
2. PHP核心將類的繼承實現放在了"編譯階段"
<?phpclass 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();
運行結果:
AA
AA
結論:
self::和parent::出現在某個類X的定義中,則將被解析為相應的類X,除非在子類中覆蓋父類的方法。
3.Static(靜態)關鍵字
作用:
- 在函數體內的修飾變數的static關鍵字用於定義靜態局部變數。
- 用於修飾類成員函數和成員變數時用於聲明靜態成員。
- (PHP5.3之後)在範圍解析符(::)前又表示靜態延遲綁定的特殊類。
例子:
定義靜態局部變數(出現位置:局部函數中)
特徵:靜態變數僅在局部函數域中存在,但當程式執行離開此範圍時,其值並不丟失。
<?phpfunction test(){ static $count = 0; $count++; echo $count; if ($count < 10) { test(); } $count--;}
定義靜態方法,靜態屬性
a)聲明類屬性或方法為靜態,就可以不執行個體化類而直接存取。
b)靜態屬性不能通過一個類已執行個體化的對象來訪問(但靜態方法可以)
c)如果沒有指定存取控制,屬性和方法預設為公有。
d)由於靜態方法不需要通過對象即可調用,所以偽變數 $this 在靜態方法中不可用。
e)靜態屬性不可以由對象通過 -> 操作符來訪問。
f)用靜態方式調用一個非靜態方法會導致一個 E_STRICT 層級的錯誤。
g)就像其它所有的 PHP 靜態變數一樣,靜態屬性只能被初始化為文字或常量,不能使用運算式。所以可以把靜態屬性初始化為整數或數組,但不能初始化為另一個變數或函數傳回值,也不能指向一個對象。
a.靜態方法例子(出現位置: 類的方法定義)
<?phpclass Foo { public static function aStaticMethod() { // ... }}Foo::aStaticMethod();$classname = 'Foo';$classname::aStaticMethod(); // 自PHP 5.3.0後,可以通過變數引用類?>
b.靜態屬性例子(出現位置:類的屬性定義)
<?phpclass 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.0print 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)中的類名。意思是當我們調用一個轉寄調用的靜態調用時,實際調用的類是上一個非轉寄調用的類。
例子分析:
<?phpclass 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()AAparent::foo()ACself::foo()AC
3.更多靜態後期靜態繫結的例子
a)Self, Parent 和 Static的對比
<?phpclass 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();?>運行結果:MangoOrangeApple
b)使用forward_static_call()
<?phpclass 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 isOrange is my favorite fruitApple is my father's favorite fruit
c)使用get_called_class()
<?phpclass Mango { static public function fruit() { echo get_called_class() . "\n"; }}class Orange extends Mango { //}Mango::fruit();Orange::fruit();?>運行結果:MangoOrange
應用
前面已經提到過了,引入後期靜態繫結的目的是:用於在繼承範圍內引用靜態調用的類。
所以, 可以用後期靜態繫結的辦法解決單例繼承問題。
先看一下使用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時出現的單例繼承問題。