PHP+Swoole的閉包寫法
2016年10月25日韓 天峰
JS程序員總是嘲笑PHP沒有閉包喇喉,今天抽空寫一篇文章來專門介紹一下PHP的閉包。從5.3版本開始PHP就增加了匿名函數(shù)支持兵多,經(jīng)過數(shù)個(gè)版本迭代到現(xiàn)在的PHP5.6萧吠、PHP7品追,PHP語言的閉包已經(jīng)非常完善了。再結(jié)合Swoole提供的事件驅(qū)動(dòng)支持卒茬,PHP的閉包功能非常強(qiáng)大而且很優(yōu)雅。
匿名函數(shù)
匿名函數(shù)是閉包的核心咖熟,匿名函數(shù)在PHP里實(shí)際上是一個(gè)Closure類的對象(請注意是對象)圃酵。與普通的面向?qū)ο缶幊谭绞讲煌涿瘮?shù)的代碼是直接寫在調(diào)用處的馍管,不需要額外寫一個(gè)類郭赐,編寫方法的代碼。這樣的好處就是更直接确沸。下面的示例是設(shè)置一個(gè)定時(shí)器捌锭,每2秒輸出hello world俘陷。
傳統(tǒng)寫法
function timer () {
? ? echo "hello world";
}
Swoole\Timer::tick(2000, 'timer');
閉包寫法
Swoole\Timer::tick(2000, function () {
? ? echo "hello world";
});
非閉包的傳統(tǒng)寫法,先要聲明一個(gè)函數(shù)观谦,再轉(zhuǎn)入函數(shù)名稱字符串拉盾。兩段代碼是分離的,不夠直觀豁状。而閉包的寫法把定時(shí)器的聲明和定時(shí)器要執(zhí)行的代碼寫在了一起捉偏,邏輯非常清晰直觀。使用閉包語法可以很方便編寫回調(diào)函數(shù)泻红。在事件驅(qū)動(dòng)編程夭禽、排序、array_walk等需要用戶傳入一段執(zhí)行代碼的場景中承桥,閉包的寫法非常優(yōu)雅驻粟。
閉包更強(qiáng)大的地方在于它可以直接在調(diào)用處引入外部變量。PHP中實(shí)現(xiàn)的方法就是use關(guān)鍵詞凶异。
Use語法
如果剛才的定時(shí)器需要傳入一個(gè)變量蜀撑,傳統(tǒng)的寫法只能通過全局變量來實(shí)現(xiàn)。與JS不同剩彬,PHP的變量引入是顯式的酷麦,如果要引用外部變量必須使用use來聲明。而JS是隱式的喉恋,匿名函數(shù)內(nèi)部可以隨意操作外部變量沃饶,無需聲明。這樣好處是少寫了一點(diǎn)代碼轻黑,缺點(diǎn)是存在風(fēng)險(xiǎn)和混亂糊肤。
傳統(tǒng)寫法
$str = "hello world";
function timer () {
? ? global $str;
? ? echo $str;
}
Swoole\Timer::tick(2000, 'timer');
閉包寫法
$str = "hello world";
Swoole\Timer::tick(2000, function () use ($str) {
? ? echo $str;
});
閉包寫法使用use直接引入了當(dāng)前的$str變量,而不需要使用global全局變量氓鄙。另外如果是在swoole的事件驅(qū)動(dòng)編程模式馆揉,使用global就無法實(shí)現(xiàn)異步并發(fā)了,因?yàn)間lobal全局變量只有1個(gè)抖拦,如果同時(shí)有多個(gè)客戶端請求升酣,每個(gè)請求要查詢數(shù)據(jù)庫,輸出不同的內(nèi)容态罪,傳統(tǒng)的編程方法就不太容易實(shí)現(xiàn)噩茄,需要使用全局變量數(shù)組,以客戶端的ID為KEY保存各自的數(shù)據(jù)复颈。
傳統(tǒng)寫法
$requestArray = array();
$dbResultArray = array();
function my_request($request, $response) {
? ? global $dbResultArray, $requestArray;
? ? $queryId = $db->query($sql, 'get_result');
? ? $requestArray[$request->fd] = array($request, $response);
? ? $dbResultArray[$queryId] = $request->fd;
}
function get_result($queryId, $queryResult) {
? ? global $dbResultArray, $requestArray;
? ? list($request, $response) = $requestArray[$dbResultArray[$queryId]];
? ? $response->end($queryResult);
}
$server->on('request', 'my_request');
閉包寫法
$server->on('request', function ($request, $response) {
? ? $queryId = $db->query($sql, function ($queryId, $queryResult) use ($request, $response) {
? ? ? ? $response->end($queryResult);
? ? });
});
傳統(tǒng)的寫法非常復(fù)雜绩聘,需要反復(fù)多次從全局?jǐn)?shù)組保存/提取數(shù)據(jù)。而閉包的寫法非常簡潔優(yōu)雅,只用了幾行代碼就實(shí)現(xiàn)了同樣的功能君纫。閉包寫法非常適合用來編寫異步非阻塞回調(diào)模式的服務(wù)器程序驯遇。目前熱門的編程語言中只有PHP和JS具備這種能力。
閉包更多特性
在類的方法中使用匿名函數(shù)蓄髓,5.4以上的版本無需使用use引入$this叉庐,直接可以在匿名函數(shù)中使用$this來調(diào)用當(dāng)前對象的方法。在swoole編程中会喝,可以利用此特性減少$serv對象的use引入傳遞陡叠。
class Server extends Swoole\Server {
? ? function onReceive($serv, $fd, $reactorId, $data) {
? ? ? ? $db->query($sql, function ($queryId, $queryResult) use ($fd) {
? ? ? ? ? ? $this->send($fd, $queryResult);
? ? ? ? }
? ? }
}
另外如果希望在閉包函數(shù)中修改外部變量,可以在use時(shí)為變量增加&引用符號即可肢执。注意對象類型不需要加&枉阵,因?yàn)樵赑HP中對象默認(rèn)就是傳引用而非傳值。