反射在 PHP 中的應用

反射在每一個面向對象的編程語言中都存在,它的主要目的就是在運行時分析類或者對象的狀態,導出或提取出關於類、方法、屬性、參數等的詳細信息,包括註釋。 反射是操縱面向對象範型中元模型的 API,可用於構建複雜,可擴展的應用。反射在平常的 Web 開發中其實用的很少,更多的是在偏向底層一些的代碼中,好比說框架的底層中依賴注入、對象池、動態代理、自動獲取插件列表、自動生成文檔以及一些設計模式等等,都會大量運用到反射技術。
PHP 的反射 API 不少,可是經常使用的通常都是 ReflectionClassReflectionMethod:
1.ReflectionClass
這個是用來獲取類的信息,能夠簡單測試一下:java

class Student {
    private    $name;

    public function setName($name)
    {
         $this->name = $name;
    }
 
    protected function getName()
    {
        return $this->name;
    }
 }

獲取類的方法列表:編程

$ref = new ReflectionClass(Student::class);
var_dump($ref->getMethods());

返回的是一個 ReflectionMethod 的數組:設計模式

array(1) {
  [0]=>
  object(ReflectionMethod)#2 (2) {
    ["name"]=>
    string(7) "setName"
    ["class"]=>
    string(7) "Student"
  }
}

附上一些經常使用方法,詳細的能夠查看文檔:數組

ReflectionClass::getMethods     獲取方法的數組
ReflectionClass::getName        獲取類名
ReflectionClass::hasMethod      檢查方法是否已定義
ReflectionClass::hasProperty    檢查屬性是否已定義
ReflectionClass::isAbstract     檢查類是不是抽象類(abstract)
ReflectionClass::isFinal        檢查類是否聲明爲 final
ReflectionClass::isInstantiable 檢查類是否可實例化
ReflectionClass::newInstance    從指定的參數建立一個新的類實例

2.ReflectionMethod
這個主要是針對方法的反射,咱們能夠簡單執行一下:框架

$stu = new Student();
$ref = new ReflectionClass(Student::class);
$method = $ref->getMethod('setName');
$method->invoke($stu, 'john');
var_dump($stu->name);

能夠輸出:編程語言

john

附上一些經常使用的方法,詳細的能夠去看看文檔:測試

ReflectionMethod::invoke        執行
ReflectionMethod::invokeArgs    帶參數執行
ReflectionMethod::isAbstract    判斷方法是不是抽象方法
ReflectionMethod::isConstructor 判斷方法是不是構造方法
ReflectionMethod::isDestructor  判斷方法是不是析構方法
ReflectionMethod::isFinal       判斷方法是否認義 final
ReflectionMethod::isPrivate     判斷方法是不是私有方法
ReflectionMethod::isProtected   判斷方法是不是保護方法 (protected)
ReflectionMethod::isPublic      判斷方法是不是公開方法
ReflectionMethod::isStatic      判斷方法是不是靜態方法
ReflectionMethod::setAccessible 設置方法是否訪問

接下來講一些反射在實際開發中比較常見的應用。ui

執行私有方法

其實反射不只能夠執行私有方法,還能夠讀取私有屬性。這個主要應用在一些設計不合理的 SDK 裏面,一些很好用的方法和屬性卻不對外開放。this

class Student {
    private    $name;
 
    private function setName($name)
    {
        $this->name = $name;
    }
}

執行私有方法:spa

$stu = new Student();
$ref = new ReflectionClass($stu);
$method = $ref->getMethod('setName');
$method->setAccessible(true);
$method->invoke($stu, 'john');

讀取私有屬性:

$stu = new Student();
$ref = new ReflectionClass($stu);
$prop = $ref->getProperty('name');
$prop->setAccessible(true);
$val = $prop->getValue($stu);
var_dump($val);

動態代理

其實 PHP 有魔術方法,因此實現動態代理已經很簡單了,可是經過魔術方法來實現的都不完美,我的理解最好的實現應該仍是 JDK 中的動態代理,基於一個接口進行掃描實現實在 PHP 中也能夠實現。咱們先來看看動態代理在 JDK 中是怎麼使用的:
1.首先定義一個實現類的接口,JDK 的動態代理必須基於接口(Cglib則不用)

package com.yao.proxy;
public interface Helloworld {
    void sayHello();
}

2.定義一個實現類,這個類就是要被代理的對象

package com.yao.proxy;
import com.yao.HelloWorld;

public class HelloworldImpl implements HelloWorld {
    public void sayHello() {
        System.out.print("hello world");
    }
}

3.調用被代理對象方法的實現類

