PHP不是像Lisp那樣的函數式編程語言,更多的,PHP適合用C的風格來編寫代碼。PHP中沒有“函數”這種類型,也就是說,函數不能直接用變量來傳遞。比如下面的代碼:
function test() {
echo "welcome to bkjia.com";
}
$test1 = test;
echo gettype($test1);
//輸出string
PHP是這樣解析上面的代碼的。
//出現一個裸字符串,不以$符開頭,那麼就把它當成常量 //PHP將test當成一個常量,但代碼中並沒有test這一常量,接著PHP將常量名當成其值 $test1 = test; //當PHP遇到一個未定義常量時,就將它當成一個字符串值,如下 var_dump(UNDEFINED_CONSTANT); //將輸出string(18) "UNDEFINED_CONSTANT"
這表明PHP中的函數名在非調用的情況下(後面沒有括號)會被當成一個字符串。
而正巧PHP中有通過一個字符串名稱動態調用一個函數的方法:
$phpInfo = 'phpinfo';
$phpInfo();
//還有另外一種方式
call_user_func('phpinfo');
在C語言中可以通過指向函數的指針來將函數作為參數傳遞以實現高級的FP,而在PHP中,則是通過將函數名稱作為字符串傳遞,通過$fname()來調用 ($fname是一個字符串類型的變量,其值為函數名),或者通過call_user_func來調用。那麼到這裡時就會發現PHP中函數的實現所帶來的不便,因為函數是通過字符串來傳遞和調用的,那麼就要求不能有函數名稱相同的兩個不同函數的存在。即,PHP中的所有的函數(這裡所說的“函數”不包括方法)都是全局函數, 並且不允許在運行時重定義,因為如果允許重復定義函數(比如像大家熟悉的JavaScript那樣),下面的代碼就會出錯:
//第一次定義test1函數
function test1() {
return 123;
}
//將變量$testFunction賦值為test1的函數名稱
//事實上這種寫法較低效,這樣寫更好一些:$testFunction='test1';
$testFunction=test1;
//.......若干行代碼之後
//重新定義了test1
function test1() {
return 456;
}
//輸出與期望值不一樣了,因為變量裡只是保存的函數名而已
//而並不是真正的對函數的引用
echo $testFunction();
既然PHP中所有函數都是全局函數,那麼函數聲明就自然不能嵌套著寫了(指Closure),在討論PHP中的Closure與Lambda之前先討論一下PHP中的方法(即已經綁定到指定對象或類上的函數)。先創建一個簡單的類,將其靜態方法直接輸出看看是什麼內容。
class Demo {
public static function test() {
return 123;
}
}
var_dump(Demo::test);//到這一步出錯了
//因為PHP在解析Demo::test時,會將其看成是對Demo類的靜態屬性的讀取
//而這個靜態屬性又不以$開頭,那麼就將其看成靜態常量(const)
//不存在這個常量,就出錯了
那麼如果要動態的調用一個類(或實例)的方法該怎麼辦呢? 先上一個現實中的例子,一個非常簡單的Controller(當然真正的控制器不能這樣簡單)
class Controller {
public function login() {
echo "Login";
}
public function home() {
echo "Home";
}
public function logout() {
echo "Logout!!!";
}
}
$action=$_GET['action'];//要調用的方法名稱在action參數裡面
//接著要動態調用指定的方法
Controller::$action();//使用這種方法,這主要得益於PHP的“變量的變量”機制
//當然,要先檢查一下類有沒有這個方法,可以用PHP的反射機制檢測
$reflectionClass=new ReflectionClass('Controller');
$reflectionClass->hasMethod($action);//測試一個是否有對應的方法
//以及測試一下方法是否是靜態方法
$reflectionMethod=$reflectionClass->getMethod($action);
$reflectionMethod->isStatic();//是靜態方法則返回true
除了使用PHP的可變變量機制外,還可以使用之前的call_user_func函數。
//傳一個數組作為第一個參數,數組中第一個元素為類名,第二個元素為靜態方法名稱
call_user_func(array('Controller',$action));
//如果要向方法或函數傳遞參數,可以將參數附在後面
function test($a,$b) {
echo $a+$b;
}
call_user_func('test',123,456);
//或者在傳不定參數時,使用call_user_func_array函數
call_user_func_array('test',array(123,456));
//在PHP 4.1之前存在call_user_method函數及
//call_user_method_array專門用來調用對象的方法,但現在廢棄不用了
如果需要調用實例的方法的話,則為
class Demo {
public function test() {
echo "Instance Method";
}
}
$d=new Demo();
$d->$action();
//或者以這種方式調用
call_user_func(array($d,$action));
//如果類Demo沒有將構造函數聲明為私有的話,還仍然可以無需實例就調用實例方法
call_user_func(array('Demo',$action));
從上面的代碼可以看得出來,PHP中的類也是以字符串形式的名稱傳遞的。像下面的代碼:
$class=Demo; echo gettype($class);//string //原因和函數一樣,PHP將Demo當成一個普通的裸字符串了
到了PHP 5.2.3時,PHP中調用類的靜態方法還有另外一種語法。
//PHP 5.2.3
call_user_func('Demo::'.$action);
到了PHP 5.3開始,PHP引入了命名空間的概念,因此上面的調用將變成
call_user_func(__NAMESPACE__.'::Demo::'.$action);
看到上面的代碼,其實可以猜想,類的靜態方法不就是加了一個命名空間前綴的函數嘛。
在PHP 5.3之前,PHP中實現Lambda的語法非常別扭,要將函數中的代碼作為字符串傳遞給create_function方法去構造。
$lambda=create_function('$a','return addslashes(trim($a));');
//相當於構造這樣一個函數
function lambda1($a) {
return addslashes(trim($a));
}
這種對Lambda的支持真是雞肋得很,用起來反而降低了效率。
將代碼寫到字符串中很容易出錯,這種風格的lambda創建方式只適用於非常簡短的函數表達式,那再看看上面的$lambda變量中保存的什麼。
var_dump($lambda); //仍然是字符串類型,並且命名特點為"lambda_"加一個數字 //後面的數字為PHP系統全局計數器,保證運行不會重復
事實上用create_function創建的仍然是全局函數,只是這個全局函數的名字不會被猜到而已。
可能我們會嘗試創建這樣命名的函數:
function lambda_1() {
retunr 'Lambda!!!';
}
$lambda=create_function('','return 123;');
//如果你的PHP是系統啟動後第一次運行,那麼$lambda裡面的字符串看起來就和lambda_1一樣
//但執行代碼調仍然一點沒有錯誤
echo $lambda();//返回123;
echo lambda_1();//返回'Lambda!!!'
其實,create_function生成的函數名有些特殊,它是NULL字符加上"lambda_"再加個一個數字標識,而NULL字符我們是沒有辦法在聲明函數時使用這個字符的,因此上面的代碼不會沖突,可以用下面的代碼測試。
$lambda=create_function('','return 123;');
preg_match('|\d+|',$lambda,$matches);//匹配出lambda數字標識
//通lambda數字編號來調用些函數表達式
call_user_func("\x00lambda_{$matches[0]}");//在字符串前面加個NULL字符
//始終輸出123
使用create_function不但寫起來別扭,而且create_function方法只能創建一般的臨時函數而已。
並不能實現閉包(雖然如果只是讀取些標量的話可以變通一下實現,如下)
function outer() {
$a=123;
return create_function('$a='.$a,'return $a;');
}
$inner=outer();
echo $inner();
//PHP有個很不爽的地主就是不把函數調用當成表達式,這種代碼會出錯
//outer()();或getRow()[0];
而到了PHP 5.3,PHP開始支持真正的閉包,而且lambda更好使了,接近於JavaScript。
#!/usr/bin/php-5.3.0/php
$data=range(0,10);
//更接近於JavaScript的lambda語法
$newData=array_map(function ($v) {
$a=range(0,$v);
return array_sum($a);
},$data);
function outer() {
$a=123;
//語法上生疏些,需要將要在lambda中訪問的閉包變量列在use列表中
return function () use($a) {//閉包
echo $a;
};
}
$inner=outer();//只是不爽的地方就是,仍然不能這樣寫outer()();
$inner();
print_r($newData);
//PHP也提供了像Lisp中的map,reduce,walk方法用於函數式編程
//下面的代碼用於將$_REQUEST中的每一個值進行trim
array_walk('trim',$_REQUEST);
PHP閉包中變量引用的問題,下面的代碼工作起來和想像中有些差距。
function outer() {
$a=0;
return function () use($a) {
return $a++;
};
}
$inner=outer();
echo $inner();//outputs 0
echo $inner();//outputs 0
echo $inner();//outputs 0
因為PHP中的閉包,只是對原變量中值進行拷貝而已,也就是說,use中列出來的變量,在運行時都是閉包函數中的局部變量,而並不是函數外面的變量。
function outer() {
$a=0;
return function () use($a) {//大致相當於有這樣一個步驟:lambda1::$a=outer::$a
return $a++;
};
}
如果要達到想像中的狀態的話,只要在變量前加個按引用傳值標識就行了。
function outer() {
$a=0;
return function () use(&$a) {
return $a++;
};
}
$inner=outer();
echo $inner();//0
echo $inner();//1
echo $inner();//2
echo $inner();//3