一起來看看 PHP 中的反射
阿新 • • 發佈:2019-01-06
什麼是反射?
動態獲取 類的方法、屬性、引數,註釋 等
資訊以及動態呼叫物件的方法的功能稱為反射API。
反射是操縱面向物件範型中元模型的API,其功能十分強大,可幫助我們構建複雜,可擴充套件的應用。比如 Laravel 的容器
反射類/函式的功能
可以獲取類的一切資訊,包括:
1. 類基本資訊(類名、是否是抽象類、是否可例項化、類是否為final或者abstract)
2. 類的方法、方法是否存在、方法返回值、方法的註釋、Method Names
3. 類的屬性,靜態屬性,常量
4. 所在名稱空間 Namespace
通過三個例子來理解反射
1.最簡單的反射
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 等函式來實現獲取類的屬性和方法
//物件
//var_dump(get_object_vars($boy));
//var_dump(get_class_vars(get_class($boy)));
//返回由類的屬性的方法名組成的陣列
//var_dump(get_class_methods(get_class($boy)));
# 列印結果如下:
Leon
您正在設定私有屬性hair
值為short
string(5) "name " string(4) "age " string(5) "hair "
string(6) "__set " string(6) "__get " string(6) "seeMe "
2.一個反射簡單的動態代理的例子,操作 sqlproxy ,通過動態傳參來代替實際的類(mysql)的執行
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)
3.基於反射實現的容器,依賴注入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) {
}
}
}
接下來會寫一篇部落格,記錄反射 在 laravel 中 di 、ioc 的 應用
。
本文用到的一些函式和魔術方法,可以參考 一些 PHP 類相關的函式。