1. 程式人生 > >PHP反射機制簡單理解

PHP反射機制簡單理解

什麼是反射呢?

在PHP的面向物件程式設計中的物件,它被系統賦予自省的能力,而這個自省的過程,我們把它叫做反射。

我們對反射的直觀理解可以是,根據達到地,找到出發地和來源這麼一個過程,通俗來講就是,我給你一個光禿禿的物件,完事你可以根據這個物件,知道它所屬的類,擁有哪些方法。

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

我們接下來通過一段程式碼來感受下:

class person{
    public $name;
    public $age;

    public function say()
    {
        echo $this->name."<br>".$this->age;
    }

    public function set($name,$value)
    {
        echo 'set name to value';
        $this->$name = $value;
    }

    public function get($name)
    {
        if(!isset($this->$name)){
            echo 'unset name';
            $this->$name = 'seting~~~';
        }

        return $this->$name;
    }
}

$stu = new person();
$stu->name = 'luyaran';
$stu->age = 26;
$stu->sex = 'girl';

上述程式碼是一個簡單的類,我們通過例項化它,以及賦值,讓它含有意義。

完事,我們就來通過反射API獲取這個stu物件的方法和屬性的一個列表:

//獲取物件的屬性列表
$reflect = new ReflectionObject($stu);
$props = $reflect->getProperties();
foreach ($props as $key_p => $value_p) {
    var_dump($value_p->getName());
}
//獲取物件的方法列表
$method = $reflect->getMethods();
foreach ($method as $key_m => $value_m) {
    var_dump($value_m->getName());
}

除了反射API之外,我們還可以使用class函式來獲取物件的各種屬性以及方法的資料,如下:

// 獲取物件的屬性的關聯陣列
var_dump(get_object_vars($stu));
//獲取類屬性
var_dump(get_class_vars(get_class($stu)));
//獲取類的方法名稱組成的陣列
var_dump(get_class_methods(get_class($stu)));

值得一說的是,這個get_class這個函式,還可以獲取從其他頁面傳遞過來的物件的屬性列表以及所屬的類。

不過,class函式和反射API相比較起來,個人感覺還是後者更勝一籌啊。

反射API甚至可以還原這個類的原型,包括方法的訪問許可權,來看程式碼感受下:

//例項化反射API獲取類名
$obj = new ReflectionObject($stu);
$class_name = $obj->getName();
$method_arr = $props_arr = array();

//獲取物件的屬性列表
$props = $obj->getProperties();
foreach ($props as $key_p => $value_p) {
    $props_arr[$value_p->getName()] = $value_p;
}
//獲取物件的方法列表
$method = $obj->getMethods();
foreach ($method as $key_m => $value_m) {
    $method_arr[$value_m->getName()] = $value_m;
}

//格式化輸出類的屬性以及方法
echo "class $class_name { \n";
is_array($props_arr) && ksort($props_arr);
foreach ($props_arr as $key_o => $value_o) {
    echo "\t";
    echo $value_o->isPublic() ? 'public' : ' ' ,$value_o->isPrivate() ? 'private' : ' ' ,$value_o->isProtected() ? 'protected' : ' ' ,$value_o->isStatic() ? 'static' : ' ';
    echo "\t$value_o\n";
}
echo "\n";
is_array($method_arr) && ksort($method_arr);
foreach ($method_arr as $key_e => $value_e) {
    echo "\t";
    echo $value_e->isPublic() ? 'public' : ' ' ,$value_e->isPrivate() ? 'private' : ' ' ,$value_e->isProtected() ? 'protected' : ' ';
    echo "\tfunction $value_e () {} \n";
}
echo '}';

根據上述程式碼,輸出結果如下: 

我們可以看到,上圖很詳細的輸出了這個類的構造。

不僅如此哦,PHP手冊中關於反射API的數量,多達幾十個,可以這麼說,反射完整的描述了一個類或者物件的原型。

同時呢,反射不僅可以用作類和物件,還可以用於函式,擴充套件模組,異常等。

咱們呢,在這裡就不贅述了,最後一點篇幅,就來聊聊反射的一些作用。

首先,它可以用作文件生成,所以,我們可以用它對文件中的類進行掃描,逐個生成掃描文件。

反射可以探知類的內部結構,也可以用作hook來實現外掛功能,還有就是可以做動態代理。

咱們來看段MySQL的動態代理的程式碼感受下:

class mysql{
    public function connect($db_name)
    {
        echo "we will connect database $db_name \r\n";
    }
}

class sql_proxy{
    private $target;

    public function __construct($tar)
    {
        $this->target = new $tar();
    }

    public function __call($name,$args)
    {
        $reflect = new ReflectionClass($this->target);
        $method = $reflect->getMethods();
        if ($method) {
            foreach ($method as $key_method => $value_method) {
                if($value_method->isPublic() && !$value_method->isAbstract()){
                    echo "方法前攔截記錄LOG\r\n";
                    $value_method->invoke();
                    echo "方法後攔截記錄LOG\r\n";
                }
            }
        }
    }
}

$obj = new sql_proxy('mysql');
$obj->coonect('luyaran');

上述程式碼真正的操作類是mysql,下面的sql_proxy只是根據動態傳入的引數,來代替了實際執行的類,並且可以在方法執行的前後進行攔截,還可以動態地改變類中的方法和屬性,這個可以叫做簡單的動態代理類。

在我們平常的開發中,用到反射的地方不多,一般是用來對物件進行除錯,還有就是獲取類的資訊,但是在MVC和外掛中,比較常見,並且反射的消耗也是不小的,我們在有另外一種方案的時候,儘量不要選擇反射。

我們還可以通過PHP中的token函式來實現簡單的反射功能,不過,從簡單靈活的角度來看,還是使用已有的反射API比較好。

很多時候,善用某個東西,會使得我們的程式碼,簡潔又優雅,但是不能貪多,比如這個反射API,用的多了,會破壞我們類的封裝性,使得本不應該暴露的方法暴露了出來,這是優點也是缺點,我們要搞搞清楚。

好啦,本次記錄就到這裡了。

如果感覺不錯的話,請多多點贊支援哦。。。