PHP 反射機制Reflection

簡介

PHP Reflection API是PHP5纔有的新功能,它是用來導出或提取出關於類、方法、屬性、參數等的詳細信息,包括註釋。php

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 { }

用得比較多的就只有兩個ReflectionClassReflectionObject,兩個的用法都同樣,只是前者針對類,後者針對對象,後者是繼承前者的類;而後其中又有一些屬性或方法能返回對應的Reflection對象(ReflectionProperty以及ReflectionMethod)數組

ReflectionClass

具體參考手冊:http://php.net/manual/zh/class.reflectionclass.phpapp

經過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)。yii

具體的參考:函數

<?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

得到一個 ReflectionProperty 類實例 (同上,得到該屬性的控制權) http://cn2.php.net/manual/zh/class.reflectionproperty.phpthis

getValue獲取屬性值
public mixed ReflectionProperty::getValue ([ object $object ] )

若是該得到該實例的類屬性不是一個static的屬性,就必須傳該類的實例spa

<?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;
}
相關文章
相關標籤/搜索