iT邦幫忙

DAY 3
5

逐步提昇PHP技術能力系列 第 3

逐步提昇PHP技術能力 - PHP的語言特性 : 匿名函數 / Closure / Callable

其實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.

雖然有些東西其實不知道算不算是匿名函數,不過就先研究到這裡吧。


上一篇
逐步提昇PHP技術能力 - PHP的語言特性 : 多載 (overloading)
下一篇
逐步提昇PHP技術能力 - PHP的語言特性 : Traits
系列文
逐步提昇PHP技術能力30

1 則留言

0
fillano
iT邦超人 1 級 ‧ 2013-10-03 10:18:43

補充一個php5.4之後加入的功能,就是可以在Callbacks中使用$this。

&lt;pre class="c" name="code">
&lt;?php
&lt;?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'));

執行結果:

&lt;pre class="c" name="code">
Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ php 1-2f.php 
Hello Fillano.

看起來即使把方法傳遞給其他物件執行,方法中的$this還是指向自己,跟傳遞的對象沒有關係。

我要留言

立即登入留言