PHP 反射機制Reflection
阿新 • • 發佈:2018-12-15
簡介
PHP Reflection API是PHP5才有的新功能,它是用來匯出或提取出關於類、方法、屬性、引數等的詳細資訊,包括註釋。
class Reflection { } interface Reflector { } class ReflectionException extends Exception { } class ReflectionFunction implements Reflector { } class ReflectionParameter implements Reflector { } class ReflectionMethod extends ReflectionFunction { } class ReflectionClass implements Reflector { } class ReflectionObject extends ReflectionClass { } class ReflectionProperty implements Reflector { } class ReflectionExtension implements Reflector { }
用得比較多的就只有兩個ReflectionClass
與ReflectionObject
,兩個的用法都一樣,只是前者針對類,後者針對物件,後者是繼承前者的類;然後其中又有一些屬性或方法能返回對應的Reflection
物件(ReflectionProperty以及ReflectionMethod)
ReflectionClass
通過ReflectionClass,我們可以得到Person類的以下資訊:
- 常量 Contants
- 屬性 Property Names
- 方法 Method Names
- 靜態屬性 Static Properties
- 名稱空間 Namespace
- Person類是否為final或者abstract
<?php namespace app; class Person{ /** * For the sake of demonstration, we"re setting this private */ private $_allowDynamicAttributes = false; /** type=primary_autoincrement */ protected $id = 0; /** type=varchar length=255 null */ protected $name; /** type=text null */ protected $biography; public function getId(){ return $this->id; } public function setId($v){ $this->id = $v; } public function getName(){ return $this->name; } public function setName($v){ $this->name = $v; } public function getBiography(){ return $this->biography; } public function setBiography($v){ $this->biography = $v; } } //傳遞類名或物件進來 $class = new \ReflectionClass('app\Person'); //獲取屬性,不管該屬性是否public $properties = $class->getProperties(); foreach($properties as $property) { echo $property->getName()."\n"; } // 輸出: // _allowDynamicAttributes // id // name // biography //預設情況下,ReflectionClass會獲取到所有的屬性,private 和 protected的也可以。如果只想獲取到private屬性,就要額外傳個引數: /* * ReflectionProperty::IS_STATIC * ReflectionProperty::IS_PUBLIC * ReflectionProperty::IS_PROTECTED * ReflectionProperty::IS_PRIVATE */ //↓↓ 注意一個|組合: 獲得IS_PRIVATE或者IS_PROTECTED的屬性 $private_properties = $class->getProperties(\ReflectionProperty::IS_PRIVATE|\ReflectionProperty::IS_PROTECTED); foreach($private_properties as $property) { //↓↓如果該屬性是受保護的屬性; if($property->isProtected()) { // ↓↓ 獲取註釋 $docblock = $property->getDocComment(); preg_match('/ type\=([a-z_]*) /', $property->getDocComment(), $matches); echo $matches[1]."\n"; } } // Output: // primary_autoincrement // varchar // text $data = array("id" => 1, "name" => "Chris", "biography" => "I am am a PHP developer"); foreach($data as $key => $value) { if(!$class->hasProperty($key)) { throw new \Exception($key." is not a valid property"); } if(!$class->hasMethod("get".ucfirst($key))) { throw new \Exception($key." is missing a getter"); } if(!$class->hasMethod("set".ucfirst($key))) { throw new \Exception($key." is missing a setter"); } $object = new Person(); // http://php.net/manual/zh/class.reflectionmethod.php // getMethod 獲得一個該方法的reflectionmethod物件,然後使用裡面的invoke方法; $setter = $class->getMethod("set".ucfirst($key)); $ok = $setter->invoke($object, $value); // Get the setter method and invoke it $getter = $class->getMethod("get".ucfirst($key)); $objValue = $getter->invoke($object); // Now compare if($value == $objValue) { echo "Getter or Setter has modified the data.\n"; } else { echo "Getter and Setter does not modify the data.\n"; } }
getMethod and invoke
ReflectionClass::getMethod
— 獲取一個類方法的 ReflectionMethod(可以理解為獲得這個類方法的控制權,不管這個類方法是否是public)。
具體的參考:
<?php
class HelloWorld {
private function sayHelloTo($name,$arg1,$arg2) {
return 'Hello ' . $name.' '.$arg1.' '.$arg2;
}
}
$obj = new HelloWorld();
// 第一個引數可以是物件,也可以是類
$reflectionMethod = new ReflectionMethod($obj , 'sayHelloTo');
if(!$reflectionMethod -> isPublic()){
$reflectionMethod -> setAccessible(true);
}
/*
* public mixed ReflectionMethod::invoke ( object $object [, mixed $parameter [, mixed $... ]] )
* 1. 獲得某個類方法的ReflectionMethod
* 2. $object 該方法所在的類例項的物件,然後第二引數起對號入座到該方法的每個引數;
* 3. 通過invoke就可以執行這個方法了
*/
echo $reflectionMethod->invoke($obj, 'GangGe','How','are you');
//也可以把引數作為陣列傳進來
echo $reflectionMethod -> invokeArgs($obj,array('GangGe','How','are you'));
getProperty
getValue獲取屬性值
public mixed ReflectionProperty::getValue ([ object $object ] )
如果該獲得該例項的類屬性不是一個static的屬性,就必須傳該類的例項
<?php
class Foo {
public static $staticProperty = 'foobar';
public $property = 'barfoo';
protected $privateProperty = 'foofoo';
}
$reflectionClass = new ReflectionClass('Foo');
var_dump($reflectionClass->getProperty('staticProperty')->getValue()); //靜態屬性可以不加引數
var_dump($reflectionClass->getProperty('property')->getValue(new Foo)); //非靜態屬性必須加傳一個類例項
$reflectionProperty = $reflectionClass->getProperty('privateProperty'); //受保護的屬性就要通過setAccessible獲得其許可權
$reflectionProperty->setAccessible(true);
var_dump($reflectionProperty->getValue(new Foo));
Example
模擬YII框架中控制器呼叫方法的實現
<?php
if (PHP_SAPI != 'cli') {
exit('Please run it in terminal!');
}
if ($argc < 3) {
exit('At least 2 arguments needed!');
}
$controller = ucfirst($argv[1]) . 'Controller';
$action = 'action' . ucfirst($argv[2]);
// 檢查類是否存在
if (!class_exists($controller)) {
exit("Class $controller does not existed!");
}
// 獲取類的反射
$reflector = new ReflectionClass($controller);
// 檢查方法是否存在
if (!$reflector->hasMethod($action)) {
exit("Method $action does not existed!");
}
// 取類的建構函式,返回的是ReflectionMethod物件
$constructor = $reflector->getConstructor();
// 取建構函式的引數,這是一個物件陣列
$parameters = $constructor->getParameters();
// 遍歷引數
foreach ($parameters as $key => $parameter) {
// 獲取引數宣告的類
$injector = new ReflectionClass($parameter->getClass()->name);
// 例項化引數宣告類並填入引數列表
$parameters[$key] = $injector->newInstance(); //例項化$parameter->getClass()->name類
}
// 使用引數列表例項 controller 類
$instance = $reflector->newInstanceArgs($parameters);
// 執行
$instance->$action();
class HelloController
{
private $model;
public function __construct(TestModel $model)
{
$this->model = $model;
}
public function actionWorld()
{
echo $this->model->property, PHP_EOL;
}
}
class TestModel
{
public $property = 'property';
}
TP框架中實現前後控制器
<?php
class BlogAction {
public function detail() {
echo 'detail' . "\r\n";
}
public function test($year = 2014, $month = 4, $day = 21) {
echo $year . '--' . $month . '--' . $day . "\r\n";
}
public function _before_detail() {
echo __FUNCTION__ . "\r\n";
}
public function _after_detail() {
echo __FUNCTION__ . "\r\n";
}
}
// 執行detail方法
$method = new ReflectionMethod('BlogAction', 'detail');
$instance = new BlogAction();
// 進行許可權判斷
if ($method->isPublic()) {
$class = new ReflectionClass('BlogAction');
// 執行前置方法
if ($class->hasMethod('_before_detail')) {
$beforeMethod = $class->getMethod('_before_detail');
if ($beforeMethod->isPublic()) {
$beforeMethod->invoke($instance);
}
}
$method->invoke(new BlogAction);
// 執行後置方法
if ($class->hasMethod('_after_detail')) {
$beforeMethod = $class->getMethod('_after_detail');
if ($beforeMethod->isPublic()) {
$beforeMethod->invoke($instance);
}
}
}
// 執行帶引數的方法
$method = new ReflectionMethod('BlogAction', 'test');
$params = $method->getParameters();
foreach ($params as $param) {
$paramName = $param->getName();
if (isset($_REQUEST[$paramName])) {
$args[] = $_REQUEST[$paramName];
} elseif ($param->isDefaultValueAvailable()) {
$args[] = $param->getDefaultValue();
}
}
if (count($args) == $method->getNumberOfParameters()) {
$method->invokeArgs($instance, $args);
} else {
echo 'parameters is wrong!';
}
其他參考
/**
* 執行App控制器
*/
public function execApp() {
// 建立action控制器例項
$className = MODULE_NAME . 'Controller';
$namespaceClassName = '\\apps\\' . APP_NAME . '\\controller\\' . $className;
load_class($namespaceClassName, false);
if (!class_exists($namespaceClassName)) {
throw new \Exception('Oops! Module not found : ' . $namespaceClassName);
}
$controller = new $namespaceClassName();
// 獲取當前操作名
$action = ACTION_NAME;
// 執行當前操作
//call_user_func(array(&$controller, $action)); // 其實吧,用這個函式足夠啦!!!
try {
$methodInfo = new \ReflectionMethod($namespaceClassName, $action);
if ($methodInfo->isPublic() && !$methodInfo->isStatic()) {
$methodInfo->invoke($controller);
} else { // 操作方法不是public型別,丟擲異常
throw new \ReflectionException();
}
} catch (\ReflectionException $e) {
// 方法呼叫發生異常後,引導到__call方法處理
$methodInfo = new \ReflectionMethod($namespaceClassName, '__call');
$methodInfo->invokeArgs($controller, array($action, ''));
}
return;
}
-------------------------END------------------------- 絕大多數博文都是從自己筆記裡面整理出來的,儘可能多發專題類的系列博文,也儘可能做到排版整潔,便於查閱。秉持著網際網路的開放分享,平等協作的精神,本人會一直不斷的更新下去。另外,感謝您指出博文的不足之處,或者對其中有疑問之處可以在評論中留言,也可以加個QQ進一步交流:822010893(請註明部落格園)