1. 程式人生 > >php反射機制以及利用php反射機制實現可插拔可擴充套件的外掛架構

php反射機制以及利用php反射機制實現可插拔可擴充套件的外掛架構

反射是什麼?
它是指在PHP執行狀態中,擴充套件分析PHP程式,匯出或提取出關於類、方法、屬性、引數等的詳細資訊,包括註釋。這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為反射API。

反射是操縱面向物件範型中元模型的API,其功能十分強大,可幫助我們構建複雜,可擴充套件的應用。其用途如:自動載入外掛,自動生成文件,甚至可用來擴充PHP語言。php反射api由若干類組成,可幫助我們用來訪問程式的元資料或者同相關的註釋互動。藉助反射我們可以獲取諸如類實現了那些方法,建立一個類的例項(不同於用new建立),呼叫一個方法(也不同於常規呼叫),傳遞引數,動態呼叫類的靜態方法。
*
**
反射api是php內建的oop技術擴充套件,包括一些類,異常和介面,綜合使用他們可用來幫助我們分析其它類,介面,方法,屬性,方法和擴充套件。這些oop擴充套件被稱為反射,位於php原始碼/ext/reflection目錄下。

可以使用反射api自省反射api本身(這可能就是反射最初的意思,自己"看"自己):

<?php
Reflection::export(new ReflectionExtension('reflection'));
?>
幾乎所有的反射api都實現了reflector介面,所有實現該介面的類都有一個export方法,該方法打印出引數物件的相關資訊。
使用get_declared_classes()獲取所有php內建類,get_declared_interfaces();
get_defined_functions();
get_defined_vars(); get_defined_constants();可獲取php介面,方法,變數,常量資訊。

**
***
反射初探:
<?php
//定義一個自定義類
class MyTestClass{
   
    public function testFunc($para0='defaultValue0'){
       
    }
}
//接下來反射它
foreach(get_declared_classes() as $class){
    //例項化一個反射類
    $reflectionClass = new ReflectionClass($class);
    //如果該類是自定義類
    if($reflectionClass->isUserDefined()){
      //匯出該類資訊
        Reflection::export($reflectionClass);
    }
}
?>


以上片段例項如何檢視自定義類的基本資訊。
描述資料的資料被稱為元資料,用反射獲取的資訊就是元資料資訊,這些資訊用來描述類,介面方法等等。(元---》就是原始之意,比如元模型就是描述模型的模型,比如UML元模型就是描述UML結構的模型),元資料進一步可分為硬元資料(hard matadata)和軟元資料(soft metadata),前者由編譯程式碼匯出,如類名字,方法,引數等。
後者是人為加入的資料,如phpDoc塊,php中的屬性等。
***
****
現在商業軟體很多都是基於外掛架構的,比如eclipse,和visual studio,netbeans等一些著名IDE都是基於外掛的GUI應用。第三方或本方開發外掛時,必須匯入定義好的相關介面,然後實現這些介面,最後把實現的包放在指定目錄下,宿主應用程式在啟動時自動檢測所有的外掛實現,並載入它們。如果我們自己想實現這樣的架構也是可能的。

<?php
//先定義UI介面
interface IPlugin {
//獲取外掛的名字
public static function getName();
//要顯示的選單項
function getMenuItems();
//要顯示的文章
function getArticles();
//要顯示的導航欄
function getSideBars();
}
//一下是對外掛介面的實現
class SomePlugin implements IPlugin {
public function getMenuItems() {
//返回選單項
return null;
}
public function getArticles() {
//返回我們的文章
return null;
}
public function getSideBars() {
//我們有一個導航欄
return array('SideBarItem');
}
//返回外掛名
public static function getName(){
return "SomePlugin";
}
}
?>
php中也有使用外掛的解決方案,不像eclipse。

使用我們的外掛:1.先使用get_declared_classes()獲取所有已載入類。2.遍歷所有類,判斷其是否實現了我們自定義的外掛介面IPlugin。3.獲取所有的外掛實現。4.在宿主應用中與外掛互動
下面這個方法幫助我們找到實現了外掛介面的所有類:
function findPlugins() {
$plugins = array();
foreach(get_declared_classes() as $class) {
$reflectionClass = new ReflectionClass($class);
//判斷一個類是否實現了IPlugin介面
if($reflectionClass->implementsInterface('IPlugin')) {
$plugins[] = $reflectionClass;
}
}
return $plugins;
}
注意到所有的外掛實現是作為反射類例項返回的,而不是類名本身,或是類的例項。因為如果使用反射來呼叫方法還需要一些條件判斷。

判斷一個類是否實現了某個方法使用反射類的hasMethod()方法。
接下來我們把所有的外掛選單項放在一個選單上。
function integratePlugInMenus() {
$menu = array();
//遍歷所有的外掛實現
foreach(findPlugins() as $plugin) {
//判斷外掛是否實現了getMenuItems方法
if($plugin->hasMethod('getMenuItems')) {
/*例項化一個方法例項(注意當你將類和方法看成概念時,它們就可以有例項,就像"人"這個概念一樣),該方法返回的是ReflectionMethod的例項*/
$reflectionMethod = $plugin->getMethod('getMenuItems');
//如果方法是靜態的
if($reflectionMethod->isStatic()) {
//呼叫靜態方法,注意引數是null而不是一個反射類例項
$items = $reflectionMethod->invoke(null);
} else {
//如果方法不是靜態的,則先例項化一個反射類例項所代表的類的例項。
$pluginInstance = $plugin->newInstance();
//使用反射api來呼叫一個方法,引數是通過反射例項化的物件引用
$items = $reflectionMethod->invoke($pluginInstance);
}
//合併所有的外掛選單項為一個選單。
$menu = array_merge($menu, $items);
}
}
return $menu;
}


