浅析PHP类的反射来实现依赖注入过程


Posted in PHP onFebruary 06, 2018

PHP具有完整的反射 API,提供了对类、接口、函数、方法和扩展进行逆向工程的能力。通过类的反射提供的能力我们能够知道类是如何被定义的,它有什么属性、什么方法、方法都有哪些参数,类文件的路径是什么等很重要的信息。也正式因为类的反射很多PHP框架才能实现依赖注入自动解决类与类之间的依赖关系,这给我们平时的开发带来了很大的方便。 本文主要是讲解如何利用类的反射来实现依赖注入(Dependency Injection),并不会去逐条讲述PHP Reflection里的每一个API,详细的API参考信息请查阅官方文档

为了更好地理解,我们通过一个例子来看类的反射,以及如何实现依赖注入。

下面这个类代表了坐标系里的一个点,有两个属性横坐标x和纵坐标y。

/**
 * Class Point
 */
class Point
{
  public $x;
  public $y;

  /**
   * Point constructor.
   * @param int $x horizontal value of point's coordinate
   * @param int $y vertical value of point's coordinate
   */
  public function __construct($x = 0, $y = 0)
  {
    $this->x = $x;
    $this->y = $y;
  }
}

接下来这个类代表圆形,可以看到在它的构造函数里有一个参数是Point类的,即Circle类是依赖与Point类的。

class Circle
{
  /**
   * @var int
   */
  public $radius;//半径

  /**
   * @var Point
   */
  public $center;//圆心点

  const PI = 3.14;

  public function __construct(Point $point, $radius = 1)
  {
    $this->center = $point;
    $this->radius = $radius;
  }
  
  //打印圆点的坐标
  public function printCenter()
  {
    printf('center coordinate is (%d, %d)', $this->center->x, $this->center->y);
  }

  //计算圆形的面积
  public function area()
  {
    return 3.14 * pow($this->radius, 2);
  }
}

ReflectionClass

下面我们通过反射来对Circle这个类进行反向工程。

把Circle类的名字传递给reflectionClass来实例化一个ReflectionClass类的对象。

$reflectionClass = new reflectionClass(Circle::class);
//返回值如下
object(ReflectionClass)#1 (1) {
 ["name"]=>
 string(6) "Circle"
}

反射出类的常量

$reflectionClass->getConstants();

返回一个由常量名称和值构成的关联数组

array(1) {
 ["PI"]=>
 float(3.14)
}

通过反射获取属性

$reflectionClass->getProperties();

返回一个由ReflectionProperty对象构成的数组

array(2) {
 [0]=>
 object(ReflectionProperty)#2 (2) {
  ["name"]=>
  string(6) "radius"
  ["class"]=>
  string(6) "Circle"
 }
 [1]=>
 object(ReflectionProperty)#3 (2) {
  ["name"]=>
  string(6) "center"
  ["class"]=>
  string(6) "Circle"
 }
}

反射出类中定义的方法

$reflectionClass->getMethods();

返回ReflectionMethod对象构成的数组

array(3) {
 [0]=>
 object(ReflectionMethod)#2 (2) {
  ["name"]=>
  string(11) "__construct"
  ["class"]=>
  string(6) "Circle"
 }
 [1]=>
 object(ReflectionMethod)#3 (2) {
  ["name"]=>
  string(11) "printCenter"
  ["class"]=>
  string(6) "Circle"
 }
 [2]=>
 object(ReflectionMethod)#4 (2) {
  ["name"]=>
  string(4) "area"
  ["class"]=>
  string(6) "Circle"
 }
}

我们还可以通过getConstructor()来单独获取类的构造方法,其返回值为一个ReflectionMethod对象。

$constructor = $reflectionClass->getConstructor();

反射出方法的参数

$parameters = $constructor->getParameters();

其返回值为ReflectionParameter对象构成的数组。

array(2) {
 [0]=>
 object(ReflectionParameter)#3 (1) {
  ["name"]=>
  string(5) "point"
 }
 [1]=>
 object(ReflectionParameter)#4 (1) {
  ["name"]=>
  string(6) "radius"
 }
}

依赖注入

好了接下来我们编写一个名为make的函数,传递类名称给make函数返回类的对象,在make里它会帮我们注入类的依赖,即在本例中帮我们注入Point对象给Circle类的构造方法。

//构建类的对象
function make($className)
{
  $reflectionClass = new ReflectionClass($className);
  $constructor = $reflectionClass->getConstructor();
  $parameters = $constructor->getParameters();
  $dependencies = getDependencies($parameters);
  
  return $reflectionClass->newInstanceArgs($dependencies);
}

//依赖解析
function getDependencies($parameters)
{
  $dependencies = [];
  foreach($parameters as $parameter) {
    $dependency = $parameter->getClass();
    if (is_null($dependency)) {
      if($parameter->isDefaultValueAvailable()) {
        $dependencies[] = $parameter->getDefaultValue();
      } else {
        //不是可选参数的为了简单直接赋值为字符串0
        //针对构造方法的必须参数这个情况
        //laravel是通过service provider注册closure到IocContainer,
        //在closure里可以通过return new Class($param1, $param2)来返回类的实例
        //然后在make时回调这个closure即可解析出对象
        //具体细节我会在另一篇文章里面描述
        $dependencies[] = '0';
      }
    } else {
      //递归解析出依赖类的对象
      $dependencies[] = make($parameter->getClass()->name);
    }
  }

  return $dependencies;
}

