1. 程式人生 > >一起來看看 PHP 中的反射

一起來看看 PHP 中的反射

什麼是反射?

動態獲取 類的方法、屬性、引數,註釋 等 資訊以及動態呼叫物件的方法的功能稱為反射API。
反射是操縱面向物件範型中元模型的API,其功能十分強大,可幫助我們構建複雜,可擴充套件的應用。比如 Laravel 的容器

反射類/函式的功能

可以獲取類的一切資訊,包括:
1. 類基本資訊(類名、是否是抽象類、是否可例項化、類是否為final或者abstract)
2. 類的方法、方法是否存在、方法返回值、方法的註釋、Method Names
3. 類的屬性,靜態屬性,常量
4. 所在名稱空間 Namespace

通過三個例子來理解反射

1.最簡單的反射

class HandsonBoy
{
    public $name = 'Leon';
    public $age = 25;

    public function __set($name,$value)
    {
        echo '您正在設定私有屬性'.$name.'<br >值為'.$value.'<br>';
        $this->$name = $value;
    }
    public function __get($name)
    {
        if(!isset($this->$name))
        {
            echo
'未設定'.$name; $this->$name = "正在為你設定預設值".'<br>'; } return $this->$name; } public function seeMe() { } } $boy = new HandsonBoy(); echo $boy->name.'<br />'; $boy->hair = 'short'; # 用反射來獲取類的屬性 $reflect = new ReflectionObject($boy); $props
= $reflect->getProperties(); //反射遍歷屬性 foreach ($props as $prop){ var_dump($prop->getName().PHP_EOL); } //獲取物件方法列表 $methods = $reflect->getMethods(); foreach ($methods as $method){ var_dump($method->getName().PHP_EOL); } # 當然也可以直接用 get_object_vars 等函式來實現獲取類的屬性和方法 //物件 //var_dump(get_object_vars($boy)); //var_dump(get_class_vars(get_class($boy))); //返回由類的屬性的方法名組成的陣列 //var_dump(get_class_methods(get_class($boy))); # 列印結果如下: Leon 您正在設定私有屬性hair 值為short string(5) "name " string(4) "age " string(5) "hair " string(6) "__set " string(6) "__get " string(6) "seeMe "

2.一個反射簡單的動態代理的例子,操作 sqlproxy ,通過動態傳參來代替實際的類(mysql)的執行


class mysql
{
    function connect($db)
    {
        echo "連線到資料庫{$db[0]}".PHP_EOL;
    }
}
class sqlproxy
{
    private $target;
    public function __construct($tar)
    {
        $this->target[] = new $tar;
    }
    public function __call($name,$args)
    {
//        var_dump('name:'.$name);connect
//        var_dump($args);siwei
        foreach($this->target as $obj)
        {
            $r = new ReflectionClass($obj);//獲取 mysql_Class 的反射類
            if($method = $r->getMethod($name))
            {
                if($method->isPublic() && !$method->isAbstract())
                {
                    echo "方法前攔截記錄LOG".PHP_EOL;
                    $method->invoke($obj,$args);//呼叫 obj = 例項化之後的mysql類物件的  args =siwei
                    echo "方法後攔截".PHP_EOL;
                }
            }
        }
    }
}
$obj = new sqlproxy('mysql');//例項化sqlproxy 類,初始化 $this->target 賦值 = new mysql()
$obj->connect('siwei'); //呼叫了魔術方法 __call  name是方法名:connect ||  args 為 引數陣列,siwei
// 遍歷初始化傳過來的 mysql 的反射類  ,然後獲取 connect 的方法,存到method 變數中
// 判斷 method 是否是抽象方法,如果不是,呼叫反射的method 的 invoke(new mysql(),siwei)

3.基於反射實現的容器,依賴注入DI

