资讯专栏INFORMATION COLUMN

反射在 PHP 中的应用

stormgens / 1983人阅读

摘要:反射在每个面向对象的编程语言中都存在,它的主要目的就是在运行时分析类或者对象的状态,导出或提取出关于类方法属性参数等的详细信息,包括注释。反射是操纵面向对象范型中元模型的,可用于构建复杂,可扩展的应用。

反射在每个面向对象的编程语言中都存在,它的主要目的就是在运行时分析类或者对象的状态,导出或提取出关于类、方法、属性、参数等的详细信息,包括注释。 反射是操纵面向对象范型中元模型的 API,可用于构建复杂,可扩展的应用。反射在日常的 Web 开发中其实用的不多,更多的是在偏向底层一些的代码中,比如说框架的底层中依赖注入、对象池、动态代理、自动获取插件列表、自动生成文档以及一些设计模式等等,都会大量运用到反射技术。
PHP 的反射 API 很多,但是常用的一般都是 ReflectionClassReflectionMethod:
1.ReflectionClass
这个是用来获取类的信息,可以简单测试一下:

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 设置方法是否访问

接下来说一些反射在实际开发中比较常见的应用。

执行私有方法

其实反射不仅可以执行私有方法,还可以读取私有属性。这个主要应用在一些设计不合理的 SDK 里面,一些很好用的方法和属性却不对外开放。

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

执行私有方法:

$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方法实际上底层也是调用了IlluminateContainerContaiern::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);
    }

实际代码很简单,反射类获取构造方法,然后解析依赖参数,传入执行。

欢迎关我的个人公众号:左手代码

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/26314.html

相关文章

  • PHP通过反射来得到类,以及一些基本的应用

    摘要:发现大量的使用了反射机制。下面就来简单看看一些反射的应用获得反射下面我们来通过这个反射来得到的私有属性得到结果得到这样我们就可以很轻松的获得的私有属性了。最后通过执行该方法反射还有很多可用的方法,这里就不一一说了。 这几天在看laravel框架的核心代码。发现大量的使用了反射机制。下面就来简单看看一些反射的应用 class A { private $_foo = this i...

    BlackHole1 评论0 收藏0
  • PHP反射机制

    摘要:反射机制反射机制从开始支持,做业务开发的话应该很少接触反射。我的理解就是反射机制能拿到类里面的属性方法,和的也可以以上是官方文档中给出的东西,说实话我看了感觉没什么感觉。在容器成员变量中数组维护这个类,反射实例调用构造函数,获取返回值。 PHP反射机制 PHP反射机制从PHP5开始支持,做业务开发的话应该很少接触反射。我其实也是接触不多,最近在学习laravel的优雅,就接触了到它其中...

    URLOS 评论0 收藏0
  • Laravel深入学习2 - 控制反转容器

    摘要:控制反转容器控制反转使依赖注入变得更加便捷。有瑕疵控制反转容器是实现的控制翻转容器的一种替代方案。容器的独立使用即使没有使用框架,我们仍然可以在项目中使用安装组件来使用的控制反转容器。在没有给定任何信息的情况下,容器是无法实例化相关依赖的。 声明:本文并非博主原创,而是来自对《Laravel 4 From Apprentice to Artisan》阅读的翻译和理解,当然也不是原汁原味...

    worldligang 评论0 收藏0
  • PHP核心技术与最佳实践(第一章 面向对象思想的核心概念)

    摘要:现代的面向对象的思想不强调为真实世界建模变得更加理性化一些,把目标放在解耦上。各种语言中的多态多态确切的含义是同一类的对象收到相同消息时,会得到不同的结果。小结本章主要介绍面向对象思想的程序的组成元素类和对象。 第一章 面向对象思想的核心概念 showImg(https://segmentfault.com/img/bVNfjM?w=673&h=334); showImg(https:...

    dreamGong 评论0 收藏0
  • 从yii2框架中的di容器源码中了解反射的作用

    摘要:反射简介参考官方简介的话,具有完整的反射,添加了对类接口函数方法和扩展进行反向工程的能力。此外,反射提供了方法来取出函数类和方法中的文档注释。 反射简介 参考官方简介的话,PHP 5 具有完整的反射 API,添加了对类、接口、函数、方法和扩展进行反向工程的能力。 此外,反射 API 提供了方法来取出函数、类和方法中的文档注释。 YII2框架中示例 对于yii2框架,应该都知道di容器,...

    dantezhao 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<