浅析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中的登陆login
Jan 18 PHP
PHP源码之 ext/mysql扩展部分
Jul 17 PHP
php在服务器执行exec命令失败的解决方法
Mar 03 PHP
关于Iframe如何跨域访问Cookie和Session的解决方法
Apr 15 PHP
PHP 关于访问控制的和运算符优先级介绍
Jul 08 PHP
PHP URL参数获取方式的四种例子
Feb 28 PHP
用 Composer构建自己的 PHP 框架之基础准备
Oct 30 PHP
全面解读PHP的人气开发框架Laravel
Oct 15 PHP
深入php内核之php in array
Nov 10 PHP
PHP数组去重比较快的实现方式
Jan 19 PHP
HTML中嵌入PHP的简单方法
Feb 16 PHP
ThinkPHP5.0 图片上传生成缩略图实例代码说明
Jun 20 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
php 常用的系统函数
2017/02/07 PHP
php变量与字符串的增删改查操作示例
2020/05/07 PHP
jquery 合并内容相同的单元格(示例代码)
2013/12/13 Javascript
jquery+php实现搜索框自动提示
2014/11/28 Javascript
Jquery和angularjs获取check框选中的值的方法汇总
2016/01/17 Javascript
微信小程序 wx.request(object) API详解及实例代码
2016/09/30 Javascript
浅析JS中对函数function的理解(基础篇)
2016/10/14 Javascript
JavaScript实现无穷滚动加载数据
2017/05/06 Javascript
js+html5实现半透明遮罩层弹框效果
2020/08/24 Javascript
使用axios实现上传图片进度条功能
2017/12/21 Javascript
微信小程序自定义tab实现多层tab嵌套功能
2018/06/15 Javascript
微信小程序出现wx.getLocation再次授权问题的解决方法分析
2019/01/16 Javascript
从组件封装看Vue的作用域插槽的实现
2019/02/12 Javascript
微信小程序实现消息框弹出动画
2020/04/18 Javascript
Vue中fragment.js使用方法小结
2020/02/17 Javascript
[45:46]2014 DOTA2国际邀请赛中国区预选赛5.21 HGT VS DT
2014/05/23 DOTA
Python中正则表达式详解
2017/05/17 Python
Python格式化输出字符串方法小结【%与format】
2018/10/29 Python
Python3 单行多行万能正则匹配方法
2019/01/07 Python
Python检查ping终端的方法
2019/01/26 Python
解决python 3 urllib 没有 urlencode 属性的问题
2019/08/22 Python
Tensorflow读取并输出已保存模型的权重数值方式
2020/01/04 Python
Python实现名片管理系统
2020/02/14 Python
使用Python文件读写,自定义分隔符(custom delimiter)
2020/07/05 Python
Python远程方法调用实现过程解析
2020/07/28 Python
Django+Django-Celery+Celery的整合实战
2021/01/20 Python
英国领先的杂志订阅网站:Magazine.co.uk
2018/01/25 全球购物
SkinCeuticals官网:美国药妆品牌
2018/04/19 全球购物
酒店周年庆活动方案
2014/08/21 职场文书
毕业生就业推荐表导师评语
2014/12/31 职场文书
2015年检验员工作总结范文
2015/04/30 职场文书
2015年民主评议党员工作总结
2015/05/19 职场文书
党员读书活动心得体会
2016/01/14 职场文书
MySQL为id选择合适的数据类型
2021/06/07 MySQL
Golang中channel的原理解读(推荐)
2021/10/16 Golang
Redis实现分布式锁的五种方法详解
2022/06/14 Redis