PHP該如何達成動態建立物件的method呢?目前有兩種方式 :
使用PHP 5.0的overload : __call()
使用PHP 5.3的closure : __invoke()
__call()
class Foo
{
public function __call($method, $args)
{
if(is_callable([$this, $method])) {
return call_user_func_array($this->$method, $args);
}
// else throw exception
}
}
$obj = new Foo('Sam');
$obj->say = function () {
return 'Hello World';
};
echo($obj->say());
// Result:
// Hello World
14行
$obj->say = function () {
return 'Hello World';
};
動態建立了say(),並且將closure直接指定給say(),到目前為止與JavaScript很像。
第3行
public function __call($method, $args)
{
if(is_callable([$this, $method])) {
return call_user_func_array($this->$method, $args);
}
// else throw exception
}
只可惜在PHP 5.0時代,要達成動態建立method,只能靠Method Overloading機制。
當17行$obj-say()試圖呼叫一個class沒有定義的method時,會出發__call(),此時我們必須先使用is_callable()判斷使用者呼叫的method是否存在,若存在,則使用call_user_func_array()轉呼叫剛剛制訂的closure。
雖然執行結果與JavaScript一樣,但還要另外實現__call()部分。
__invoke()
class Foo
{
}
$obj = new Foo('Sam');
$obj->say = function () {
return 'Hello World';
};
echo($obj->say->__invoke());
// Result:
// Hello World
第7行
$obj->say = function () {
return 'Hello World';
};
一樣動態建立了say(),並且將closure直接指定給say(),到目前為止與JavaScript很像。
10行
echo($obj->say->__invoke());
有趣的地方來了,在這裏我們將say並不是看成method,而是看成一個closure物件,__invoke()是closure物件自帶的method,可執行closure自己本身,也就是說,我們藉由呼叫該closure來達成類似動態建立method的需求。
這裡雖然觀念不一樣,因為語法與JavaScript相近,又不用事先實作__call(),不失為目前PHP最好的方法。
動態建立Method並存取Property
在物件導向世界裡,使用property儲存物件狀態天經地義,而method存取物件的property也是理所當然,之前動態建立method的範例都沒存取property反而顯得不切實際。
JavaScript
function Foo(name) {
this.name = name;
}
var obj = new Foo('Sam');
obj.say = function () {
return "Hello " + this.name;
};
print(obj.say());
// Result:
// Hello Sam
第6行
obj.say = function () {
return "Hello " + this.name;
};
因為say()變成obj物件的method,所以使用this.name存取property看似理所當然。
目前JavaScript這種寫法非常漂亮,也很容易理解。
PHP
class Foo
{
private $name;
function __construct($name)
{
$this->name = $name;
}
}
$obj = new Foo('Sam');
$obj->say = function() {
return "Hello " . $this->name;
};
$obj->say->__invoke();
// Result:
// Error
將JavaScript的寫法等效改寫成PHP。
13行
$obj->say = function() {
return "Hello " . $this->name;
};
我們模擬了JavaScript的this,改寫成PHP的$this。
實際執行得到了以下錯誤訊息 :
PHP Fatal error: Using $this when not in object context in /Users/oomusou/invoke_err.php on line 15
簡單的說,PHP的$this是指向say這個closure物件,而不是指向如JavaScript認為是$obj。
事實上,JavaScript的this原本也是指向closure物件,但因為closure動態成為obj的method後,this 自動指向obj了。
既然PHP的$this無法如JavaScript那樣自動改變,那PHP是否允許我們改用手動注入的方式改變$this呢?
PHP
class Foo
{
private $name;
function __construct($name)
{
$this->name = $name;
}
}
$obj = new Foo('Sam');
$cl = function() {
return "Hello " . $this->name;
};
$cl = $cl->bindTo($obj, $obj);
echo($cl());
// Result:
// Hello Sam
14行
$cl = function() {
return "Hello " . $this->name;
};
我們不再執著該closure一定要動態成為 $obj的method,但要存取$obj property的目標不變,程式也不變,一樣使用$this。
假如我們能將$obj以手動注入的方式,讓closure內部的$this改指向$obj,我們就能達到如JavaScript的效果了。
18行
$cl = $cl->bindTo($obj, $obj);
bindTo()如同__invoke()一樣,是closure物件內建的method,它的目的就是讓我們能手動注入一個物件,讓closure物件的$this指向手動注入的物件$obj。
因為在closure中我們有$this->name,經過bindTo()去手動注入 $obj後,$this已經改指向$obj,所以$this->name就相當於$obj->name。
根據bindTo()文件 :
若要讓closure物件只能存取其他物件的public變數,只傳第1個參數即可。
若要讓closure物件存取其他物件的private或protected變數,就要傳第2個參數。
bindTo()對於第2個參數的要求不嚴,有幾種傳法 :
傳進欲存取物件的class名稱,是字串。
傳進欲存取的物件也可以,bindTo()會自動得知該物件的class名稱。
在此就一併傳進與第一個參數相同的$obj。
19行
echo($cl());
因為$obj已經透過bindTo() 手動注入進$cl(),此時$this已經指向$obj,所以執行$cl()就可順利存$obj的property。
本文来自博客园,作者:孙龙-程序员,转载请注明原文链接:https://www.cnblogs.com/sunlong88/articles/8681385.html