浅析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小技巧搜集,每个PHPer都来露一手
Jan 02 PHP
elgg 获取文件图标地址的方法
Mar 20 PHP
PHP 类商品秒杀计时实现代码
May 05 PHP
php中设置多级目录session的问题
Aug 08 PHP
Discuz批量替换帖子内容的方法(使用SQL更新数据库)
Jun 23 PHP
Yii实现多数据库主从读写分离的方法
Dec 29 PHP
Laravel 5框架学习之路由、控制器和视图简介
Apr 07 PHP
php使用escapeshellarg时中文被过滤的解决方法
Jul 10 PHP
php、java、android、ios通用的3des方法(推荐)
Sep 09 PHP
PHP命名空间namespace的定义方法详解
Mar 29 PHP
PHP调用其他文件中的类
Apr 02 PHP
laravel框架中控制器的创建和使用方法分析
Nov 23 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定时计划任务的实现方法详解
2013/06/06 PHP
yii实现model添加默认值的方法(2种方法)
2016/01/06 PHP
巧妙破除网页右键禁用的十大绝招
2006/08/12 Javascript
JS日历 推荐
2006/12/03 Javascript
extjs grid设置某列背景颜色和字体颜色的实现方法
2010/09/06 Javascript
javascript判断iphone/android手机横竖屏模式的函数
2011/12/20 Javascript
根据身份证号自动输出相关信息(籍贯,出身日期,性别)
2013/11/15 Javascript
jquery复选框多选赋值给文本框的方法
2015/01/27 Javascript
php结合imgareaselect实现图片裁剪
2015/07/05 Javascript
JS实现仿新浪微博发布内容为空时提示功能代码
2015/08/19 Javascript
JS动态改变浏览器标题的方法
2016/04/06 Javascript
Bootstrap分页插件之Bootstrap Paginator实例详解
2016/10/15 Javascript
AngularJS操作键值对象类似java的hashmap(填坑小结)
2016/11/12 Javascript
React 子组件向父组件传值的方法
2017/07/24 Javascript
vue+Element-ui实现分页效果实例代码详解
2018/12/10 Javascript
javascript对HTML字符转义与反转义
2018/12/13 Javascript
Vue路由守卫之路由独享守卫
2019/09/25 Javascript
详解node登录接口之密码错误限制次数(含代码)
2019/10/25 Javascript
JavaScript ECMA-262-3 深入解析(一):执行上下文实例分析
2020/04/25 Javascript
Vue之封装公用变量以及实现方式
2020/07/31 Javascript
Vue实现todo应用的示例
2021/02/20 Vue.js
Python中用PIL库批量给图片加上序号的教程
2015/05/06 Python
Python随机函数random()使用方法小结
2018/04/29 Python
python 匹配url中是否存在IP地址的方法
2018/06/04 Python
详解python使用pip安装第三方库(工具包)速度慢、超时、失败的解决方案
2018/12/02 Python
在python中计算ssim的方法(与Matlab结果一致)
2019/12/19 Python
python读取与处理netcdf数据方式
2020/02/14 Python
Python项目跨域问题解决方案
2020/06/22 Python
Pytorch如何切换 cpu和gpu的使用详解
2021/03/01 Python
求职简历推荐信范文
2013/12/02 职场文书
怎样写离婚协议书
2014/09/10 职场文书
中级会计大学生职业生涯规划书
2014/09/16 职场文书
2014年控辍保学工作总结
2014/12/08 职场文书
故意伤害罪辩护词
2015/05/21 职场文书
详解JAVA中的OPTIONAL
2021/06/14 Java/Android
详解OpenCV曝光融合
2022/04/29 Python