摘要
相比於Java的反射,PHP中的反射可真的是良心之作。雖然從維護的角度來看,Java更勝一籌也更有優勢。但是繁瑣的處理也為Java的反射機制增加了一定的學習成本。
今天嘗試著使用PHP的反射技術來擷取類的資訊。
核心操作可以在PHP的官方協助文檔上看到,這裡用得最多的就是
getPropertiesgetMethods
目標類
為了更好的示範反射的結果以及維護,下面就先建立一個類,目錄結構如下:
<?phpclass Person { private $name; private $age; private $address; public function __construct($name, $age, $address) { $this->name = $name; $this->age = $age; $this->address = $address; } public function setter($key, $value) { exec ( "{$this}->" . $key . "={$value}" ); } /** * 通配型的getter方法不好用。 * <br /> * 原因: Object Person can not be converted to string. * * @param unknown $key * @return string */ public function getter($key) { return exec ( "$this" . "->{$key}" ); } /** * 類比Java語言實現的getter方法。<br /> * * 缺點: 需要為每一個private屬性提供單獨的getter方法,使得代碼略顯臃腫。 */ public function getName() { return $this->name; }}class Grade { private $name; public function __construct($name) { $this->name = $name; } public function setName($name) { $this->name = $name; } public function getName() { return $this->name; }}
載入問題
載入機制
在正式進行反射操作之前,先來探討一下__autoload自動載入機制。顧名思義,自動化的進行載入(類,也可以是其他php檔案)唄。
對於更深層次而言,這就涉及到PHP解譯器的工作原理了。也就是說,我們不可能一個項目唯寫一個php檔案,相反,一個項目中可能會有數以百計的php檔案,而且不可避免的會進行相互調用。
也就是說,我們在A檔案中聲明並實現了一個加法函數,而需要在B檔案中進行調用。很明顯B檔案中根本沒有實現這個加法,所以PHP解譯器就沒辦法進行加法運算了。
這個時候就需要讓PHP解譯器知道這個加法怎麼做,於是就需要require / include 包含了這個加法函數的A檔案了。
這樣,PHP解譯器就知道如何解釋並運行咱們的PHP檔案了。
與PHP相似的是,在Java語言中我們只需要在源檔案前面添加import語句,Java虛擬機器就能自動的在相關的類資訊了。而且強型別的Java可以在編譯之前就發現這樣的問題,所以代碼維護起來比較方便一點。而PHP則需要進行手動的 include/require了。
但是應該清楚的是,這兩者換湯不換藥而已。
自動載入機制
但是如果對每一個要被引用的php檔案進行手動的載入的話,可能就要寫好多個這樣的載入語句了。所以為了方便處理這種問題,PHP5之後引入了自動載入機制。
void __autoload ( string $class )
$class 就是要進行載入的類的名稱,請注意是類的名稱。
怎麼使用?
既然自動載入機制這麼好,那麼我們要怎麼使用呢?
答案就是在需要載入其他類檔案的php中,添加一個自訂的__autoload($class) 函數即可。 還是以剛才的檔案AB來舉例。
檔案A中有一個寫好的類Person,在檔案B中要進行使用。這個時候在檔案B中添加一個__aotoload函數即可。而且這個函數的寫法也比較的簡單(一切按照最簡思路來設計的話)。
function __autoload($class) { $filename = "$class.class.php"; if(!file_exists($filename)){ throw new RuntimeException("$filename 檔案不存在!"); }else { require "$filename"; }}
PHP解譯器在掃描到檔案B的時候會先進行檢查,如果未引入目標類Person,則會判斷有沒有實現__autoload,如果存在則使用自動載入函數進行載入,否則報錯退出。
注意問題
雖然上面的自動載入函數比較簡單,但是現實中卻需要為此付出很多的“代價”,也就是被載入的類檔案的名稱要和類保持一致(無需區分大小寫)。如:
要載入的類的名稱為Person,則該類所在的檔案的名稱需要為person.class.php,或者Person.class.php
而且,路徑問題也是一個比較棘手的問題,在這個簡易的自動載入函數中也不難看到,這裡他們位於同級目錄下,試想一下不滿足這個條件的情形,就可以知道這個自動載入函數的代碼量將會多麼大了吧。
如此的話,也會違反了自動載入機制的設計的初衷。所以按照特定的目錄結構存放相關的類檔案是非常有必要的。
所謂:增加了冗餘的特點,卻帶來了容易維護的好處。
個人覺得,不妨按照Java語言的目錄結構來維護PHP程式,這樣會有意想不到的收穫的。
反射
下面正式進入反射的話題,在摘要部分已經提到。重點就在於ReflectionClass的使用。
反射屬性
<?phprequire './bean/beans.php';// Person 在beans.php檔案中聲明$protype = new ReflectionClass("Person");// 可以添加一個參數,來進行過濾操作。如只擷取public類型的屬性$properties = $protype->getProperties();// 反射擷取到類的屬性資訊foreach ($properties as $property) { echo $property."<br />";}
相比於Java,要擷取 private屬性,PHP更為簡單。
反射方法
<?phprequire './bean/beans.php';$protype = new ReflectionClass("Person");$methods = $protype->getMethods();foreach ($methods as $method) { echo $method->getName()."<br />";}
另外,還可以添加過濾條件。給getMethods方法天機一個過濾參數即可。
filter過濾結果為僅包含某些屬性的方法。預設不過濾。 ReflectionMethod::IS_STATIC、 ReflectionMethod::IS_PUBLIC、 ReflectionMethod::IS_PROTECTED、 ReflectionMethod::IS_PRIVATE、 ReflectionMethod::IS_ABSTRACT、 ReflectionMethod::IS_FINAL 的任意組合。
反射注釋
注釋資訊,這裡就以文檔資訊為例。
<?phprequire './bean/beans.php';$protype = new ReflectionClass ( "Person" );$properties = $protype->getProperties ();// 反射擷取到類的屬性資訊foreach ( $properties as $property ) { echo $property . ":"; $doc = $property->getDocComment (); echo " " . $doc . "<br />"; echo "--------------------------------------------------------" . "<br />";}$methods = $protype->getMethods();foreach ($methods as $method) { echo $method->getName()."<br />"; $doc = $method->getDocComment (); echo " " . $doc . "<br />"; echo "--------------------------------------------------------" . "<br />";}
反射執行個體化
反射Person類
<?phprequire './bean/beans.php';$protype = new ReflectionClass ( "Person" );// 類比資料庫中擷取到的值,以關聯陣列的形式拋出$values = array( "name"=>"郭璞", "age"=> 21, "address"=>"遼寧省大連市");// 開始執行個體化$instance = $protype->newInstanceArgs($values); print_r($instance);// var_dump($instance);echo $instance->getName();
反射Grade類
<?phprequire './bean/beans.php';$classprotype = new ReflectionClass("Grade");$class = $classprotype->newInstanceArgs(array("name"=>"大三"));var_dump($class);echo $class->getName();
執行類的方法
$instance->getName(); // 執行Person 裡的方法getName// 或者:$method = $class->getmethod('getName'); // 擷取Person 類中的getName方法$method->invoke($instance); // 執行getName 方法// 或者:$method = $class->getmethod('setName'); // 擷取Person 類中的setName方法$method->invokeArgs($instance, array('snsgou.com'));
總結
回顧一下,本次實驗示範了PHP中的反射技術,對比分析了Java語言的反射技術的實現。也只能說各有利弊吧。
在Java中,反射技術是編寫架構的基礎。雖然在PHP中反射技術不是特別的重要,而且用的時候約束也比較多,稍顯雞肋。但是比葫蘆畫瓢的話,還是可以做出一些有用的小工具的。