定义好make方法后我们通过它来帮我们实例化Circle类的对象:

$circle = make('Circle');
$area = $circle->area();
/*var_dump($circle, $area);
object(Circle)#6 (2) {
 ["radius"]=>
 int(1)
 ["center"]=>
 object(Point)#11 (2) {
  ["x"]=>
  int(0)
  ["y"]=>
  int(0)
 }
}
float(3.14)*/

通过上面这个实例我简单描述了一下如何利用PHP类的反射来实现依赖注入,Laravel的依赖注入也是通过这个思路来实现的,只不过设计的更精密大量地利用了闭包回调来应对各种复杂的依赖注入。

源码分享:https://github.com/kevinyan815/php_reflection_dependency_injection_demo/blob/master/reflection.php

PHP 相关文章推荐
php,不用COM,生成excel文件
Oct 09 PHP
php array_intersect比array_diff快(附详细的使用说明)
Jul 03 PHP
php入门学习知识点二 PHP简单的分页过程与原理
Jul 14 PHP
PHP中用hash实现的数组
Jul 17 PHP
php生成QRcode实例
Sep 22 PHP
简单了解将WordPress中的工具栏移到底部的小技巧
Dec 31 PHP
PHP上传图片类显示缩略图功能
Jun 30 PHP
php compact 通过变量创建数组
Nov 15 PHP
Laravel框架实现利用监听器进行sql语句记录功能
Jun 06 PHP
ThinkPHP框架整合微信支付之刷卡模式图文详解
Apr 10 PHP
PHP 实现文件压缩解压操作的方法
Jun 14 PHP
详解PHP服务器如何在有限的资源里最大提升并发能力
May 25 PHP
php打开本地exe程序,js打开本地exe应用程序,并传递相关参数方法
Feb 06 #PHP
PHP给源代码加密的几种方法汇总(推荐)
Feb 06 #PHP
php 替换文章中的图片路径,下载图片到本地服务器的方法
Feb 06 #PHP
PHP定义字符串的四种方式详解
Feb 06 #PHP
浅谈PHP中pack、unpack的详细用法
Mar 12 #PHP
阿里云Win2016安装Apache和PHP环境图文教程
Mar 11 #PHP
Yii2 中实现单点登录的方法
Mar 09 #PHP
You might like
可快速识别放射性物质-国外大神教你diy一个开放式辐射探测器
2020/03/12 无线电
PHP 防注入函数(格式化数据)
2011/08/08 PHP
PHP zip扩展Linux下安装过程分享
2014/05/05 PHP
什么情况下可以不写PHP的闭合标签“?>”
2014/08/28 PHP
ThinkPHP中where()使用方法详解
2016/04/19 PHP
Yii2框架实现注册和登录教程
2016/09/30 PHP
php 比较获取两个数组相同和不同元素的例子(交集和差集)
2019/10/18 PHP
jQuery操作Select的Option上下移动及移除添加等等
2013/11/18 Javascript
JavaScript创建闭包的两种方式的优劣与区别分析
2015/06/22 Javascript
jQuery原型属性和原型方法详解
2015/07/07 Javascript
微信js-sdk分享功能接口常用逻辑封装示例
2016/10/13 Javascript
BootStrap 模态框实现刷新网页并关闭功能
2017/01/04 Javascript
AngularJS学习第二篇 AngularJS依赖注入
2017/02/13 Javascript
Javascript实现的StopWatch功能示例
2017/06/13 Javascript
jQuery:unbind方法的使用详解
2017/08/14 jQuery
微信小程序实现action-sheet弹出底部菜单功能【附源码下载】
2017/12/09 Javascript
详解vue axios用post提交的数据格式
2018/08/07 Javascript
小程序如何自主实现拦截器的示例代码
2019/11/04 Javascript
vue使用video插件vue-video-player详解
2020/10/23 Javascript
Python编程实现粒子群算法(PSO)详解
2017/11/13 Python
python抽取指定url页面的title方法
2018/05/11 Python
Python插入Elasticsearch操作方法解析
2020/01/19 Python
新版Pycharm中Matplotlib不会弹出独立的显示窗口的问题
2020/06/02 Python
Python实现Kerberos用户的增删改查操作
2020/12/14 Python
python脚本定时发送邮件
2020/12/22 Python
解析HTML5的存储功能和web SQL的相关操作方法
2016/02/19 HTML / CSS
汇科协同Java笔试题
2012/03/31 面试题
广播节目策划方案
2014/05/23 职场文书
银行求职信怎么写
2014/05/26 职场文书
党员批评与自我批评发言材料
2014/10/14 职场文书
2015年毕业实习工作总结
2014/12/12 职场文书
小学班主任工作总结2015
2015/04/07 职场文书
社区党支部承诺书
2015/04/29 职场文书
两行代码解决Jupyter Notebook中文不能显示的问题
2021/04/24 Python
解析原生JS getComputedStyle
2021/05/25 Javascript
Java由浅入深通关抽象类与接口(下篇)
2022/04/26 Java/Android