讀了石頭成的《為什麼還不升級PHP5》 以及 jaceju 於《PHP5 將滿 4 歲》.
一轉眼, PHP5 發表已經四個年頭了呀~真快.
個人覺得 PHP5(Zend Engine 2) 最大的演進, 在於 Exceptions / SPL / OO 的導入.
還記得四年前在 Zend 剛看到 PHP5 Exceptions/SPL/OO 的功能介紹, 內心的震撼很大.
因為它承襲了 java 的許多特性, 而這也意味著 PHP5 由 PHP3/PHP4 給人的 Scripting Language 印象,
成為一個更為強固的語言(那是什麼??? 還是 Scripting Language .. 來亂的).
##CONTINUE##
而 Exception Handle 的導入, 我們不再是利用 @function_name 或 set error_reporting 的方式來 "隱藏" 或"吃掉" 問題, 而是主動把可能發生例外的地方 try catch 起來, 這讓用 php5 開發的 web site 更加強固及可預測性.
而 SPL 則提供了類 Java 的 collections 架構. iterator interface, 這個在 Java 中是大量被使用的方法.
然而, 利用 Google 查詢一下, 可以發現討論和使用 SPL 及 Iterator 特點的 php 討論並不多..果然是會令 Zend 及 PHP5 開發團隊有些失落.
PHP5 擁有了類似 java 的優點及特性, 卻保有了 PHP 的精巧及簡便的思維來實作.. 這在當時, 讓阿土伯流了很多口水.
這篇著重在 PHP5 的 Iterator 這個觀念上.
Loop 迴圈
上面是 php 中常見的 collections 或 datas 利用迴圈走訪每一筆資料的作法.. 它們看起來類似, 但是對於不同的資源(array, files, database) 又有些許的差異.
對於不同的物件及程式實作品, 你便需要知道每一個差異及輪巡的方式, 才能夠利用迴圈一一輪巡他們.
我們能夠提供一個標準的方式, 不論我們面對的是什麼資源(array, files, database, xml, ldap), 都只要使用相同且最直覺的方式輪巡嗎?
是的, 這就是 SPL 的 Iterator 的目的, 它不是解決您迴圈輪巡的效率問題, 它提供標準介面解決單一操作方式.
Iterator of Array
OKOK!! 我知道大部份人會說, 不透過 ArrayObject 這個 Wrapper , 直接 foreach ($colors as $color) 也是可以.
沒錯, 同上所述, 在 array 的 iterator 上, 和傳統寫法是一致且類似的, 因為"單一操作"的解決 solution.
但在後面進階應用中 Iterator Iterator, 您會發現取得物件的 iterator 帶來的額外好處. 包含您對 ArrayIterator 加上 FilterIterator 或 LimitIterator.
順便說一下 java 的方式. 在 java 1.4 以前, 對於 collections 的 iterator 必需要
並加上 class cast 等.. 十分不方便, java 1.5 tiger 提供了類似 PHP5 SPL 的作法
相信 php5 / java 1.5 和 ruby 的 collect.each 已經提供了相對優雅簡便的操作方式.
Turn Object to Iterator
前面提到的, 有時侯我們希望對物件輪巡多筆資料也變的很容易且直覺, 我們不再需要暴露出一個內部 properties array 讓使用者輪巡, 有時也可能內部資料不是個simple array(也許是properties, 也許是 data), 利用 SPL 實作的 Bookmark.php 範例
而我們一樣只要使用標準的 foreach 語法, 即可對我們 Bookmark 物件輪巡資料, 而不再只是對 array foreach.
透過實作 IteratorAggregate 介面, 我們能為任何 Class 提供 getIterator的功能並實現了此介面的功能.
Iterator Iterator
在說明 Iterator Iterator 部份, 我以寫作一個類似 Unix 下的 find 指令為範例, 並示範了幾個 SPL 已經實作的幾個有用的 Iterator.
和 Java collections framework 相同, 您在使用 SPL 預設實作品時, 應保握一個原則, "優先考慮復合(composition), 然後才是繼承(inheritance)" ( Effective Java Programming Language Guide , item 14).
也就是優先考慮 has a , 然後才是 is a 的關係.
也就是說對於 ArrayObject / ArrayIterator / DirectoryIterator /RecursiveDirectoryIterator .... 等實作品, 在大部份情況下, 請不要繼承它, 而是復合它.
第一版的 RackFind, 我們先把目錄下的檔案全部列出來, DirectoryIterator 和 RecursiveDirectoryIterator 是 SPL 提供的目錄操作的方便 class , 差別在於後者會一併例出子目錄下的檔案.
上面程式已能將 tmp 目錄下的所以檔案列出來了, 那 find 功能在哪呢??
我們需要為 RecursiveDirectoryIterator 提供一個 Filename FilterIterator. 完整範例二如下:
OK~~ 現在只有檔名中包含 test 的會被 iterator 出來...
最後, 老板永遠是機車的, 一個 find 決對沒那麼容易, 假設老板說, 檔案太多, 可不可以分頁, 每頁只列出 5 個檔案...
這時, 我們讓我們的 Iterator 再經過 LimitIterator 的簡單實作.
Iterator useful classes
最後, 你可以透過 spl_classes() 來得到 SPL 的實作 classes , 它提供了您很多的預設實作品, 大部份情況下都夠用的.
如 5.2.3 提供了:
且 Adodb 最新的版本, 對於 ResultSet 也都支援 SPL 的 Iterator , 所以, 您都可以利用本篇的技巧處理它們.
來取代傳統的.
透過 SPL Iterator , 不論您對到的是任何資料, 處理的方式都是相同, 這對於您將資料處理程式包成 DAO
來說, 不管您是後端是利用 XML, Array, Database , LDAP , 利用 iterator , 所有的呼叫方式及使用方式是相同的.
附錄 Adodb Iterator 實作, 也是相當直覺優雅
http://api.lifetype.org.tw/d5/ddf/adodb-iterator_8inc_8php-source.html
一轉眼, PHP5 發表已經四個年頭了呀~真快.
個人覺得 PHP5(Zend Engine 2) 最大的演進, 在於 Exceptions / SPL / OO 的導入.
還記得四年前在 Zend 剛看到 PHP5 Exceptions/SPL/OO 的功能介紹, 內心的震撼很大.
因為它承襲了 java 的許多特性, 而這也意味著 PHP5 由 PHP3/PHP4 給人的 Scripting Language 印象,
成為一個更為強固的語言(那是什麼??? 還是 Scripting Language .. 來亂的).
##CONTINUE##
而 Exception Handle 的導入, 我們不再是利用 @function_name 或 set error_reporting 的方式來 "隱藏" 或"吃掉" 問題, 而是主動把可能發生例外的地方 try catch 起來, 這讓用 php5 開發的 web site 更加強固及可預測性.
而 SPL 則提供了類 Java 的 collections 架構. iterator interface, 這個在 Java 中是大量被使用的方法.
然而, 利用 Google 查詢一下, 可以發現討論和使用 SPL 及 Iterator 特點的 php 討論並不多..果然是會令 Zend 及 PHP5 開發團隊有些失落.
PHP5 擁有了類似 java 的優點及特性, 卻保有了 PHP 的精巧及簡便的思維來實作.. 這在當時, 讓阿土伯流了很多口水.
這篇著重在 PHP5 的 Iterator 這個觀念上.
Loop 迴圈
<?php
/* Looping over an array */
foreach ( $array as $item ) {
// Do something with $item
}
/* Looping over a MySQL result set */
while ( $row = mysql_fetch_array($result) ) {
// Do something with $row
}
/* Looping over files in a directory */
while ( false !== ($file = readdir($dir)) ) {
// Do something with $file
}
?>
上面是 php 中常見的 collections 或 datas 利用迴圈走訪每一筆資料的作法.. 它們看起來類似, 但是對於不同的資源(array, files, database) 又有些許的差異.
對於不同的物件及程式實作品, 你便需要知道每一個差異及輪巡的方式, 才能夠利用迴圈一一輪巡他們.
我們能夠提供一個標準的方式, 不論我們面對的是什麼資源(array, files, database, xml, ldap), 都只要使用相同且最直覺的方式輪巡嗎?
是的, 這就是 SPL 的 Iterator 的目的, 它不是解決您迴圈輪巡的效率問題, 它提供標準介面解決單一操作方式.
foreach($iterator as $obj){/* ...... */ }
Iterator of Array
<?php
$colors = array ('red','green','blue');
$iterator = new ArrayObject($colors);
foreach($iterator as $color) {
echo "color is: " . $color . "\n";
}
?>
OKOK!! 我知道大部份人會說, 不透過 ArrayObject 這個 Wrapper , 直接 foreach ($colors as $color) 也是可以.
沒錯, 同上所述, 在 array 的 iterator 上, 和傳統寫法是一致且類似的, 因為"單一操作"的解決 solution.
但在後面進階應用中 Iterator Iterator, 您會發現取得物件的 iterator 帶來的額外好處. 包含您對 ArrayIterator 加上 FilterIterator 或 LimitIterator.
順便說一下 java 的方式. 在 java 1.4 以前, 對於 collections 的 iterator 必需要
while(iterator.hasNext()) {
Object obj = iterator.next();
}
並加上 class cast 等.. 十分不方便, java 1.5 tiger 提供了類似 PHP5 SPL 的作法
for(Object obj : collection ) {
}
相信 php5 / java 1.5 和 ruby 的 collect.each 已經提供了相對優雅簡便的操作方式.
Turn Object to Iterator
前面提到的, 有時侯我們希望對物件輪巡多筆資料也變的很容易且直覺, 我們不再需要暴露出一個內部 properties array 讓使用者輪巡, 有時也可能內部資料不是個simple array(也許是properties, 也許是 data), 利用 SPL 實作的 Bookmark.php 範例
<?php
class Bookmark implements IteratorAggregate {
/* 使用 array 示範資料, 在 real case 中, 資料不一定是個 simple array */
private $links = array('阿土伯程式大觀園', 'google', 'delicious');
/* 部份原 Object 實作的專屬功能, 和 iterator 無關.... */
public function getResponseHtml() { /* ....... */ }
public function imRobot() { /* ....... */ }
/* 僅實作 getIterator 即可 */
public function getIterator() {
return new ArrayObject($this->links);
}
}
?>
<?php
$bookmark = new Bookmark();
foreach ($bookmark as $link) {
echo "link is: $link \n";
}
?>
而我們一樣只要使用標準的 foreach 語法, 即可對我們 Bookmark 物件輪巡資料, 而不再只是對 array foreach.
透過實作 IteratorAggregate 介面, 我們能為任何 Class 提供 getIterator
註: 本文假設您已了解 Java/PHP5 Interface 的定義, 這裡的任何類別實作了 XXXX 介面, 即可提供 XXXX 功能.
實作 - 除了 keyword 的 implements 外, 您必須要在您的程式內"實作(現)" 介面中所定義抽象方法.
所有範例中, 都有實作所有定義的抽象方法, 而不僅於 class A implements IteratorAggregate {} 就存檔執行.
Iterator Iterator
在說明 Iterator Iterator 部份, 我以寫作一個類似 Unix 下的 find 指令為範例, 並示範了幾個 SPL 已經實作的幾個有用的 Iterator.
和 Java collections framework 相同, 您在使用 SPL 預設實作品時, 應保握一個原則, "優先考慮復合(composition), 然後才是繼承(inheritance)" ( Effective Java Programming Language Guide , item 14).
也就是優先考慮 has a , 然後才是 is a 的關係.
也就是說對於 ArrayObject / ArrayIterator / DirectoryIterator /RecursiveDirectoryIterator .... 等實作品, 在大部份情況下, 請不要繼承它, 而是復合它.
第一版的 RackFind, 我們先把目錄下的檔案全部列出來, DirectoryIterator 和 RecursiveDirectoryIterator 是 SPL 提供的目錄操作的方便 class , 差別在於後者會一併例出子目錄下的檔案.
<?php
class RackFind implements IteratorAggregate {
private $path = "";
private $file = "";
public function __construct($path, $file="") {
$this->path = $path;
$this->file = $file;
}
public function getIterator() {
return new RecursiveDirectoryIterator($this->path);
}
}
$find = new RackFind("c:/tmp", "test");
foreach($find as $f) {
echo $f->getFileName() . "\n";
}
?>
上面程式已能將 tmp 目錄下的所以檔案列出來了, 那 find 功能在哪呢??
我們需要為 RecursiveDirectoryIterator 提供一個 Filename FilterIterator. 完整範例二如下:
<?php
class RackFind implements IteratorAggregate {
private $path = "";
private $file = "";
public function __construct($path, $file="") {
$this->path = $path;
$this->file = $file;
}
public function getIterator() {
return new FilenameFilter(new RecursiveDirectoryIterator($this->path), $this->file);
}
}
class FilenameFilter extends FilterIterator {
private $file = "";
public function __construct($it, $file="") {
parent::__construct($it);
$this->file = $file;
}
public function accept(){
if (strlen($this->file) ==0) return true;
return ereg($this->file, $this->current()->getFileName());
}
}
$find = new RackFind("c:/tmp", "test");
foreach($find as $f) {
echo $f->getFileName() . "\n";
}
?>
OK~~ 現在只有檔名中包含 test 的會被 iterator 出來...
最後, 老板永遠是機車的, 一個 find 決對沒那麼容易, 假設老板說, 檔案太多, 可不可以分頁, 每頁只列出 5 個檔案...
這時, 我們讓我們的 Iterator 再經過 LimitIterator 的簡單實作.
<?php
class RackFind implements IteratorAggregate {
private $path = "";
private $file = "";
private $size =0;
public function __construct($path, $file="", $size=0) {
$this->path = $path;
$this->file = $file;
$this->size = $size;
}
public function getIterator() {
if ($this->size >0) {
return new LimitIterator(new FilenameFilter(new RecursiveDirectoryIterator($this->path), $this->file), 0, $this->size);
}else {
return new FilenameFilter(new RecursiveDirectoryIterator($this->path), $this->file);
}
}
}
class FilenameFilter extends FilterIterator {
private $file = "";
public function __construct($it, $file="") {
parent::__construct($it);
$this->file = $file;
}
public function accept(){
if (strlen($this->file) ==0) return true;
return ereg($this->file, $this->current()->getFileName());
}
}
$find = new RackFind("c:/tmp", "test", 5);
foreach($find as $f) {
echo $f->getFileName() . "\n";
}
?>
Iterator useful classes
最後, 你可以透過 spl_classes() 來得到 SPL 的實作 classes , 它提供了您很多的預設實作品, 大部份情況下都夠用的.
如 5.2.3 提供了:
Array
(
[AppendIterator] => AppendIterator
[ArrayIterator] => ArrayIterator
[ArrayObject] => ArrayObject
[CachingIterator] => CachingIterator
[DirectoryIterator] => DirectoryIterator
[EmptyIterator] => EmptyIterator
[FilterIterator] => FilterIterator
[InfiniteIterator] => InfiniteIterator
[IteratorIterator] => IteratorIterator
[LimitIterator] => LimitIterator
[NoRewindIterator] => NoRewindIterator
[OuterIterator] => OuterIterator
[ParentIterator] => ParentIterator
[RecursiveArrayIterator] => RecursiveArrayIterator
[RecursiveCachingIterator] => RecursiveCachingIterator
[RecursiveDirectoryIterator] => RecursiveDirectoryIterator
[RecursiveFilterIterator] => RecursiveFilterIterator
[RecursiveIterator] => RecursiveIterator
[RecursiveIteratorIterator] => RecursiveIteratorIterator
[RecursiveRegexIterator] => RecursiveRegexIterator
[RegexIterator] => RegexIterator
[SeekableIterator] => SeekableIterator
[SimpleXMLIterator] => SimpleXMLIterator
[SplFileInfo] => SplFileInfo
[SplFileObject] => SplFileObject
[SplObjectStorage] => SplObjectStorage
[SplObserver] => SplObserver
[SplSubject] => SplSubject
[SplTempFileObject] => SplTempFileObject
)
且 Adodb 最新的版本, 對於 ResultSet 也都支援 SPL 的 Iterator , 所以, 您都可以利用本篇的技巧處理它們.
$rs = $db->Execute($sql);
foreach($rs as $row) {
echo "r1=".$row[0]." r2=".$row[1]."
";
}
來取代傳統的.
while (!$recordSet->EOF) {
print $recordSet->fields[0].' '.$recordSet->fields[1].'
';
$recordSet->MoveNext();
}
透過 SPL Iterator , 不論您對到的是任何資料, 處理的方式都是相同, 這對於您將資料處理程式包成 DAO
來說, 不管您是後端是利用 XML, Array, Database , LDAP , 利用 iterator , 所有的呼叫方式及使用方式是相同的.
附錄 Adodb Iterator 實作, 也是相當直覺優雅
http://api.lifetype.org.tw/d5/ddf/adodb-iterator_8inc_8php-source.html
8 則留言:
沒想到阿土伯也對 PHP5 感興趣,真是讓我太驚喜了~~
這篇談到的 Iterators 真的很不錯,這也是 PHP5 特色之一。
另外我先前也寫了一篇有關
SPL 的文章,不過沒有您寫得詳細。然而我倒是體會到了 PHP 在 Iterator 上的強大,例如:
foreach (new RecursiveIteratorIterator($dir) as $entry)
{
echo "$entry\n";
}
這段程式可以列舉某 $dir 目錄下所有子目錄,真的非常簡單而強大。
希望往後能和您多多交流 PHP5 的心得 :)
Dear Jaceju:
在單一 Iterator 使用 foreach 所帶來的強大是"標準操作"的統一好處..
但 Iterator 之間的串接, 也就是 Decorator Pattern 才是它強大且讓整個專案精巧直覺的關鍵.
Java InputStream/OutputStream 就是大家最常接觸到的 Decorator Pattern.
myStringBuffer=new StringBuffer("This is a sample string to be read");
FilterInputStream myStream=new LineNumberInputStream
( new BufferInputStream( new StringBufferInputStream( myStringBuffer)));
"透過實作 IteratorAggregate 介面, 我們能為任何 Class 提供 getIterator 的能力."
這句話不太完整。因為 IteratorAggregate 只有一個抽象行為: getIterator() 。此行為必須回傳一個 primitive array 或實作 Traversable 介面的實例 (PHP 會檢查)。
但我卻覺得這個實現方式卻不夠直覺。在C++要overload一個operator時,直接定義方法就行了。哪需要什麼介面?我會想 PHP5 幹嘛不用 magic method 機制?舉例來說,來個 __getIterator() 的 magic method。嗯,效果一樣嘛。
Dear 石頭成:
可能我是以一個 Java fan 的述語來陳訴 "實作" 介面的概念.
Java 中的 "實作", 表示了您必須在您的 Class 中 "提供實現" 該介面的程式碼...
而不是實作該介面即擁有該功能, 這在 java 中, 利用了提供 Abstract Adapter Class, 來省下您自行”提供實現"的程式碼.
小弟覺得 Traversable 本身沒有任何的 method 需要被實作,比較像是一個 "標示" 介面. 如同 java 中的 Cloneable or Serializable.
在 spl 文件中建議不要/也不能僅實作 Traversable (Abstract base interface that cannot be implemented alone. Instead it must be implemented by either IteratorAggregate or Iterator.)
而 Iterator 介面本身便是 implements Traversable.
http://www.php.net/~helly/php/ext/spl/interfaceTraversable.html
至於 Interface, 只養成習慣利用介面來操作物件的方式, 而不要直接操作物件, 這看起來像多餘, 但確是一個標準化以及未來專案擴充變更時的保命符..
REF: Effective Java Programming Language Guide.(Item 34: Refer to objects by their interfaces)
"不是實作該介面即擁有該功能"
嗯,你對 Java 的解釋有點怪。不知道你看的是哪本書,這樣的用字遣詞反而讓人愈看愈迷糊了。
介面是「只有純虛擬函數的抽象類別」。這意味著它只有函數的「原型宣告」而無「內容(程式碼)定義」。
implement 表示 "必須" 在此類別中實作介面的函數定義。換句話說,當我 implement 某介面,就表示我實現了此介面的功能。否則編譯器會擲出錯誤。
Dear 石頭成:
我覺得我們在一個 implements / implemented 的英譯上打轉已失去了意義..
以依您的高見, 修改我 Blog 上的文字說明.
宣告的 implements (實作) , 而實際實作的程式碼 implemented as, 大部份的中文書亦稱為 (實作) .
為了表達不是只有宣告要實作(implements)一個介面, 並必須要實作 (implemented) 它, 這個 implemented 為了和 implements 區分, 我又形容它為 (提供實現).
《介面是「只有純虛擬函數的抽象類別」。這意味著它只有函數的「原型宣告」而無「內容(程式碼)定義」。
implement 表示 "必須" 在此類別中實作介面的函數定義。換句話說,當我 implement 某介面,就表示我實現了此介面的功能。否則編譯器會擲出錯誤。》
我實不知和您形容的差異有多大,或許我的中文表達實在有問題.
英文 implements 及 implemented as before.
http://java.sun.com/docs/books/tutorial/java/concepts/interface.html
Sun 的說法是「在類別宣告中使用關鍵字 "implements" 。這讓類別以更正式的形式承諾它提供了這些行為」。動作是宣告(declare, promise),名詞子句是 "implements this"。
This class declare it will implement this interface.
你看的中譯書,把名詞子句變成主格的動詞了。好的譯本,在這一點上就把握得很到位。國內翻譯者的水準參差不齊,尤其在程式語言這種專業書籍上差異更明顯。有時我也不得不多看幾本,再中英對照,才能知道正確意涵。
Dear 石頭成:
這和中譯本作者水準及是否有把握得很到位無關.
是阿土伯看書不求慎解, 每一位作者的水準都比阿土伯高.
或許我文章一開始, 沒有先聲明 "本文重點在 Iterators in PHP5, 讀者必需先了解 Interface 概念".
在 Java 論壇中, 當有人問如: "我要如何在 Tomcat 中為每一個 Request/Response 轉 Charset"
答:"您可以實作 javax.servlet.Filter 介面, 然後將它加入 web.xml 即可"
又有人問:"我要如何監視 Session 的生成及銷毀, 並做後續的 Log 或其它動作"
答: "您可以實作 javax.servlet.http.HttpSessionListener介面, 然後將它加入 web.xml 即可"
這樣的簡式回答, 當然是假設讀者知道, 實作不是指在 class 後面跟個 implements keyword 而已, 而是必須把 interface 中的所有方法實作.
所以我文章中只有一語帶過 "您只要實作 XXX 即可", 因為我假設來看這篇文章的讀者, 不會只打 implements XXX 就存檔執行.
借用一句口頭禪: "謝謝指教"
張貼留言