浅析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 相关文章推荐
?繁体转换的class
Oct 09 PHP
PHP 源代码压缩小工具
Dec 22 PHP
PHP在线生成二维码代码(google api)
Jun 03 PHP
php中session使用示例
Mar 29 PHP
php实现基于微信公众平台开发SDK(demo)扩展的方法
Dec 22 PHP
php+mysql实现无限分类实例详解
Jan 15 PHP
PHP判断网络文件是否存在的方法
Mar 12 PHP
PHP简单获取视频预览图的方法
Mar 12 PHP
详谈php ip2long 出现负数的原因及解决方法
Apr 05 PHP
PHP实现上传图片到数据库并显示输出的方法
May 31 PHP
PHP mongodb操作类定义与用法示例【适合mongodb2.x和mongodb3.x】
Jun 16 PHP
TP5框架实现上传多张图片的方法分析
Mar 29 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同时支持GIF、png、JPEG
2006/10/09 PHP
PHP下通过exec获得计算机的唯一标识[CPU,网卡 MAC地址]
2011/06/09 PHP
解析在PHP中使用mysqli扩展库对mysql的操作
2013/07/03 PHP
PHP对象相互引用的内存溢出实例分析
2014/08/28 PHP
PHP使用in_array函数检查数组中是否存在某个值
2015/03/25 PHP
nginx+thinkphp下解决不支持pathinfo模式
2015/07/01 PHP
PHP迭代器和迭代的实现与使用方法分析
2018/04/19 PHP
PHP高并发和大流量解决方案整理
2019/12/24 PHP
php使用redis的几种常见操作方式和用法示例
2020/02/20 PHP
JavaScript 在线压缩和格式化收藏
2009/01/16 Javascript
jquery马赛克拼接翻转效果代码分享
2015/08/24 Javascript
JS禁止查看网页源代码的实现方法
2016/10/12 Javascript
Node.js websocket使用socket.io库实现实时聊天室
2017/02/20 Javascript
angularjs的select使用及默认选中设置
2017/04/08 Javascript
微信小程序视图template模板引用的实例详解
2017/09/20 Javascript
React 无状态组件(Stateless Component) 与高阶组件
2018/08/14 Javascript
微信小程序生成二维码的示例代码
2019/03/29 Javascript
JS块级作用域和私有变量实例分析
2019/05/11 Javascript
Python画图学习入门教程
2016/07/01 Python
python编程通过蒙特卡洛法计算定积分详解
2017/12/13 Python
Python面向对象之类和对象属性的增删改查操作示例
2018/12/14 Python
DJango的创建和使用详解(默认数据库sqlite3)
2019/11/18 Python
Pytorch技巧:DataLoader的collate_fn参数使用详解
2020/01/08 Python
python正则过滤字母、中文、数字及特殊字符方法详解
2020/02/11 Python
css3圆角样式分享自定义按钮样式
2013/12/27 HTML / CSS
在线学习西班牙语、法语或其他语言:Babbel.com
2018/02/07 全球购物
美国气象仪器、花园装饰和墙壁艺术商店:Wind & Weather
2019/05/29 全球购物
生产部经理岗位职责
2013/12/16 职场文书
物流仓储计划书
2014/01/10 职场文书
社区八一活动方案
2014/02/03 职场文书
《蒙娜丽莎之约》教学反思
2014/02/27 职场文书
2014两会学习心得:榜样精神伴我行
2014/03/17 职场文书
员工合理化建议书
2014/05/19 职场文书
神龙架导游词
2015/02/11 职场文书
导游词之广东佛山(南风古灶)
2019/09/24 职场文书
MYSQL 表的全面总结
2021/11/11 MySQL