浅析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 相关文章推荐
IIS php环境配置PHP5 MySQL5 ZendOptimizer phpmyadmin安装与配置
Nov 18 PHP
php安全配置 如何配置使其更安全
Dec 16 PHP
php数组声明、遍历、数组全局变量使用小结
Jun 05 PHP
合并ThinkPHP配置文件以消除代码冗余的实现方法
Jul 22 PHP
PHP获取服务器端信息的方法
Nov 28 PHP
php插入排序法实现数组排序实例
Feb 16 PHP
php动态函数调用方法
May 21 PHP
Yii2中DropDownList简单用法示例
Jul 18 PHP
PHP APP微信提现接口代码
Sep 30 PHP
PHP实现数组和对象的相互转换操作示例
Mar 20 PHP
PHP项目多语言配置平台实现过程解析
May 18 PHP
PHP7 字符串处理机制修改
Mar 09 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实现的英文名字全拼随机排号脚本
2014/07/04 PHP
php中动态修改ini配置
2014/10/14 PHP
PHP操作MongoDB实现增删改查功能【附php7操作MongoDB方法】
2018/04/24 PHP
基于JQuery的密码强度验证代码
2010/03/01 Javascript
javascript实现日历控件(年月日关闭按钮)
2012/12/12 Javascript
Jquery带搜索框的下拉菜单
2013/05/06 Javascript
如何使用jQuery技术开发ios风格的页面导航菜单
2015/07/29 Javascript
JavaScript的removeChild()函数用法详解
2015/12/27 Javascript
原生JS实现拖拽图片效果
2020/08/27 Javascript
基于jQuery实现左侧菜单栏可折叠功能
2016/12/27 Javascript
JS排序之冒泡排序详解
2017/04/08 Javascript
js使用i18n实现页面国际化的方法
2017/05/09 Javascript
jQuery实现获取table中鼠标click点击位置行号与列号的方法
2017/10/09 jQuery
vue学习笔记五:在vue项目里面使用引入公共方法详解
2019/04/04 Javascript
从0到1搭建element后台框架优化篇(打包优化)
2019/05/12 Javascript
JQuery常用简单动画操作方法回顾与总结
2019/12/07 jQuery
vue分页插件的使用方法
2019/12/25 Javascript
jQuery实现获取多选框的值示例
2020/02/07 jQuery
JavaScript中的各种宽高属性的实现
2020/05/08 Javascript
Python学习笔记(一)(基础入门之环境搭建)
2014/06/05 Python
Python 字典dict使用介绍
2014/11/30 Python
用Python制作简单的钢琴程序的教程
2015/04/01 Python
连接Python程序与MySQL的教程
2015/04/29 Python
使用Python对Excel进行读写操作
2017/03/30 Python
使用python获取(宜宾市地震信息)地震信息
2019/06/20 Python
解决Keras中Embedding层masking与Concatenate层不可调和的问题
2020/06/18 Python
HTML5和CSS3让网页设计提升到下一个高度
2009/08/14 HTML / CSS
Mytheresa英国官网:拥有160多个奢侈品品牌
2016/10/09 全球购物
Book Depository欧盟:一家领先的国际图书零售商
2019/05/21 全球购物
会计学专业学生的求职信范文
2014/01/27 职场文书
大学生档案自我鉴定(2篇)
2014/10/14 职场文书
企业财务经理岗位职责
2015/04/08 职场文书
大学运动会加油稿
2015/07/22 职场文书
高一军训口号
2015/12/25 职场文书
2019年暑期法院实习报告
2019/12/18 职场文书
Nginx 根据URL带的参数转发的实现
2021/04/01 Servers