2007年6月28日 星期四

Get class name in static method ( Java and PHP )

在撰寫 Iterators in PHP5 這篇文章時, 逛了一下 TWPUG 看看大家對於 PHP5 或相關的討論,
無意中看到了這個有趣的主題《取得 static method 實際上的 caller class type.
雖不知為何有這樣的需求(是希望藉於 parent class 提供 static factory methods, 然後子類別呼叫 parent class's static factory methods ?? ), 這有個觀念上需要釐清, 但就結果論, 要得到這個結果的確是一個有趣的嘗試..
##CONTINUE##

Java static method 觀念:
static method(靜態方法) 不屬於物件實體,而是由類別名稱直接被存取的,所以它們不像實體方法一樣是在執行時選取的,也因此 static method 會被稱為「靜態」的原因,因為它們總是在編譯時就連結好了。
父類別中的 static method 可以被子類別中其它的 static method 所 shadow,但不能被 override。
但要是父類別的 method 被宣告成 final 的話,就代表它既無法被 shadow 也無法被 override。

一段關於 shadow 的名詞解釋 [1]:「使用和父類別中之變數相同的名稱來宣告變數,我們便可稱此一變數 shadow(遮蔽) 了父類別的變數。使用關鍵字 super 可以指向被遮蔽的父類別變數,或藉由將此物件轉型成父類別的型態來指向它」。
[1] Niemeyer & Knudsen,2001,Java 學習手冊,O'REILLY,第 6 章、附錄C。

相信在 PHP5 中的 static method 應該也是和 java 相同的定義.

所以這個問題的關鍵變成不在 this / self (php) / __CLASS__ (php) 在執行時期是誰.
是實作 static method class 本身, 所以我們得到的都會是 Parent 這個輸出. 換言中, 我們需要的是 呼叫的順序是什麼, 誰是第一個呼叫我? 就能可能嘗試推出想要的結果.

Java Way:
在 Java 中, 我們可以透過 Class.getName() 是取得 class name 的方式.
然而, 要使用 Class.getName() , 有幾個方式
  1. 物件實體(instance object).getClass().getName() 前題是要有物件的實體.
  2. Dog.class.getName() 然而, 您都寫 Dog.class 就表示你知道 Class Name = Dog .. orz.
  3. Class.forName("Dog").getName() 同上.
同上觀念部分, 我們無法利用 Class.getName() 得到想要的結果(同上,即使我還是不知道為什麼要這麼做).

利用 Exception
還記每個 java programmer 的好朋友, 每每發生 Exception , 呼叫 e.printStackTrace() 時那 Dump 出來的資訊.
它記錄了所有 call stack trace. 所以利用 StackTrace 剛好可以得到這個問題的解決方式(算是偏門).


public class Parent {

public static void test() {
StackTraceElement[] stackTraceElement = new Throwable().getStackTrace();
System.out.println(stackTraceElement[stackTraceElement.length-1].getClassName());

}

public static void main(String[] args) {
Parent.test();
}

}


public class Child extends Parent {
public static void main(String[] args) {
Child.test();
}
}


public class AnotherChild extends Parent {
public static void main(String[] args) {
Child.test();
}
}


PHP Way:
PHP 中也可以呼叫 get_call_stack() 得到 call stack trace.
然而, 很不幸的 get_call_stack() 無法為 php 解決這個結果.
於是轉向 Hook static method 它. 利用 runkit 可以做到. (同 java: 算是偏門).
runkit 可於 PECL 下載 http://pecl.php.net/package/runkit , http://pecl4win.php.net/list_dlls.php
下面例子,我在 windows (只要丟個 php_runkit.dll , php.ini 加一加很快)測試通過.. 其它平台沒有測試.


<?php
class Father {
public static function getInstance($class=null) {
if ($class == null) $class = get_class();
echo "className = " . $class . "\n";
}
}

class Child extends Father{
}

class AnotherChild extends Father {
}


// Before runkit hook
echo Child::getInstance();
echo AnotherChild::getInstance();


class StaticMethodHelper {
public static function fixStaticMethod($parentClass) {
foreach (get_declared_classes() as $class) {
if (is_subclass_of($class, $parentClass)) {
$rc = new ReflectionClass($class);
foreach ($rc->getMethods() as $m) {
if ($m->isStatic() && $m->isPublic() && ($m->getDeclaringClass()->getName()!=$class) ) {
$body='$args = func_get_args(); array_unshift($args, "'.$class.'"); return call_user_func_array(array("parent","'.$m->getName().'"),$args);';
runkit_method_redefine($class, $m->getName(),'',$body, RUNKIT_ACC_PUBLIC);
}
}
}
}
}
}

// Runkit redefine static method.
StaticMethodHelper::fixStaticMethod('Father');

echo Child::getInstance();
echo AnotherChild::getInstance();

?>


Output:

C:\dev\php\php-5.2.3-Win32>php c:\tmp\test.php
className = Father
className = Father
className = Child
className = AnotherChild


以上僅就結果而言達到 "想要的效果結果", 但在 Java/PHP 實務上建議應該避免這樣的嘗試.

2007年6月27日 星期三

Observer Pattern in PHP5

PHP5.1 後, 新增了 Observer Pattern 的支援.
這個能改變物件依存關係,讓物件狀態一有變動,就自動通知其他相依物件做該做的更新動作, 由程式需要主動呼叫, 改為物件觀察的角色. 這依然是 java 中最常見的特性 awt 以及 MVC 的基楚.
但依然很少人利用.
##CONTINUE##

這篇著重在 PHP5 的 Observer 這個觀念上.
並應用了 SplObjectStorage 這個方便的 Class , 它可以讓您將任何 object 放入其中, 並 iterator 它.

這個不倫不類的例中, 用來承現 PHP 常處理的資料物件上, (因為 PHP 目前尚未有 App Container , 所以暫時 (不會?)用來處理 event 或 listener 上).
我們假設 Teacher / Student / School 都是 DB 上的物件的 ORM , 在以往, 我們需要在 Controller 中處理它的相關性,
並各自呼叫變動的狀態.. 利用 Observer Pattern, 我們能寫出更抽象的 OR 物件, 它僅關心在本身發生了什麼事,
而不需由呼叫端依 Student 或 Teacher 各別處理狀態改變.


<?php

class Teacher implements SplObserver {
private $name;

public function __construct($name) {
$this->name=$name;
}

public function update(SplSubject $subject) {
echo $this->name . " Teacher Says: Hello~~~ \n";
}

}

class Student implements SplObserver {
private $name;

public function __construct($name) {
$this->name=$name;
}

public function update(SplSubject $subject) {
echo $this->name . " Student Says: Hello~~~ \n";
}
}

class School implements SplSubject {

private $name;
private $people ;
private $state;

public function __construct($name) {
$this->people = new SplObjectStorage();
$this->name = $name;
}

public function getName(){
return $this->name;
}
public function attach(SplObserver $observer) {
$this->people->attach($observer);
}

public function detach (SplObserver $observer) {
$this->people->detach($observer);
}

public function notify() {
foreach($this->people as $person) {
$person->update($this);
}
}

public function getState() {
return $this->state;
}

public function setState($state) {
$this->state = $state;
}

}

$school = new School('Rack Lin junior school');

$rack = new Student('rack');
$rack2 = new Student('rack2');
$lily = new Teacher('Lily');
$school->attach($rack);
$school->attach($rack2);
$school->attach($lily);
$school->notify();
?>

Output:

rack Says: Hello~~~
rack Says: Hello~~~
rack2 Says: Hello~~~
rack Says: Hello~~~
rack2 Says: Hello~~~
Lily Says: Hello~~~




Technorati Tags: , ,

2007年6月26日 星期二

Iterators in PHP5

讀了石頭成的《為什麼還不升級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

/* 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

2007年6月24日 星期日

ExifPhotoStamper 1.0.3 - 更新版推出

每天一新版, 這大概是阿土伯的 opensource/freeware 中更版最快的了..
因為它有個惡勢力使用者(老婆大人), 他的需求會立即新增上去 :D

##CONTINUE##
因為 google code 限制單一檔案 20mb, 請下載分割檔案以下面指令合併或用 7-zip 合併解壓即可.


copy /b exifphotostamper-1.0.3-win32.zip.001+exifphotostamper-1.0.3-win32.zip.002 exifphotostamper-1.0.3-win32.zip

1.0.3 版:

  • 目的檔案 變更檔名 功能.
  • 執行結果 debug.log , 有使用上的問題, 請將 log 寄給阿土伯.

詳情請見:

http://racklin.blogspot.com/2007/06/exifphotostamper.html

2007年6月21日 星期四

ExifPhotoStamper 1.0.2 - 更新版推出

部份網友反應的問題及想法, 阿土伯將它做了修正, 並加上了新的功能.. 謝謝大家的建議..
因為, 阿土伯的小土仔只有一位, 沒有太多 Sample Case 可以測, 還望大家繼續試用嘍 :D

##CONTINUE##
1.0.2 版:
  • 修正部份年齡日推算得到大於 1000 的 bug (阿土伯沒注意到 java 回傳的是 微秒, 不是秒 Orz....)
  • 新增字體外框可設定顏色.
  • 字體下方畫陰影.

詳情請見:

http://racklin.blogspot.com/2007/06/exifphotostamper.html

2007年6月20日 星期三

ExifPhotoStamper 1.0.3 - 批次為照片加上日期+


簡介:
ExifPhotoStamper 是一個精巧的 Java 應用程式 For JavaSE 5.0 或 以上.
它提供您批次處理 Exif 中的拍照日期加入到您的照片的四個角落其中一個.
然而, 除此之外, 它提供了一個特殊的功能: "開始日期" 功能.

##CONTINUE##
什麼是"開始日期", 利用"開始日期"功能, 您可以為您的照片設定一個起始的日期, 如您寶寶的出生日期, 男女朋友開始交往日期, 阿土伯的生日等, 利用 ExifPhotoStamper , 它除了將您相片 Exif 中的拍照日期加在相片中外, 還會加上由開始日期到拍照日期經過的時間.
所以, 你可以很輕鬆知道這是寶寶幾個月大的相片, 男女雙方交往多久後的相片, 或阿土伯混吃等死多少年了等, 方便為您的寶寶及親人做相片日記.

當然, 利用 format syntax 功能, 您可以自定壓上去的日期格式或乎略開始時間功能, 將 ExifPhotoStamper 當成標準的 批次壓日期小工具.

最後, ExifPhotoStamper 產生的圖檔, 依然會保留著原始檔案中的 Exif 資訊, 所以您上傳到 flickr 或支援 exif 的網路相簿或管理軟體, 依然可以正確的排序/統計及管理.


支援功能:
  • Exif Date/Time support.
  • Target File will exist Source File 's Exif Infomation metadata.
  • Font/Size/Color Setup.
  • Corner / Margin Setup.
  • Stamp Format Syntax support.
  • Font Border Color Setup.
  • Drop Shadow .
  • Target File Rename.
  • Debug Log.

ScreenShot:




小土仔的 Sample:

Sample (客制化格式, 中文 format)




Sample (Stamp)



Large-dl:http://exifphotostamper.googlecode.com/svn/trunk/sample/DSCN2007-out.JPG

Sample (Orig)

Large-dl:http://exifphotostamper.googlecode.com/svn/trunk/sample/DSCN2007.JPG


使用中文格式:

1. 請在字形名稱中選擇一個中文字體.
2. 格式: (%Y歲%M個月%D日) %y/%m/%d

其中 % 後面緊跟一個英文字母表示指令, 其它隨您填寫, 您也可填

格式: 我的小寶貝 - (%Y歲%M個月%D日) 於 %y年.%m月.%d日.

它等於是幫您浮水印字在上面唷..

指令有:
%Y - 經過(年) %M - 經過(月) %D - 經過(日)
%y - 拍照(年) %m - 拍照(月) %d - 拍照(日)
%H - 拍照(時) %i - 拍照(分) %s - 拍照(秒)


Featured Download:
所有作業系統: without-jvm
http://exifphotostamper.googlecode.com/files/exifphotostamper-1.0.3-bin.zip (340 KB)

Windows: with-jvm (請下載二個檔後一起解壓縮, 因為 google code 有單一檔案 20mb 的限制)

http://exifphotostamper.googlecode.com/files/exifphotostamper-1.0.3-win32.zip.001
http://exifphotostamper.googlecode.com/files/exifphotostamper-1.0.3-win32.zip.002
(29.5 MB)

copy /b exifphotostamper-1.0.3-win32.zip.001+exifphotostamper-1.0.3-win32.zip.002 exifphotostamper-1.0.3-win32.zip



Open Source:

http://code.google.com/p/exifphotostamper/


Require JavaSE5 以上:
ExifPhotoStamper 需要 JavaSE5 或以上的環境, 若您不是下載 Windows 版, 或您的電腦尚未安裝 JavaSE , 請到下列網址先下載安裝.
http://java.sun.com/javase/downloads/index.jsp
點選 Java Runtime Environment (JRE) 6u1 旁的 Download.


軟體背後:
每一個軟體背後, 都有一個心酸的故事~~~ :D
才瘋狂的結束了前一陣子的專案, 本以為可以利用端午連假休息在家躺四天, 沒想到老婆大人一個命令下, 傕生出了這個軟體.
"把小土仔的相片整理一下, 去采風輸出一本相簿給他的爺爺看吧..." 這個.. 我能躺的時間少了一天 ....
"對了... 送去沖印的相片能不能加上當時拍照的日期"... 這... 容易, 網路上一堆 Free Software 能做吧..
"呀~~ 那日期前面能不能加上小土仔目前 幾個月大又幾天 "... 這... 沒有這樣的軟體吧........
"不管~~ 沒有你就一張一張用 gimp 打字把幾個月大又幾天打上去" ...... orz....

相信一定有很多爸爸/媽媽需要這個軟件... 希望對大家有幫助....