浅析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版本实现代码
Sep 15 PHP
php教程之魔术方法的使用示例(php魔术函数)
Feb 12 PHP
php冒泡排序、快速排序、快速查找、二维数组去重实例分享
Apr 24 PHP
yii操作session实例简介
Jul 31 PHP
php基于base64解码图片与加密图片还原实例
Nov 03 PHP
PHP+jQuery+Ajax实现用户登录与退出
Apr 27 PHP
PHP使用Pear发送邮件(Windows环境)
Jan 05 PHP
php mysql 封装类实例代码
Sep 18 PHP
利用php操作memcache缓存的基础方法示例
Aug 02 PHP
深入理解PHP的远程多会话调试
Sep 21 PHP
thinkPHP框架实现的无限回复评论功能示例
Jun 09 PHP
Yii2处理密码加密及验证的方法
May 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
数据库中排序的对比及使用条件详解
2012/02/23 PHP
解析php5配置使用pdo
2013/07/03 PHP
处理(php-cgi.exe - FastCGI 进程超过了配置的请求超时时限)的问题
2013/07/03 PHP
php支持断点续传、分块下载的类
2016/05/02 PHP
yii2实现Ueditor百度编辑器的示例代码
2018/11/02 PHP
JXTree对象,读取外部xml文件数据,生成树的函数
2007/04/02 Javascript
JavaScript学习笔记(十)
2010/01/17 Javascript
style、 currentStyle、 runtimeStyle区别分析
2010/08/01 Javascript
js charAt的使用示例
2014/02/18 Javascript
jQuery操作属性和样式详解
2016/04/13 Javascript
JS 全屏和退出全屏详解及实例代码
2016/11/07 Javascript
基于bootstrap-datetimepicker.js不支持IE8的快速解决方法
2016/11/07 Javascript
JavaScript之cookie技术详解
2016/11/18 Javascript
你有必要知道的10个JavaScript难点
2017/07/25 Javascript
JavaScript实现body内任意节点的自定义属性功能示例
2017/09/18 Javascript
JavaScript编程设计模式之观察者模式(Observer Pattern)实例详解
2017/10/25 Javascript
利用10行js代码实现上下滚动公告效果
2017/12/08 Javascript
微信小程序实现导航栏选项卡效果
2020/06/19 Javascript
jQuery插件jsonview展示json数据
2018/05/26 jQuery
深入理解 TypeScript Reflect Metadata
2019/12/12 Javascript
Vue组件为什么data必须是一个函数
2020/06/11 Javascript
使用JavaScript实现贪吃蛇游戏
2020/09/29 Javascript
python re模块的高级用法详解
2018/06/06 Python
在python中对变量判断是否为None的三种方法总结
2019/01/23 Python
python实现快速文件格式批量转换的方法
2020/10/16 Python
python 实现百度网盘非会员上传超过500个文件的方法
2021/01/07 Python
CSS3实现歌词进度文字颜色填充变化动态效果的思路详解
2020/06/02 HTML / CSS
html5的canvas方法使用指南
2014/12/15 HTML / CSS
html5 canvas 实现光线沿不规则路径运动
2020/04/20 HTML / CSS
安全检查管理制度
2014/02/02 职场文书
网络营销策划方案
2014/06/04 职场文书
优秀班主任材料
2014/12/16 职场文书
酒店辞职书范文
2015/02/26 职场文书
2016入党积极分子党校培训心得体会
2016/01/06 职场文书
社区干部培训心得体会
2016/01/06 职场文书
Nginx的反向代理实例详解
2021/03/31 Servers