iT邦幫忙

DAY 5
4

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

逐步提昇PHP技術能力 - PHP的語言特性 : Generators / Iterators

Generators是PHP5.5才加入的全新功能,原本有點猶豫是否要嘗試一下,因為Javascript1.7就加入了這個新特性,雖然用法稍有不同(本來其實一樣,但是剛剛看了一下MDN,發現他把文件都改了,舊的用法舊屍骨無存),但是是大同小異的。不過看到文件中介紹SPL中的Iterator與Generator的比較,感覺Generator真的很好用,所以還是拿來作Iterator的練習,然後跟Generator來比較看看。
在試用之前,需要先安裝好PHP5.5...如果使用Mac或是Linux,強烈建議使用C9S大神的神作:phpbrew,使用他可以協助安裝/編譯不同版本的PHP,然後用指令切換...不支援WindowsXD

參考:
* PHP: Generators - Manual
* PHP: Generator - Manual (PHP內建的Interface,從PHP5.5.0開始支援)
* PHP: Iterator - Manual (PHP內建的Interface,從PHP5.0.0開始支援)

基本上只要實作了Iterator的物件,除了透過介面存取,也可以透過foreach來操作。一個最簡單的例子:

<?php
class a implements Iterator {
  var $keys = array("k1", "k2", "k3");
  var $vals = array("v1", "v2", "v3");
  var $pos = 0;
  function current() {
    return $this->vals[$this->pos];
  }
  function key() {
    return $this->keys[$this->pos];
  }
  function next() {
    $this->pos++;
  }
  function rewind() {
    $this->pos = 0;
  }
  function valid() {
    if($this->pos>=count($this->keys)) {
      return false;
    }
    else {
      return true;
    }
  }
}
$a = new a;
foreach($a as $k=>$v) echo "$k:::$v\n";

執行結果:

Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ php 1-4a.php
k1:::v1
k2:::v2
k3:::v3

利用Iterator,就可以把許多複雜的循序操作,包裝成可以透過foreach來迭代的物件。例如需要剖析一個很大的csv檔,如果一次把檔案讀入記憶體,會使用掉很大的資源,比較好的處理方式是用streaming的方式讀取,逐步處理讀取的內容。這個動作如果包裝成Iterator,就可以透過foreach來築行處理csv。例如:

<?php
class a implements Iterator {
  private $count = 0;
  private $fd;
  private $buffer='';
  function __construct($file) {
    $this->fd = fopen($file, 'r+');
  }
  function __destruct() {
    fclose($this->fd);
  }
  function current() {
    return explode(',', $this->buffer);
  }
  function key() {
    return $this->count;
  }
  function next() {
    $this->count++;
    $this->buffer = fgets($this->fd);
  }
  function rewind() {
    $this->count = 0;
    fseek($this->fd, 0);
    $this->buffer = fgets($this->fd);
  }
  function valid() {
    if($this->buffer) {
      return true;
    }
    else {
      return false;
    }
  }
}
$a = new a('1-4b.csv');
foreach($a as $line) echo implode(' : ', $line);
echo "\n";

讀取的csv檔案內容如下:

a,b,c
d,e,f
h,i,j
k,l,m
n,o,p

程式執行的結果:

Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ php 1-4b.php
a : b : c
d : e : f
h : i : j
k : l : m
n : o : p

大致上介紹了一下Iterator,可以看到使用他可以把一個循序操作的過程包裝起來,然後利用foreach把它當作陣列來操作。(除了檔案,我想資料庫查詢的過程也可以用這個方法包裝成recordset)不過為了要能這樣操作,就需要打破程式的流程,把步驟分散到各個不同方法裡,其實是有一點麻煩的。使用Generator的話,就可以用更簡單的方式來做,只要在適當的時機使用yield。

如果把上面的例子改成用Generator來做,就變成這樣:

<?php
function getcsv($file) {
  $fd = fopen($file, 'r+');
  if($fd) {
    while(($buffer=fgets($fd))!==false) {
      yield explode(',',$buffer);
    }
    fclose($fd);
  }
}
$a = getcsv('1-4b.csv');
foreach($a as $line) echo implode(' : ', $line);
echo "\n";

執行的結果是一樣的:

Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ phpbrew switch php-5.5.4
Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ php 1-4c.php
a : b : c
d : e : f
h : i : j
k : l : m
n : o : p

使用了yield的函數,執行後會回傳一個Generator,而Generator也implement了Iterator,所以一樣可以用foreach來操作,只是寫起來更簡單。再來看一下更複雜的Generator。

除了Iterator的介面,Generator的介面定義了一個方法,叫做send。透過send方法,可以把資料回傳給yield。像這樣:

<?php
function gen() {
  $arr = array('a,b,c', 'd,e,f', 'g,h,i', 'j,k,l', 'm,n,o');
  foreach($arr as $l) {
    $r = (yield $l);
    echo $r;
  }
}
$a = gen();
while($a->valid()) {
  $a->send(implode(' : ', explode(',',$a->current()))."\n");
  $a->next();
}

執行結果:

Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ php 1-4d.php 
a : b : c
g : h : i
m : n : o

接收yield回傳值,需要用括號把yield包起來,這跟Javascript的yield稍微不一樣。如果是要做一些檔案資料格式轉換,用Generator來做就很方便,而且因為轉換的動作是在Generator外部,所以寫一個Generator,就可以做不同格式的轉換,在Generator函數內存檔,一些複雜的動作就一次做完了。把上面的例子改一下,然後把csv轉成tab seperated txt看看:

<?php
function conv($from, $to) {
  $fd1 = fopen($from, 'r+');
  $fd2 = fopen($to, 'w+');
  if($fd1 && $fd2) {
    while(($r=fgets($fd1))!==false) {
      $w = (yield $r);
      fputs($fd2, $w);
    }
    fclose($fd1);
    fclose($fd2);
  }
}
$a = conv('1-4b.csv', '1-4e.txt');
while($a->valid()) {
  $a->send(implode("\t", explode(',',$a->current())));
  $a->next();
}

執行結果:

Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ php 1-4e.php
Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ cat 1-4e.txt
a	b	c
h	i	j
n	o	p

如果要做其他方式的轉換,就再呼叫一次conv(),然後迭代、轉換、回傳就可以,非常方便又有彈性。

本來覺得Generator沒什麼特別,沒想到試了一下還是很有趣呢XD...先就此打住,睡個覺以後還要去phpconf...


上一篇
逐步提昇PHP技術能力 - PHP的語言特性 : Traits
下一篇
逐步提昇PHP技術能力 - PHP的語言特性 : Namespaces 與 Class Autoloading
系列文
逐步提昇PHP技術能力30

尚未有邦友留言

立即登入留言