浅析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的cURL快速入门教程 (小偷采集程序)
Jun 02 PHP
PHP迭代器实现斐波纳契数列的函数
Nov 12 PHP
php准确计算复活节日期的方法
Apr 18 PHP
php中使用base HTTP验证的方法
Apr 20 PHP
PHP函数超时处理方法
Feb 14 PHP
php cookie工作原理与实例详解
Jul 18 PHP
浅谈PHP拦截器之__set()与__get()的理解与使用方法
Oct 18 PHP
Zend Framework上传文件重命名的实现方法
Nov 25 PHP
php+js实现百度地图多点标注的方法
Nov 30 PHP
Thinkphp 空操作、空控制器、命名空间(详解)
May 05 PHP
PHP中的empty、isset、isnull的区别与使用实例
Mar 22 PHP
关于laravel 子查询 & join的使用
Oct 16 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
超人钢铁侠联手合作?美漫作家呼吁DC漫威合作联动以抵抗疫情
2020/04/09 欧美动漫
菜鸟学PHP之Smarty入门
2007/01/04 PHP
PHP无限分类(树形类)的深入分析
2013/06/02 PHP
php中curl和file_get_content的区别
2014/05/10 PHP
php数组中删除元素之重新索引的方法
2014/09/16 PHP
基于PHP给大家讲解防刷票的一些技巧
2015/11/18 PHP
Paypal实现循环扣款(订阅)功能
2017/03/23 PHP
DOMAssitant最新版 DOMAssistant 2.5发布
2007/12/25 Javascript
javascript笔记 String类replace函数的一些事
2011/09/22 Javascript
跟我学习javascript的垃圾回收机制与内存管理
2015/11/23 Javascript
JS设置下拉列表框当前所选值的方法
2015/12/22 Javascript
AngularJS仿苹果滑屏删除控件
2016/01/18 Javascript
JavaScript函数中关于valueOf和toString的理解
2016/06/14 Javascript
一个超简单的jQuery回调函数例子(分享)
2016/08/08 Javascript
基于JavaScript实现自定义滚动条
2017/01/25 Javascript
js实现时间轴自动排列效果
2017/03/09 Javascript
详解vue2.0 transition 多个元素嵌套使用过渡
2017/06/19 Javascript
Vue和Bootstrap的整合思路详解
2017/06/30 Javascript
jQuery实现模糊搜索功能的方法分析
2018/06/29 jQuery
vue基础之v-bind属性、class和style用法分析
2019/03/11 Javascript
原生js实现移动小球(碰撞检测)
2020/12/17 Javascript
[03:37]2015国际邀请赛第四日现场精彩集锦
2015/08/08 DOTA
python中函数传参详解
2016/07/03 Python
keras实现VGG16方式(预测一张图片)
2020/07/07 Python
HTML5实现多张图片上传功能
2016/03/11 HTML / CSS
有关HTML5中背景音乐的自动播放功能
2017/10/16 HTML / CSS
小区门卫管理制度
2014/01/29 职场文书
大学生个人求职口试自我评价
2014/02/16 职场文书
党员实事承诺书
2014/03/26 职场文书
社会实践活动总结报告
2014/04/29 职场文书
三方股份合作协议书
2014/10/13 职场文书
民用住房租房协议书
2014/10/29 职场文书
员工辞职信怎么写
2015/02/27 职场文书
因身体原因离职的辞职信范文
2015/05/12 职场文书
大学生安全教育心得体会
2016/01/15 职场文书
2019年感恩励志演讲稿(收藏备用)
2019/09/11 职场文书