詳解PHP后期靜態(tài)綁定分析與應(yīng)用
基礎(chǔ)知識
1. 范圍解析操作符 (::)
- 可以用于訪問靜態(tài)成員,類常量,還可以用于覆蓋類中的屬性和方法。
- self,parent 和 static 這三個特殊的關(guān)鍵字是用于在類定義的內(nèi)部對其屬性或方法進行訪問的。
- parent用于調(diào)用父類中被覆蓋的屬性或方法(出現(xiàn)在哪里,就將解析為相應(yīng)類的父類)。
- self用于調(diào)用本類中的方法或?qū)傩裕ǔ霈F(xiàn)在哪里,就將解析為相應(yīng)的類;注意與$this區(qū)別,$this指向當(dāng)前實例化的對象)。
- 當(dāng)一個子類覆蓋其父類中的方法時,PHP 不會調(diào)用父類中已被覆蓋的方法。是否調(diào)用父類的方法取決于子類。
2. PHP內(nèi)核將類的繼承實現(xiàn)放在了"編譯階段"
<?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();
運行結(jié)果:
AA
AA
結(jié)論:
self::和parent::出現(xiàn)在某個類X的定義中,則將被解析為相應(yīng)的類X,除非在子類中覆蓋父類的方法。
3.Static(靜態(tài))關(guān)鍵字
作用:
- 在函數(shù)體內(nèi)的修飾變量的static關(guān)鍵字用于定義靜態(tài)局部變量。
- 用于修飾類成員函數(shù)和成員變量時用于聲明靜態(tài)成員。
- (PHP5.3之后)在作用域解析符(::)前又表示靜態(tài)延遲綁定的特殊類。
例子:
定義靜態(tài)局部變量(出現(xiàn)位置:局部函數(shù)中)
特征:靜態(tài)變量僅在局部函數(shù)域中存在,但當(dāng)程序執(zhí)行離開此作用域時,其值并不丟失。
<?php
function test()
{
static $count = 0;
$count++;
echo $count;
if ($count < 10) {
test();
}
$count--;
}
定義靜態(tài)方法,靜態(tài)屬性
a)聲明類屬性或方法為靜態(tài),就可以不實例化類而直接訪問。
b)靜態(tài)屬性不能通過一個類已實例化的對象來訪問(但靜態(tài)方法可以)
c)如果沒有指定訪問控制,屬性和方法默認(rèn)為公有。
d)由于靜態(tài)方法不需要通過對象即可調(diào)用,所以偽變量 $this 在靜態(tài)方法中不可用。
e)靜態(tài)屬性不可以由對象通過 -> 操作符來訪問。
f)用靜態(tài)方式調(diào)用一個非靜態(tài)方法會導(dǎo)致一個 E_STRICT 級別的錯誤。
g)就像其它所有的 PHP 靜態(tài)變量一樣,靜態(tài)屬性只能被初始化為文字或常量,不能使用表達式。所以可以把靜態(tài)屬性初始化為整數(shù)或數(shù)組,但不能初始化為另一個變量或函數(shù)返回值,也不能指向一個對象。
a.靜態(tài)方法例子(出現(xiàn)位置: 類的方法定義)
<?php
class Foo {
public static function aStaticMethod() {
// ...
}
}
Foo::aStaticMethod();
$classname = 'Foo';
$classname::aStaticMethod(); // 自PHP 5.3.0后,可以通過變量引用類
?>
b.靜態(tài)屬性例子(出現(xiàn)位置:類的屬性定義)
<?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.用于后期靜態(tài)綁定(出現(xiàn)位置: 類的方法中,用于修飾變量或方法)
下面詳細(xì)分析
后期靜態(tài)綁定(late static binding)
自 PHP 5.3.0 起,PHP 增加了一個叫做后期靜態(tài)綁定的功能,用于在繼承范圍內(nèi)引用靜態(tài)調(diào)用的類。
1.轉(zhuǎn)發(fā)調(diào)用與非轉(zhuǎn)發(fā)調(diào)用
轉(zhuǎn)發(fā)調(diào)用 :
指的是通過以下幾種方式進行的靜態(tài)調(diào)用:self::,parent::,static:: 以及 forward_static_call()。
非轉(zhuǎn)發(fā)調(diào)用 :
明確指定類名的靜態(tài)調(diào)用(例如Foo::foo())
非靜態(tài)調(diào)用(例如$foo->foo())
2.后期靜態(tài)綁定工作原理
原理:存儲了在上一個“非轉(zhuǎn)發(fā)調(diào)用”(non-forwarding call)中的類名。意思是當(dāng)我們調(diào)用一個轉(zhuǎn)發(fā)調(diào)用的靜態(tài)調(diào)用時,實際調(diào)用的類是上一個非轉(zhuǎn)發(fā)調(diào)用的類。
例子分析:
<?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(); //非轉(zhuǎn)發(fā)調(diào)用 ,進入test()調(diào)用后,“上一次非轉(zhuǎn)發(fā)調(diào)用”存儲的類名為C
*
* //當(dāng)前的“上一次非轉(zhuǎn)發(fā)調(diào)用”存儲的類名為C
* public static function test() {
* A::foo(); //非轉(zhuǎn)發(fā)調(diào)用, 進入foo()調(diào)用后,“上一次非轉(zhuǎn)發(fā)調(diào)用”存儲的類名為A,然后實際執(zhí)行代碼A::foo(), 轉(zhuǎn) 0-0
* parent::foo(); //轉(zhuǎn)發(fā)調(diào)用, 進入foo()調(diào)用后,“上一次非轉(zhuǎn)發(fā)調(diào)用”存儲的類名為C, 此處的parent解析為A ,轉(zhuǎn)1-0
* self::foo(); //轉(zhuǎn)發(fā)調(diào)用, 進入foo()調(diào)用后,“上一次非轉(zhuǎn)發(fā)調(diào)用”存儲的類名為C, 此處self解析為B, 轉(zhuǎn)2-0
* }
*
*
* 0-0
* //當(dāng)前的“上一次非轉(zhuǎn)發(fā)調(diào)用”存儲的類名為A
* public static function foo() {
* static::who(); //轉(zhuǎn)發(fā)調(diào)用, 因為當(dāng)前的“上一次非轉(zhuǎn)發(fā)調(diào)用”存儲的類名為A, 故實際執(zhí)行代碼A::who(),即static代表A,進入who()調(diào)用后,“上一次非轉(zhuǎn)發(fā)調(diào)用”存儲的類名依然為A,因此打印 “A”
* }
*
* 1-0
* //當(dāng)前的“上一次非轉(zhuǎn)發(fā)調(diào)用”存儲的類名為C
* public static function foo() {
* static::who(); //轉(zhuǎn)發(fā)調(diào)用, 因為當(dāng)前的“上一次非轉(zhuǎn)發(fā)調(diào)用”存儲的類名為C, 故實際執(zhí)行代碼C::who(),即static代表C,進入who()調(diào)用后,“上一次非轉(zhuǎn)發(fā)調(diào)用”存儲的類名依然為C,因此打印 “C”
* }
*
* 2-0
* //當(dāng)前的“上一次非轉(zhuǎn)發(fā)調(diào)用”存儲的類名為C
* public static function foo() {
* static::who(); //轉(zhuǎn)發(fā)調(diào)用, 因為當(dāng)前的“上一次非轉(zhuǎn)發(fā)調(diào)用”存儲的類名為C, 故實際執(zhí)行代碼C::who(),即static代表C,進入who()調(diào)用后,“上一次非轉(zhuǎn)發(fā)調(diào)用”存儲的類名依然為C,因此打印 “C”
* }
*/
故最終結(jié)果為:
A::foo()
A
A
parent::foo()
A
C
self::foo()
A
C
3.更多靜態(tài)后期靜態(tài)綁定的例子
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();
?>
運行結(jié)果:
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";
}
?>
運行結(jié)果:
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();
?>
運行結(jié)果:
Mango
Orange
應(yīng)用
前面已經(jīng)提到過了,引入后期靜態(tài)綁定的目的是:用于在繼承范圍內(nèi)引用靜態(tài)調(diào)用的類。
所以, 可以用后期靜態(tài)綁定的辦法解決單例繼承問題。
先看一下使用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);
運行結(jié)果:
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會得到什么樣的結(jié)果
<?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);
運行結(jié)果:
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時出現(xiàn)的單例繼承問題。
相關(guān)文章
詳解基于Vue cli生成的Vue項目的webpack4升級
這篇文章主要介紹了詳解基于Vue cli生成的Vue項目的webpack4升級,本文將詳細(xì)介紹從webpack3到webpack4的升級過程,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-06-06
前端使用JS內(nèi)置Blob實現(xiàn)下載各種形式的文件實例
通過使用JavaScript我們可以很方便地實現(xiàn)文件的下載功能,這篇文章主要給大家介紹了關(guān)于前端使用JS內(nèi)置Blob實現(xiàn)下載各種形式文件的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-06-06
使用Promise解決多層異步調(diào)用的簡單學(xué)習(xí)心得
下面小編就為大家?guī)硪黄褂肞romise解決多層異步調(diào)用的簡單學(xué)習(xí)心得。小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-05-05

