浅析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+mysql一个名片库程序
Oct 09 PHP
PHP截取汉字乱码问题解决方法mb_substr函数的应用
Mar 30 PHP
PHP使用PHPMailer发送邮件的简单使用方法
Nov 12 PHP
php实现加减法验证码代码
Feb 14 PHP
PHP在网页中动态生成PDF文件详细教程
Jul 05 PHP
在WordPress中实现评论头像的自定义默认和延迟加载
Nov 24 PHP
thinkphp实现图片上传功能
Jan 13 PHP
PHP简单实现上一页下一页功能示例
Sep 14 PHP
PHP串行化与反串行化实例分析
Dec 27 PHP
PHP基于堆栈实现的高级计算器功能示例
Sep 15 PHP
PHP分页显示的方法分析【附PHP通用分页类】
May 10 PHP
PHP+RabbitMQ实现消息队列的完整代码
Mar 20 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
php用户注册页面利用js进行表单验证具体实例
2013/10/17 PHP
php fsockopen解决办法 php实现多线程
2014/01/20 PHP
PHP用星号隐藏部份用户名、身份证、IP、手机号等实例
2014/04/08 PHP
在WordPress中实现评论头像的自定义默认和延迟加载
2015/11/24 PHP
Yii2使用dropdownlist实现地区三级联动功能的方法
2016/07/18 PHP
PHP 进度条函数的简单实例
2017/09/19 PHP
PHP 并发场景的几种解决方案
2019/06/14 PHP
JavaScript DOM 学习第三章 内容表格
2010/02/19 Javascript
Jquery异步请求数据实例代码
2011/12/28 Javascript
Web Inspector:关于在 Sublime Text 中调试Js的介绍
2013/04/18 Javascript
js中onload与onunload的使用示例
2013/08/25 Javascript
纯JS实现根据CSS的class选择DOM
2014/03/22 Javascript
JavaScript和JQuery的鼠标mouse事件冒泡处理
2015/06/19 Javascript
详解iframe与frame的区别
2016/01/13 Javascript
D3.js实现雷达图的方法详解
2016/09/22 Javascript
Node.js中常规的文件操作总结
2016/10/13 Javascript
vue登录以及权限验证相关的实现
2019/10/25 Javascript
javascript实现前端成语点击验证优化
2020/06/24 Javascript
python时间整形转标准格式的示例分享
2014/02/14 Python
python实现html转ubb代码(html2ubb)
2014/07/03 Python
完美解决Python2操作中文名文件乱码的问题
2017/01/04 Python
python itsdangerous模块的具体使用方法
2020/02/17 Python
Python3通过chmod修改目录或文件权限的方法示例
2020/06/08 Python
Python 爬虫的原理
2020/07/30 Python
基于python实现图片转字符画代码实例
2020/09/04 Python
HTML5探秘:用requestAnimationFrame优化Web动画
2018/06/03 HTML / CSS
HTML5 Canvas玩转酷炫大波浪进度图效果实例(附demo)
2016/12/14 HTML / CSS
Moda Italia荷兰:意大利男士服装
2019/08/31 全球购物
回门宴新郎答谢词
2014/01/12 职场文书
汽车运用工程专业求职信
2014/06/18 职场文书
三方协议书
2015/01/27 职场文书
二审答辩状格式
2015/05/22 职场文书
中考百日冲刺决心书
2015/09/22 职场文书
go 实现简易端口扫描的示例
2021/05/22 Golang
《艾尔登法环》1.03.3补丁上线 碎星伤害调整
2022/04/06 其他游戏
Python面试不修改数组找出重复的数字
2022/05/20 Python