package com.yao.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler{
    private Object target;
    public MyInvocationHandler(Object target) {
        this.target=target;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("前置工做!");
        Object obj = method.invoke(target,args);
        System.out.println("後置工做!");
        return obj;
    }

4.測試

package com.yao.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
 
public class Demo {
    public static void main(String[] args) {
        HelloworldImpl realSubject = new HelloworldImpl();
        MyInvocationHandler handler = new MyInvocationHandler(realSubject);
 
        ClassLoader loader = realSubject.getClass().getClassLoader();
        Class[] interfaces = realSubject.getClass().getInterfaces();
      
        HelloworldImpl proxySubject = (HelloworldImpl) Proxy.newProxyInstance(loader, interfaces, handler);
        String hello = proxySubject.sayHello();
    }
}

JDK 的動態代理在底層其實是掃描實現的接口,而後動態生成類的字節碼文件。PHP 是動態語言,因此能夠用 eval 來實現。
1.定義調度器接口

interface InvocationHandler  
{  
    function invoke($method, array $arr_args);  
}

2.動態代理實現
定義一個類的 stub:

return new Class($handler,$target) implements %s {  
    private $handler;  
    private $target;
    public function __construct(InvocationHandler $handler, $target) {
        $this->handler = $handler;  
        $this->target = $target;
    }  
    %s  
};

定義一個方法的 stub:

public function %s(%s) {  
    $args = func_get_args();  
    $method = explode("::", __METHOD__);  
    $this->handler->invoke(new ReflectionMethod($this->target, $method[1]), $args);
}

Proxy 實現:

final class Proxy  
{   
    const CLASS_TEMPLATE = class_stub;      //這裏顯示上面定義的,爲了方便閱讀
    const FUNCTION_TEMPLATE = function_stub;    //同上
 
    public static function newProxyInstance($target, array $interfaces, InvocationHandler $handler)  {

    }
    protected static function generateClass(array $interfaces) {
       
    }
    protected static function checkInterfaceExists(array $interfaces)  {

    }
}

其中 newProxyInstancegenerateClass 代碼:

public static function newProxyInstance($target, array $interfaces, InvocationHandler $handler)  {
        self::checkInterfaceExists ($interfaces);
        $code = self::generateClass ($interfaces);
        return eval($code);
    }
protected static function generateClass(array $interfaces)
    {
        $interfaceList = implode(',', $interfaces);
        $functionList = '';
        foreach ($interfaces as $interface) {
            $class = new ReflectionClass ($interface);
            $methods = $class->getMethods();
            foreach ($methods as $method){
                $parameters = [];
                foreach ($method->getParameters() as $parameter){
                    $parameters[] = '$' . $parameter->getName();
                }
                $functionList .= sprintf( self::FUNCTION_TEMPLATE, $method->getName(), implode( ',', $parameters ) );
            }
        }
        return sprintf ( self::CLASS_TEMPLATE, $interfaceList, $functionList );
    }

其中generateClass就是經過反射掃描接口方法,而後根據 stub 模板生成方法拼接成代碼,最後經過 eval 執行。

2.測試

interface Test1{
    public function t1();
}
interface Test2{
    public function t2();
}
class TestImpl implements Test1,Test2{
    public function t1(){
        echo 't1';
    }
    public function t2(){
        echo 't2';
    }
}
$impl = new TestImpl();
class Handler implements InvocationHandler {
    private $target;
    public function __construct($impl){
        $this->target = $impl;
    }
    function invoke(ReflectionMethod $method, array $arr_args){
        echo '前置操做';
        $method->invokeArgs($this->target, $arr_args);
        echo '後置操做';
    }
}

$proxy = Proxy::newProxyInstance($impl, ['Test1', 'Test2'], new Handler($impl));
$proxy->t1();

輸出:

前置操做
t1
後置操做

依賴注入

依賴注入是現代化框架中很是常見的一個功能,它必須和服務容器結合使用。用過 Laravel 框架的童鞋應該很熟悉,咱們能夠在任意須要服務的地方經過類型提示聲明,運行時框架就會自動幫咱們注入所須要的對象。以 Laravel 框架的源碼簡單解析下:
在 Laravel 框架中,咱們解析一個對象的方法能夠這樣:

$obj = App::make(ClassName);

make方法實際上底層也是調用了Illuminate\Container\Contaiern::build($concrete)這個方法,整理一下源碼就是:

public function build($concrete){
        $reflector = new ReflectionClass($concrete);
        $constructor = $reflector->getConstructor();

        if (is_null($constructor)) {
            return new $concrete;
        }

        $dependencies = $constructor->getParameters();
        $instances = $this->resolveDependencies($dependencies);
        return $reflector->newInstanceArgs($instances);
    }

實際代碼很簡單,反射類獲取構造方法,而後解析依賴參數,傳入執行。

歡迎關個人我的公衆號:左手代碼

相關文章
相關標籤/搜索