浅析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 相关文章推荐
关于mysql 字段的那个点为是定界符
Jan 15 PHP
PHP实现多服务器session共享之NFS共享的方法
Mar 16 PHP
php+mysql开源XNA 聚合程序发布 下载
Jul 13 PHP
php Hex RGB颜色值互换的使用
May 10 PHP
php调用nginx的mod_zip模块打包ZIP文件
Jun 11 PHP
Codeigniter整合Tank Auth权限类库详解
Jun 12 PHP
10个超级有用的PHP代码片段果断收藏
Sep 23 PHP
PHP GD库相关图像生成和处理函数小结
Sep 30 PHP
利用php的ob缓存机制实现页面静态化方法
Jul 09 PHP
Laravel 使用查询构造器配合原生sql语句查询的例子
Oct 12 PHP
laravel 解决groupBy时出现的错误 isn't in Group By问题
Oct 17 PHP
PHP实用小技巧之调用录像的方法
Dec 05 PHP
php打开本地exe程序,js打开本地exe应用程序,并传递相关参数方法
Feb 06 #PHP
PHP给源代码加密的几种方法汇总(推荐)
Feb 06 #PHP
php 替换文章中的图片路径,下载图片到本地服务器的方法
Feb 06 #PHP
PHP定义字符串的四种方式详解
Feb 06 #PHP
浅谈PHP中pack、unpack的详细用法
Mar 12 #PHP
阿里云Win2016安装Apache和PHP环境图文教程
Mar 11 #PHP
Yii2 中实现单点登录的方法
Mar 09 #PHP
You might like
PHP Ajax实现页面无刷新发表评论
2007/01/02 PHP
WordPress判断用户是否登录的代码
2011/03/17 PHP
微信随机生成红包金额算法php版
2016/07/21 PHP
一个用javascript写的select支持上下键、首字母筛选以及回车取值的功能
2009/09/09 Javascript
JS控制显示隐藏兼容问题(IE6、IE7、IE8)
2010/04/01 Javascript
flexigrid 参数说明
2010/11/23 Javascript
jquery常用技巧及常用方法列表集合
2011/04/06 Javascript
分享20款好玩的jQuery游戏
2011/04/17 Javascript
jquery获取html元素的绝对位置和相对位置的方法
2014/06/20 Javascript
jQuery实现高亮显示网页关键词的方法
2015/08/07 Javascript
详解jquery事件delegate()的使用方法
2016/01/25 Javascript
Node.js实用代码段之正确拼接Buffer
2016/03/17 Javascript
jQuery javascript获得网页的高度与宽度的实现代码
2016/04/26 Javascript
BootStrap学习系列之布局组件(下拉,按钮组[toolbar],上拉)
2017/01/03 Javascript
JS中的多态实例详解
2017/10/15 Javascript
微信小程序websocket聊天室的实现示例代码
2019/02/12 Javascript
Element中的Cascader(级联列表)动态加载省\市\区数据的方法
2019/03/27 Javascript
vue 实现搜索的结果页面支持全选与取消全选功能
2019/05/10 Javascript
Node.js系列之连接DB的方法(3)
2019/08/30 Javascript
Python中enumerate函数代码解析
2017/10/31 Python
Python日期时间对象转换为字符串的实例
2018/06/22 Python
用python求一个数组的和与平均值的实现方法
2019/06/29 Python
Python Django 实现简单注册功能过程详解
2019/07/29 Python
python 函数的缺省参数使用注意事项分析
2019/09/17 Python
pytorch torch.expand和torch.repeat的区别详解
2019/11/05 Python
python通过链接抓取网站详解
2019/11/20 Python
Python requests.post方法中data与json参数区别详解
2020/04/30 Python
纯CSS3单页切换导航菜单界面设计的简单实现
2016/08/16 HTML / CSS
高中军训第一天感言
2014/03/06 职场文书
预防艾滋病宣传标语
2014/06/25 职场文书
工商局领导班子存在的问题整改措施思想汇报
2014/10/05 职场文书
结婚保证书(卖身契)
2015/02/26 职场文书
通知函的格式
2015/04/27 职场文书
辩护词范文大全
2015/05/21 职场文书
一篇文章了解正则表达式的替换技巧
2022/02/24 Javascript
Python万能模板案例之matplotlib绘制直方图的基本配置
2022/04/13 Python