<?php
/**
 *
 * 工具類,使用該類來實現自動依賴注入。
 *
 * 使用php的反射函式,建立了一個容器類,使用該類來實現其他類的依賴注入功能。
 *
 * 依賴注入分為兩種,
 * 一種是建構函式的依賴注入,
 * 一種是方法的依賴注入。
 * 我們使用下面三個類來做下測試。
 */
class Ioc {

    // 獲得類的物件例項
    public static function getInstance($className) {

        $paramArr = self::getMethodParams($className);

        return (new ReflectionClass($className))->newInstanceArgs($paramArr);
    }

    /**
     * 執行類的方法
     * @param  [type] $className  [類名]
     * @param  [type] $methodName [方法名稱]
     * @param  [type] $params     [額外的引數]
     * @return [type]             [description]
     */
    public static function make($className, $methodName, $params = []) {

        // 獲取類的例項
        $instance = self::getInstance($className);

        // 獲取該方法所需要依賴注入的引數
        $paramArr = self::getMethodParams($className, $methodName);

        return $instance->{$methodName}(...array_merge($paramArr, $params)); #將 合併之後的陣列作為函式引數
    }

    /**
     * 獲得類的方法引數,只獲得有型別的引數
     * @param  [type] $className   [description]
     * @param  [type] $methodsName [description]
     * @return [type]              [description]
     */
    protected static function getMethodParams($className, $methodsName = '__construct') {

        // 通過反射獲得該類
        $class = new ReflectionClass($className);
        $paramArr = []; // 記錄引數,和引數型別

        // 判斷該類是否有建構函式
        if ($class->hasMethod($methodsName)) {
            // 獲得建構函式
            $construct = $class->getMethod($methodsName);

            // 判斷建構函式是否有引數
            $params = $construct->getParameters();

            if (count($params) > 0) {

                // 判斷引數型別
                foreach ($params as $key => $param) {

                    if ($paramClass = $param->getClass()) {

                        // 獲得引數型別名稱
                        $paramClassName = $paramClass->getName();

                        // 獲得引數型別
                        $args = self::getMethodParams($paramClassName);
                        $paramArr[] = (new ReflectionClass($paramClass->getName()))->newInstanceArgs($args);
                    }
                }
            }
        }

        return $paramArr;
    }
}
class A {

    protected $cObj;

    /**
     * 用於測試多級依賴注入 B依賴A,A依賴C
     * @param C $c [description]
     */
    public function __construct(C $c) {

        $this->cObj = $c;
    }

    public function aa() {

        echo 'this is A->test';
    }

    public function aac() {

        $this->cObj->cc();
    }
}

class B {

    protected $aObj;

    /**
     * 測試建構函式依賴注入
     * @param A $a [使用引來注入A]
     */
    public function __construct(A $a) {

        $this->aObj = $a;
    }

    /**
     * [測試方法呼叫依賴注入]
     * @param  C      $c [依賴注入C]
     * @param  string $b [這個是自己手動填寫的引數]
     * @return [type]    [description]
     */
    public function bb(C $c, $b) {

        $c->cc();
        echo "\r\n";

        echo 'params:' . $b;
    }

    /**
     * 驗證依賴注入是否成功
     * @return [type] [description]
     */
    public function bbb() {

        $this->aObj->aac();
    }
}

class C {

    public function cc() {

        echo 'this is C->cc';
    }
}


// 使用Ioc反射 來建立B類的例項,B的建構函式依賴A類,A的建構函式依賴C類。最終呼叫 C的 cc方法 this is C->cc , 說明依賴注入成功。
$bObj = Ioc::getInstance('B');
$bObj->bbb();
var_dump($bObj);


# 列印結果

this is C->cc

object(B)#3 (1) {
  ["aObj":protected]=>
  object(A)#7 (1) {
    ["cObj":protected]=>
    object(C)#10 (0) {
    }
  }
}

接下來會寫一篇部落格,記錄反射 在 laravel 中 di 、ioc 的 應用
本文用到的一些函式和魔術方法,可以參考 一些 PHP 類相關的函式