這裡主要用到的反射方法例項的方法呼叫:
public mixed invoke(stdclass object, mixed args=null);
請一定搞清楚我們常規方法的呼叫是這種形式:$objRef->someMethod($argList...);
因為使用了反射,這時你在想呼叫一個方法時形式變為:
$reflectionMethodRef->invoke($reflectionClassRef,$argList...);
如果使用反射呼叫方法,我們必須例項化一個反射方法的例項,如果是例項方法還要有一個例項的引用,可能還需傳遞必要的引數。當呼叫一個靜態方法時,顯式傳入null作為第一引數。
對外掛類實現的其他方法有類似的處理邏輯,這裡不再敷述。
以下是我的一個簡單測試:

<?php
/**
* 定義一個外掛介面
* */
interface IPlugIn
{
    /**
     * getSidebars()
     *
     * @return 返回側導航欄
     */
    public function getSidebars();
    /**
     * GetName()
     *
     * @return 返回類名
     */
    public static function GetName();
}

/*下面是對外掛的實現,其實應該放在不同的檔案中,甚至是不同的包中*/
class MyPlugIn implements IPlugIn
{
    public function getSidebars()
    {
        //構造自己的導航欄
        $sideBars = '<div><ul >
            <li><a href="">m1</a>
                                </li>
                 <li><a href="">m2</a>
                                </li>              
               </ul>
               </div>';
        return $sideBars;
    }
    public static function GetName()
    {
        return 'MyPlugIn';
    }
}
//第二個外掛實現;
class MyPlugIn2 implements IPlugIn
{
    public function getSidebars()
    {
        //構造自己的導航欄
         $sideBars = '<div><ul >
            <li><a href="">mm1</a>
                                </li>
                 <li><a href="">mm2</a>
                                </li>              
               </ul>
               </div>';
        return $sideBars;
    }
    public static function GetName()
    {
        return 'MyPlugIn2';
    }
}

//在宿主程式中使用外掛
class HostApp
{

   public function initAll()
    {
        // 初始化各個部分
        echo "yiqing95.";
     $this->renderAll();
    }
    //渲染GUI格部分
    function renderAll(){
        $rsltSidebars="<table>";
        foreach($this->integrateSidebarsOfPlugin() as $sidebarItem){
            $rsltSidebars.="<tr><td>$sidebarItem</td></tr>";
        }
        $rsltSidebars.="</table>";
       
        echo $rsltSidebars;
    }
    /*載入所有的外掛實現:*/
   protected function findPlugins()
    {
        $plugins = array();
        foreach (get_declared_classes() as $class) {
            $reflectionClass = new ReflectionClass($class);
            if ($reflectionClass->implementsInterface('IPlugin')) {
                $plugins[] = $reflectionClass;
            }
        }
        return $plugins;
    }
    /**載入組裝所有外掛實現***/
   protected function integrateSidebarsOfPlugin()
    {
        $sidebars = array();
        foreach ($this->findPlugins() as $plugin) {
            if ($plugin->hasMethod('getSidebars')) {
                $reflectionMethod = $plugin->getMethod('getSidebars');
                if ($reflectionMethod->isStatic()) {
                    $items = $reflectionMethod->invoke(null);
                } else {
                    $pluginInstance = $plugin->newInstance();
                    $items = $reflectionMethod->invoke($pluginInstance)                     ;
                }
            }
            //$sidebars = array_merge($sidebars, $items);
            $sidebars[]=$items;
        }
        return $sidebars;
    }
   
}
//執行程式:
$entryClass =new HostApp();
$entryClass->initAll();
?>
****
××××
$reflectionClass = new ReflectionClass("IPlugIn");
echo $reflectionClass-> getDocComment();


這段程式碼可以幫助我們獲取類的文件註釋,一旦我們獲取了類的註釋內容我們就可以擴充套件我們的類功能,比如先獲取註釋,然後分析註釋使用docblock tokenizer 『pecl擴充套件』,或使用自帶的Tokenizer類又或者使用正則表示式,字串函式來解析註釋文件,你可以在註釋中加入任何東西,包括指令,在使用反射呼叫前可判斷這些通過註釋傳遞的指令或資料:
<?php
//"分析相關的註釋資料"
analyse($reflectionClass-> getDocComment());//analyse是自己定義的!!!
//根據分析的結果來執行方法,或者傳遞引數等
if(xxxx){
$reflectionMethod->invoke($pluginInstance) ;
}
?>
因為註釋畢竟是字串,可以使用任何字串解析技術,提取有用的資訊,再根據這些資訊來呼叫方法,就是說程式的邏輯不光可由方法實現決定,還可能由註釋決定(前提是你使用了反射,註釋格式嚴格有要求)。
××××
*****
反射api和其他類一樣可被繼承擴充套件,所以我們可以為這些api新增自己的功能。結合自定義註釋標記。就是以@開頭的東東,標註(Java中稱為annotation),.net中稱為屬性attribute(或稱為特性)。然後擴充套件Reflection類,就可以實現強大的擴充套件功能了。
值得一提的是工廠方法設計模式(GOF之一),也常使用反射來例項化物件,下面是示例性質的偽碼:

Class XXXFactory{
function getInstance($className){
   $reflectionClass =new ReflectionClass($className);
   return $reflectionClass->newInstance();   
}
//使用介面的那個類實現,可能來自配置檔案
function getInstance(){
$pathOfConfig = "xxx/xx/XXXImplement.php";
$className= Config->getItem($pathOfClass,'SomeClassName');
return $this->getInstance($className);
}
}
*****

轉載自:http://www.zui88.com/blog/view-205.html