其實PHP的Variable Functions就已經有一些彈性,讓一些需要使用自定函數傳給函數或方法使用時,比較有彈性。不過這樣還是需要在Global Scope定義一個函數,多少會增加一些干擾。
從PHP5.3開始,PHP開始支援匿名函數,讓一些需要彈性的場合更方便。
參考:
* PHP: Anonymous functions - Manual
* PHP: Callbacks - Manual
PHP的陣列函數,例如usort,可以讓使用者使用自定的函數來排序。在沒有匿名函數之前,需要使用Variable Functions的方式傳遞給usort:
<?php
$a = array(2,3,1,8,5,4,6,7);
function s($a, $b) {return $a-$b;}
print_r($a);
usort($a, 's');
print_r($a);
執行的結果:
Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ php 1-2a.php
Array
(
[0] => 2
[1] => 3
[2] => 1
[3] => 8
[4] => 5
[5] => 4
[6] => 6
[7] => 7
)
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
[5] => 6
[6] => 7
[7] => 8
)
有了匿名函數之後,直接把函數傳給usort就解決了:
<?php
$a = array(2,3,1,8,5,4,6,7);
print_r($a);
usort($a, function($a, $b){return $a-$b;});
print_r($a);
執行的結果一樣:
Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ php 1-2b.php
Array
(
[0] => 2
[1] => 3
[2] => 1
[3] => 8
[4] => 5
[5] => 4
[6] => 6
[7] => 7
)
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
[5] => 6
[6] => 7
[7] => 8
)
PHP在過去只有兩種變數的作用域,一個是全域,一個是函數中的區域。匿名函數除了可以傳遞給函數(或物件的方法)作為參數,同時還支援使用Closure。透過use($var...)這樣的宣告語法,就可以讓匿名函數取用外部的變數。有了Closure,便可以更有彈性地使用變數。
先來試做一個EventEmitter類別,透過這個類別,可以用on方法定義事件處理函數,然後可以用emit方法來觸發。(其實是模仿node.js的EventEmitter)
<?php
class EventEmitter {
public $events;
function on($event, $callback) {
if(!isset($this->events[$event])) {
$this->events[$event] = array();
}
$this->events[$event][] = $callback;
}
function emit($event, $payload) {
if(isset($this->events[$event])) {
foreach($this->events[$event] as $f) {
$f($payload);
}
}
}
}
class StringChuckSplit extends EventEmitter {
private $str='';
function __construct($str) {
$this->str = $str;
}
function split($delimiter) {
$result = explode($delimiter, $this->str);
foreach($result as $s) {
$this->emit('chuck', $s);
}
}
}
function test($t) {
$a = new StringChuckSplit('a,b,c,d,e,f,g');
$a->on('chuck', function($s)use($t){echo $t.$s."\n";});
$a->split(',');
}
test('* ');
test(' -');
StringChuckSplit這個類別,繼承了EventEmitter,他定義了一個split方法,能夠讓使用者用自定的delimiter來切割字串,並且將切割好的chucks傳給chuck事件處理函數。最後定義一個test函數,他接收一個字串做參數,然後使用StringChuckSplit把字串在,的地方切開,然後在每個chuck前prepend這個參數。執行的結果:
Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ php 1-2c.php
* a
* b
* c
* d
* e
* f
* g
- a
- b
- c
- d
- e
- f
- g
匿名函數還有一些適用的場合,是在把一些處理過程或是結果的資訊,委派給一個匿名函數處理(不過太複雜的話就不適合了),從而讓兩者可以解耦。例如:
<?php
class Human {
private $name;
function __construct($name) {
$this->name = $name;
}
function welcome($act) {
$act($this->name);
}
}
$me = new Human('Fillano');
$me->welcome(function($name){echo "Hello $name.\n";});
$me->welcome(function($name){echo "你好 $name 。\n";});
用這樣的作法,可以讓打招呼的方式到執行welcome方法時才用匿名函數決定。執行結果就是:
Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ php 1-2d.php
Hello Fillano.
你好 Fillano 。
簡單地說,匿名函數可以讓程式更有彈性,也可以避免在全域中定義太多的函數,避免衝突。不過究竟使用多了還是會讓程式的組織比較混亂,所以還是要考量使用的時機。
除了傳遞匿名函數,PHP5.4其實還定義了一個型別,叫做Callable,除了匿名函數,還可以把物件的方法當做Callback來使用。格式就是一個陣列,第一個元素是物件或物件變數名稱,第二個是方法名稱。例如:
<?php
class Human {
private $name;
function __construct($name) {
$this->name = $name;
}
function say() {echo "Hello ".$this->name.".\n";}
}
$a = new Human('Fillano');
function test($f) {$f();}
test(array($a, 'say'));
test(function(){echo "go home.\n";});
執行結果:
Feng-Hsu-Pingteki-MacBook-Air:~ fillano$ phpbrew switch php-5.4.20
Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ php 1-2e.php
Hello Fillano.
go home.
雖然有些東西其實不知道算不算是匿名函數,不過就先研究到這裡吧。
補充一個php5.4之後加入的功能,就是可以在Callbacks中使用$this。
<pre class="c" name="code">
<?php
<?php
class a {
private $name = 'Fillano';
function hello () {
echo "Hello ".$this->name.".\n";
}
}
class b {
private $name = "Hildegard";
function hello($f) {
$f();
}
}
$a = new a;
$b = new b;
$b->hello(array($a, 'hello'));
執行結果:
<pre class="c" name="code">
Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ php 1-2f.php
Hello Fillano.
看起來即使把方法傳遞給其他物件執行,方法中的$this還是指向自己,跟傳遞的對象沒有關係。