浅析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 error_log()将错误信息写入一个文件(定义和用法)
Oct 25 PHP
PHP中使用strpos函数实现屏蔽敏感关键字功能
Aug 21 PHP
php使用post数组的键值创建同名变量并赋值的方法
Apr 03 PHP
CI框架AR操作(数组形式)实现插入多条sql数据的方法
May 18 PHP
php操作xml并将其插入数据库的实现方法
Sep 08 PHP
CI框架表单验证实例详解
Nov 21 PHP
PHPCMS手机站伪静态设置详细教程
Feb 06 PHP
Laravel框架中自定义模板指令总结
Dec 17 PHP
PHP实现的mongoDB数据库操作类完整实例
Apr 10 PHP
PHP PDOStatement::getColumnMeta讲解
Feb 01 PHP
浅谈PHP中的那些魔术常量
Dec 02 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
phpMyAdmin2.11.6安装配置方法
2008/08/24 PHP
PHP 获取MSN好友列表的代码(2009-05-14测试通过)
2009/09/09 PHP
php 生成静态页面的办法与实现代码详细版
2010/02/15 PHP
探讨PHP函数ip2long转换IP时数值太大产生负数的解决方法
2013/06/06 PHP
Yii框架函数简单用法分析
2019/09/09 PHP
利用google提供的API(JavaScript接口)获取网站访问者IP地理位置的代码详解
2010/07/24 Javascript
如何让页面加载完成后执行js
2013/06/26 Javascript
原生javascript兼容性测试实例
2013/07/01 Javascript
JavaScript中instanceof运算符的用法总结
2013/11/19 Javascript
jQuery中extend函数详解
2015/02/13 Javascript
jquery ui resize 中border-box的bug修正
2015/04/26 Javascript
快速掌握WordPress中加载JavaScript脚本的方法
2015/12/17 Javascript
全面了解JavaScript的数据类型转换
2016/07/01 Javascript
AngularJS 依赖注入详解和简单实例
2016/07/28 Javascript
JS仿hao123导航页面图片轮播效果
2016/09/01 Javascript
JavaScript和jQuery制作光棒效果
2017/02/24 Javascript
Nodejs进阶:express+session实现简易登录身份认证
2017/04/24 NodeJs
ionic2自定义cordova插件开发以及使用(Android)
2017/06/19 Javascript
React教程之Props验证的具体用法(Props Validation)
2017/09/04 Javascript
p5.js入门教程之鼠标交互的示例
2018/03/16 Javascript
JS实现图片上传多次上传同一张不生效的处理方法
2018/08/06 Javascript
VUE 实现滚动监听 导航栏置顶的方法
2018/09/11 Javascript
Vue3新特性之在Composition API中使用CSS Modules
2020/07/13 Javascript
Python单元测试实例详解
2018/05/25 Python
Python OpenCV对本地视频文件进行分帧保存的实例
2019/01/08 Python
Python实现统计英文文章词频的方法分析
2019/01/28 Python
如何在vscode中安装python库的方法步骤
2021/01/06 Python
解决selenium+Headless Chrome实现不弹出浏览器自动化登录的问题
2021/01/09 Python
python利用后缀表达式实现计算器功能
2021/02/22 Python
幼儿园儿童节活动主持词+串词大全
2014/03/21 职场文书
毕业典礼主持词大全
2014/03/26 职场文书
社区关爱留守儿童活动方案
2014/08/22 职场文书
学校领导班子对照检查材料
2014/08/28 职场文书
2015年幼儿园保育工作总结
2015/05/12 职场文书
2015小学师德工作总结
2015/07/21 职场文书
Go gorilla/sessions库安装使用
2022/08/14 Golang