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 實務上建議應該避免這樣的嘗試.
張貼留言