浅析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 相关文章推荐
教你IIS6的PHP最佳配置方法
Sep 05 PHP
2.PHP入门
Oct 09 PHP
PHP 网页过期时间的控制代码
Jun 29 PHP
php session和cookie使用说明
Apr 07 PHP
php地址引用(php地址引用的效率问题)
Mar 23 PHP
php提示无法加载或mcrypt没有找到 PHP 扩展 mbstring解决办法
Mar 27 PHP
PHP引用符&的用法详细解析
Aug 22 PHP
php阿拉伯数字转中文人民币大写
Dec 21 PHP
我整理的PHP 7.0主要新特性
Jan 07 PHP
[原创]php求圆周率的简单实现方法
May 30 PHP
Codeigniter里的无刷新上传的实现代码
Apr 14 PHP
Laravel解决nesting level错误和隐藏index.php的问题
Oct 12 PHP
php打开本地exe程序,js打开本地exe应用程序,并传递相关参数方法
Feb 06 #PHP
PHP给源代码加密的几种方法汇总(推荐)
Feb 06 #PHP
php 替换文章中的图片路径,下载图片到本地服务器的方法
Feb 06 #PHP
PHP定义字符串的四种方式详解
Feb 06 #PHP
PHP异步进程助手async-helper
Feb 05 #PHP
详解PHP中的外观模式facade pattern
Feb 05 #PHP
浅析PHP开发规范
Feb 05 #PHP
You might like
咖啡语言
2021/03/03 咖啡文化
php异常:Parse error: syntax error, unexpected T_ENCAPSED_AND_WHITESPACE  eval()'d code error
2011/05/19 PHP
PHP开发中解决并发问题的几种实现方法分析
2017/11/13 PHP
javascript中字符串拼接详解
2014/09/26 Javascript
JavaScript 变量、作用域及内存
2015/04/08 Javascript
jQuery焦点图轮播插件KinSlideshow用法分析
2016/06/08 Javascript
jQuery排序插件tableSorter使用方法
2017/02/10 Javascript
深入理解Javascript中的观察者模式
2017/02/20 Javascript
原生JS实现幻灯片
2017/02/22 Javascript
详解如何使用Node.js编写命令工具——以vue-cli为例
2017/06/29 Javascript
对layui中表单元素的使用详解
2018/08/15 Javascript
Angular(5.2->6.1)升级小结
2018/12/27 Javascript
javascript触发模拟鼠标点击事件
2019/06/26 Javascript
layui复选框限制选择个数的方法
2019/09/18 Javascript
[01:02:26]DOTA2-DPC中国联赛 正赛 SAG vs RNG BO3 第二场 1月18日
2021/03/11 DOTA
tornado框架blog模块分析与使用
2013/11/21 Python
python的dataframe和matrix的互换方法
2018/04/11 Python
从django的中间件直接返回请求的方法
2018/05/30 Python
Python hashlib模块用法实例分析
2018/06/12 Python
mac安装scrapy并创建项目的实例讲解
2018/06/13 Python
django缓存配置的几种方法详解
2018/07/16 Python
Python引用计数操作示例
2018/08/23 Python
Python实现FTP文件传输的实例
2019/07/07 Python
解决Django layui {{}}冲突的问题
2019/08/29 Python
Python爬虫实例——爬取美团美食数据
2020/07/15 Python
Python Tricks 使用 pywinrm 远程控制 Windows 主机的方法
2020/07/21 Python
Python修改DBF文件指定列
2020/12/19 Python
CSS3选择器新增问题的实现
2021/01/21 HTML / CSS
size?德国官方网站:英国伦敦的球鞋精品店
2018/03/17 全球购物
工业自动化毕业生自荐信范文
2014/01/04 职场文书
初中生操行评语大全
2014/04/24 职场文书
2014年营销工作总结
2014/11/22 职场文书
道德与公民自我评价
2015/03/09 职场文书
婚育证明格式
2015/06/17 职场文书
简单聊聊Golang中defer预计算参数
2022/03/25 Golang
Python调用腾讯API实现人脸身份证比对功能
2022/04/04 Python