什么是反射?
PHP5以及之后具有完整的反射API,动态获取类的方法、属性、参数,注释 等
信息以及动态调用对象的方法的功能称为反射API。
反射是操纵面向对象范型中元模型的API,其功能十分强大,可帮助我们构建复杂,可扩展的应用。比如 Laravel 的容器
功能?
可以获取类的一切信息,包括:
1. 类基本信息(类名、是否是抽象类、是否可实例化、类是否为final或者abstract)
2. 类的方法、方法是否存在、方法返回值、方法的注释、Method Names
3. 类的属性,静态属性,常量
4. 所在命名空间 Namespace
通过ReflectionClass,我们可以得到Person类的以下信息:
1)常量 Contants
2)属性 Property Names
3)方法 Method Names静态
4)属性 Static Properties
5)命名空间 Namespace
6)Person类是否为final或者abstract
简单例子:
<?php
class HandsonBoy
{
public $name = 'Leon';
public $age = 25;
public function __set($name, $value)
{
echo '您正在设置私有属性' . $name . '<br >值为' . $value . '<br>';
$this->$name = $value;
}
public function __get($name)
{
if (!isset($this->$name)) {
echo '未设置' . $name;
$this->$name = "正在为你设置默认值" . '<br>';
}
return $this->$name;
}
public function seeMe()
{
}
}
$boy = new HandsonBoy();
echo $boy->name . '<br />';
$boy->hair = 'short';
# 用反射来获取类的属性
$reflect = new ReflectionObject($boy);
$props = $reflect->getProperties();
//反射遍历属性
foreach ($props as $prop) {
var_dump($prop->getName() . PHP_EOL);
}
//获取对象方法列表
$methods = $reflect->getMethods();
foreach ($methods as $method) {
var_dump($method->getName() . PHP_EOL);
}
# 当然也可以直接用 get_object_vars 等函数来实现获取类的属性和方法
//对象
echo '<br>';
//var_dump(get_object_vars($boy));
//var_dump(get_class_vars(get_class($boy)));
//返回由类的属性的方法名组成的数组
//var_dump(get_class_methods(get_class($boy)));
<?php
class mysql
{
function connect($db)
{
echo "连接到数据库{$db[0]}".PHP_EOL;
}
}
class sqlproxy
{
private $target;
public function __construct($tar)
{
$this->target[] = new $tar;
}
public function __call($name,$args)
{
// var_dump('name:'.$name);connect
// var_dump($args);siwei
foreach($this->target as $obj)
{
$r = new ReflectionClass($obj);//获取 mysql_Class 的反射类
if($method = $r->getMethod($name))
{
if($method->isPublic() && !$method->isAbstract())
{
echo "方法前拦截记录LOG".PHP_EOL;
$method->invoke($obj,$args);//调用 obj = 实例化之后的mysql类对象的 args =siwei
echo "方法后拦截".PHP_EOL;
}
}
}
}
}
$obj = new sqlproxy('mysql');//实例化sqlproxy 类,初始化 $this->target 赋值 = new mysql()
$obj->connect('siwei'); //调用了魔术方法 __call name是方法名:connect || args 为 参数数组,siwei
// 遍历初始化传过来的 mysql 的反射类 ,然后获取 connect 的方法,存到method 变量中
// 判断 method 是否是抽象方法,如果不是,调用反射的method 的 invoke(new mysql(),siwei)
基于反射实现的容器,依赖注入DI
<?php
/**
*
* 工具类,使用该类来实现自动依赖注入。
*
* 使用php的反射函数,创建了一个容器类,使用该类来实现其他类的依赖注入功能。
*
* 依赖注入分为两种,
* 一种是构造函数的依赖注入,
* 一种是方法的依赖注入。
* 我们使用下面三个类来做下测试。
*/
class Ioc
{
// 获得类的对象实例
public static function getInstance($className)
{
$paramArr = self::getMethodParams($className);
return (new ReflectionClass($className))->newInstanceArgs($paramArr);
}
/**
* 执行类的方法
* @param [type] $className [类名]
* @param [type] $methodName [方法名称]
* @param [type] $params [额外的参数]
* @return [type] [description]
*/
public static function make($className, $methodName, $params = [])
{
// 获取类的实例
$instance = self::getInstance($className);
// 获取该方法所需要依赖注入的参数
$paramArr = self::getMethodParams($className, $methodName);
return $instance->{$methodName}(...array_merge($paramArr, $params)); #将 合并之后的数组作为函数参数
}
/**
* 获得类的方法参数,只获得有类型的参数
* @param [type] $className [description]
* @param [type] $methodsName [description]
* @return [type] [description]
*/
protected static function getMethodParams($className, $methodsName = '__construct')
{
// 通过反射获得该类
$class = new ReflectionClass($className);
$paramArr = []; // 记录参数,和参数类型
// 判断该类是否有构造函数
if ($class->hasMethod($methodsName)) {
// 获得构造函数
$construct = $class->getMethod($methodsName);
// 判断构造函数是否有参数
$params = $construct->getParameters();
if (count($params) > 0) {
// 判断参数类型
foreach ($params as $key => $param) {
if ($paramClass = $param->getClass()) {
// 获得参数类型名称
$paramClassName = $paramClass->getName();
// 获得参数类型
$args = self::getMethodParams($paramClassName);
$paramArr[] = (new ReflectionClass($paramClass->getName()))->newInstanceArgs($args);
}
}
}
}
return $paramArr;
}
}
class A
{
protected $cObj;
/**
* 用于测试多级依赖注入 B依赖A,A依赖C
* @param C $c [description]
*/
public function __construct(C $c)
{
$this->cObj = $c;
}
public function aa()
{
echo 'this is A->test';
}
public function aac()
{
$this->cObj->cc();
}
}
class B
{
protected $aObj;
/**
* 测试构造函数依赖注入
* @param A $a [使用引来注入A]
*/
public function __construct(A $a)
{
$this->aObj = $a;
}
/**
* [测试方法调用依赖注入]
* @param C $c [依赖注入C]
* @param string $b [这个是自己手动填写的参数]
* @return [type] [description]
*/
public function bb(C $c, $b)
{
$c->cc();
echo "\r\n";
echo 'params:' . $b;
}
/**
* 验证依赖注入是否成功
* @return [type] [description]
*/
public function bbb()
{
$this->aObj->aac();
}
}
class C
{
public function cc()
{
echo 'this is C->cc';
}
}
// 使用Ioc反射 来创建B类的实例,B的构造函数依赖A类,A的构造函数依赖C类。最终调用 C的 cc方法 this is C->cc , 说明依赖注入成功。
$bObj = Ioc::getInstance('B');
$bObj->bbb();
var_dump($bObj);
# 打印结果
this is C->cc
object(B)#3 (1) {
["aObj":protected]=>
object(A)#7 (1) {
["cObj":protected]=>
object(C)#10 (0) {
}
}
}