浅析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 zip文件解压类代码
Dec 02 PHP
通过缓存数据库结果提高PHP性能的原理介绍
Sep 05 PHP
PHP设置图片文件上传大小的具体实现方法
Oct 11 PHP
php中把美国时间转为北京时间的自定义函数分享
Jul 28 PHP
php之readdir函数用法实例
Nov 13 PHP
php生成图片验证码
Jun 09 PHP
php实现的农历算法实例
Aug 11 PHP
php获取汉字拼音首字母的方法
Oct 21 PHP
PHP+MySql+jQuery实现的"顶"和"踩"投票功能
May 21 PHP
php+ajax登录跳转登录实现思路
Jul 31 PHP
在PHP 7下安装Swoole与Yar,Yaf的方法教程
Jun 02 PHP
laravel excel 上传文件保存到本地服务器功能
Nov 14 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
swfupload 多文件上传实现代码
2008/08/27 PHP
PHP读取文件并可支持远程文件的代码分享
2012/10/03 PHP
PHP错误提示的关闭方法详解
2013/06/23 PHP
Codeigniter出现错误提示Error with CACHE directory的解决方案
2014/06/12 PHP
浅谈PHP表单提交(POST&GET&URL编/解码)
2017/04/03 PHP
thinkPHP5.0框架事务处理操作简单示例
2018/09/07 PHP
PHP中将一个字符串部分字符用星号*替代隐藏的实现代码
2019/09/08 PHP
体验js中splice()的强大(插入、删除或替换数组的元素)
2013/01/16 Javascript
javascript模拟实现C# String.format函数功能代码
2013/11/25 Javascript
使用js画图之正弦曲线
2015/01/12 Javascript
jquery validate.js表单验证入门实例(附源码)
2015/11/10 Javascript
基于jQuery实现仿51job城市选择功能实例代码
2016/03/02 Javascript
将html页面保存成图片,图片写入pdf的实现方法(推荐)
2016/09/17 Javascript
jquery实现焦点轮播效果
2017/02/23 Javascript
vue-自定义组件传值的实例讲解
2018/09/18 Javascript
详解Vue 匿名、具名和作用域插槽的使用方法
2019/04/22 Javascript
关于layui的下拉搜索框异步加载数据的解决方法
2019/09/28 Javascript
js实现html滑动图片拼图验证
2020/06/24 Javascript
Python网络编程 Python套接字编程
2017/09/13 Python
使用Turtle画正螺旋线的方法
2017/09/22 Python
Python cookbook(数据结构与算法)字典相关计算问题示例
2018/02/18 Python
基于Django与ajax之间的json传输方法
2018/05/29 Python
django框架使用orm实现批量更新数据的方法
2019/06/21 Python
使用python计算三角形的斜边例子
2020/04/15 Python
Pycharm学生免费专业版安装教程的方法步骤
2020/09/24 Python
天猫国际进口超市直营:官方直采,一站购齐
2017/12/11 全球购物
C#实现启动一个进程
2016/10/01 面试题
保荐人的岗位职责
2013/11/19 职场文书
小学清明节活动方案
2014/03/08 职场文书
幼儿园母亲节活动方案
2014/03/10 职场文书
分公司总经理岗位职责
2014/08/03 职场文书
2015年派出所工作总结
2015/04/24 职场文书
Nginx访问日志及错误日志参数说明
2021/03/31 Servers
PHP使用非对称加密算法RSA
2021/04/21 PHP
vue+spring boot实现校验码功能
2021/05/27 Vue.js
JavaScript中10个Reduce常用场景技巧
2022/06/21 Javascript