浅析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 相关文章推荐
一个阿拉伯数字转中文数字的函数
Oct 09 PHP
php xml文件操作实现代码(二)
Mar 20 PHP
谈PHP生成静态页面分析 模板+缓存+写文件
Aug 17 PHP
CentOS 6.3下安装PHP xcache扩展模块笔记
Sep 10 PHP
dedecms中使用php语句指南
Nov 13 PHP
Yii把CGridView文本框换成下拉框的方法
Dec 03 PHP
php根据日期显示所在星座的方法
Jul 13 PHP
PHP对XML内容进行修改和删除实例代码
Oct 26 PHP
php实现购物车功能(以大苹果购物网为例)
Mar 09 PHP
thinkPHP5.0框架应用请求生命周期分析
Mar 25 PHP
PHP读取并输出XML文件数据的简单实现方法
Dec 22 PHP
thinkphp框架类库扩展操作示例
Nov 26 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 创建标签云函数代码
2010/05/26 PHP
基于PHP遍历数组的方法汇总分析
2013/06/08 PHP
php常用的url处理函数总结
2014/11/19 PHP
php截取字符串函数分享
2015/02/02 PHP
php获取本周星期一具体日期的方法
2015/04/20 PHP
学习PHP Cookie处理函数
2016/08/09 PHP
php is_writable判断文件是否可写实例代码
2016/10/13 PHP
tp5.0框架隐藏index.php入口文件及模块和控制器的方法分析
2020/02/11 PHP
加速IE的Javascript document输出的方法
2010/12/02 Javascript
拉动滚动条加载数据的jquery代码
2012/05/03 Javascript
JavaScript动态生成二维码图片
2016/04/20 Javascript
微信小程序 出现错误:{"baseresponse":{"errcode":-80002,"errmsg":""}}解决办法
2017/02/23 Javascript
微信小程序之购物车功能
2020/09/23 Javascript
js如何获取网页所有图片
2017/05/12 Javascript
js实现分页功能
2017/05/24 Javascript
深入浅析var,let,const的异同点
2018/08/07 Javascript
小程序文字跑马灯效果
2018/12/28 Javascript
函数式编程入门实践(一)
2019/04/20 Javascript
jQuery+ajax实现批量删除功能完整示例
2019/06/06 jQuery
浅析VUE防抖与节流
2020/11/24 Vue.js
新手该如何学python怎么学好python?
2008/10/07 Python
10个Python小技巧你值得拥有
2018/09/29 Python
Python中new方法的详解
2019/01/15 Python
python多线程同步实例教程
2019/08/11 Python
利用anaconda作为python的依赖库管理方法
2019/08/13 Python
解决python Jupyter不能导入外部包问题
2020/04/15 Python
python与js主要区别点总结
2020/09/13 Python
使用python实现学生信息管理系统
2021/02/25 Python
使用html5制作loading图的示例
2014/04/14 HTML / CSS
Kiwi.com中国:找到特价机票并发现新目的地
2019/10/27 全球购物
Windows和Linux动态库应用异同
2016/07/28 面试题
大学生学年自我鉴定
2014/02/10 职场文书
房产公证书范本
2014/04/10 职场文书
工业设计毕业生自荐信
2014/04/13 职场文书
地陪导游欢迎词
2015/01/26 职场文书
离婚承诺书格式范文
2015/05/04 职场文书