參考官方簡介的話,PHP 5 具備完整的反射 API,添加了對類、接口、函數、方法和擴展進行反向工程的能力。 此外,反射 API 提供了方法來取出函數、類和方法中的文檔註釋。php
對於yii2框架,應該都知道di容器,對於di容器的源碼這裏也主要講明Container類,先看看平時怎麼使用di,就用yii2框架中註釋的示例代碼來展現;數據庫
namespace app\models;
use yii\base\BaseObject;
use yii\db\Connection;
use yii\di\Container;
interface UserFinderInterface
{
function findUser();
}
class UserFinder extends BaseObject implements UserFinderInterface
{
public $db;
public function __construct(Connection $db, $config = [])
{
$this->db = $db;
parent::__construct($config);
}
public function findUser()
{
}
}
class UserLister extends BaseObject
{
public $finder;
public function __construct(UserFinderInterface $finder, $config = [])
{
$this->finder = $finder;
parent::__construct($config);
}
}
$container = new Container;
$container->set('yii\db\Connection', [
'dsn' => '...',
]);
$container->set('app\models\UserFinderInterface', [
'class' => 'app\models\UserFinder',
]);
$container->set('userLister', 'app\models\UserLister');
$lister = $container->get('userLister');
// 上述操做至關於下列實現
$db = new \yii\db\Connection(['dsn' => '...']);
$finder = new UserFinder($db);
$lister = new UserLister($finder);
複製代碼
上面的示例代碼只是實例化了Container類,而後調用set方法注入了其餘對象,最後獲取到了依賴與其餘對象建立的lister對象,既然只調用了set方法與get方法,那就先從調用最多的set開始看Container代碼。數組
public function set($class, $definition = [], array $params = [])
{
$this->_definitions[$class] = $this->normalizeDefinition($class, $definition);
$this->_params[$class] = $params;
unset($this->_singletons[$class]);
return $this;
}
複製代碼
上面的代碼比較簡潔,調用了類的normalizeDefinition方法,這個一會再說,先說明在該方法中出現的三個屬性的含義bash
再看normalizeDefinition方法,該方法主要做用是規範類定義yii2
protected function normalizeDefinition($class, $definition)
{
if (empty($definition)) {
// 爲空
return ['class' => $class];
} elseif (is_string($definition)) {
// 爲字符串
return ['class' => $definition];
} elseif (is_callable($definition, true) || is_object($definition)) {
// 檢驗是否爲可調用函數或者對象
return $definition;
} elseif (is_array($definition)) {
// 檢測是否爲數組
if (!isset($definition['class'])) {
if (strpos($class, '\\') !== false) {
$definition['class'] = $class;
} else {
throw new InvalidConfigException('A class definition requires a "class" member.');
}
}
return $definition;
}
throw new InvalidConfigException("Unsupported definition type for \"$class\": " . gettype($definition));
}
複製代碼
上述代碼中已作了一些判斷註釋,不難發現最後須要返回的definition變量須要爲數組格式,或者可調用函數與對象,注意回到剛開始的調用示例代碼,definition變量分別有數組格式不帶class鍵, 數組格式帶class鍵,與字符串類型。到底set方法調用已完畢,從源碼中分析基本上看不到反射的影子,也就是些傳入參數格式兼容處理再寫入類屬性,接着來看下示例代碼中的get方法吧。app
public function get($class, $params = [], $config = [])
{
if (isset($this->_singletons[$class])) {
// 直接返回單例
return $this->_singletons[$class];
} elseif (!isset($this->_definitions[$class])) {
// 調用bulid
return $this->build($class, $params, $config);
}
$definition = $this->_definitions[$class];
if (is_callable($definition, true)) {
// 可調用函數狀況
$params = $this->resolveDependencies($this->mergeParams($class, $params));
$object = call_user_func($definition, $this, $params, $config);
} elseif (is_array($definition)) {
// 數組
$concrete = $definition['class'];
unset($definition['class']);
$config = array_merge($definition, $config);
$params = $this->mergeParams($class, $params);
if ($concrete === $class) {
$object = $this->build($class, $params, $config);
} else {
$object = $this->get($concrete, $params, $config);
}
} elseif (is_object($definition)) {
// 對象直接保存到單例屬性集合中去
return $this->_singletons[$class] = $definition;
} else {
throw new InvalidConfigException('Unexpected object definition type: ' . gettype($definition));
}
if (array_key_exists($class, $this->_singletons)) {
// singleton
$this->_singletons[$class] = $object;
}
return $object;
}
複製代碼
上述代碼,簡要劃分一下,請稍做瀏覽,後面會繼續講述,先說明屬性_definitions集合中不存在的狀況,即調用build,這個一會說明,再看若是存在相關class鍵的狀況,下面會作幾種狀況的處理,框架
先看下build方法調用源碼yii
protected function build($class, $params, $config)
{
// 聲明變量分別存儲getDependencies方法返回的數組
list($reflection, $dependencies) = $this->getDependencies($class);
// 將params數組的數據mergy並覆蓋入變量$dependencies
foreach ($params as $index => $param) {
$dependencies[$index] = $param;
}
// 調用resolveDependencies方法
$dependencies = $this->resolveDependencies($dependencies, $reflection);
// 調用反射類方法,檢測類是否可實例化
if (!$reflection->isInstantiable()) {
throw new NotInstantiableException($reflection->name);
}
if (empty($config)) {
// 建立一個類的新實例,變量$dependencies做爲參數將傳遞到類的構造函數。
return $reflection->newInstanceArgs($dependencies);
}
$config = $this->resolveDependencies($config);
// 若是變量$dependencies爲空而且class是yii\base\Configurable接口的實現
if (!empty($dependencies) && $reflection->implementsInterface('yii\base\Configurable')) {
// set $config as the last parameter (existing one will be overwritten)
$dependencies[count($dependencies) - 1] = $config;
return $reflection->newInstanceArgs($dependencies);
}
// 建立對象,注入參數
$object = $reflection->newInstanceArgs($dependencies);
// 對象屬性賦值
foreach ($config as $name => $value) {
$object->$name = $value;
}
return $object;
}
複製代碼
看了上述源碼,也基本瞭解此方法是爲了返回實例化對象,並調用了反射的一些接口函數,這裏基本上能夠知道反射的一些做用,第一個就是檢測類的合法性,例如檢測是否爲接口實現,是否可實例化, 還有一個就是創造,上述能夠看出根據反射建立類的實例,並注入構造函數依賴的參數。下面再瞭解下該方法裏面調用的兩個依賴方法,分別爲開頭的變量聲明getDependencies與resolveDependencies 處理變量。函數
protected function getDependencies($class)
{
// 檢測是否已存在該反射
if (isset($this->_reflections[$class])) {
return [$this->_reflections[$class], $this->_dependencies[$class]];
}
$dependencies = [];
$reflection = new ReflectionClass($class); // 反射對應類的信息
$constructor = $reflection->getConstructor(); // 獲取類的構造函數
if ($constructor !== null) {
// 若是構造函數不爲空,獲取構造函數中的參數循環處理
foreach ($constructor->getParameters() as $param) {
if (version_compare(PHP_VERSION, '5.6.0', '>=') && $param->isVariadic()) {
// 檢測php版本與構造參數檢測是否爲可變參數
break;
} elseif ($param->isDefaultValueAvailable()) {
// 檢測參數是不是否有默認值,若是有數據保存默認值
$dependencies[] = $param->getDefaultValue();
} else {
// 獲取參數的類型提示符,查看是否爲null,返回的是reflectClass對象
// 這裏再舉個例子,例如構造函數爲這樣__construct(Db $db);這裏返回的就是Db類的反射
$c = $param->getClass();
// 建立Instance實例存儲類名
$dependencies[] = Instance::of($c === null ? null : $c->getName());
}
}
}
// 存儲起來
$this->_reflections[$class] = $reflection;
$this->_dependencies[$class] = $dependencies;
return [$reflection, $dependencies];
}
複製代碼
該方法主要做用爲解析依賴信息,主要是獲取類的構造函數的信息,這樣才能調用構造函數建立實例。ui
該方法主要是實例化依賴,也就是建立構造函數的參數對象,不做過多贅述
protected function resolveDependencies($dependencies, $reflection = null)
{
foreach ($dependencies as $index => $dependency) {
// 在解析依賴信息的getDependencies中,有部分參數沒有默認值,而是建立了Instance對象
// 這裏會將這些Instance對象實例化對真正的構造函數的參數對象
if ($dependency instanceof Instance) {
if ($dependency->id !== null) {
$dependencies[$index] = $this->get($dependency->id);
} elseif ($reflection !== null) {
$name = $reflection->getConstructor()->getParameters()[$index]->getName();
$class = $reflection->getName();
throw new InvalidConfigException("Missing required parameter \"$name\" when instantiating \"$class\".");
}
}
}
return $dependencies;
}
複製代碼
在上述源碼中基本上能夠看到幾處反射的應用,而反射究竟是什麼,由什麼做用呢?想必看完上文也會有一點點理解,嗯,其實意義如其名, 就是反射類的信息,其做用是獲取類的信息,而php的反射類也提供了不少的接口函數以供使用,使用的時候能夠去查詢官網手冊。 上文也看出來yii2框架中的di容器建立對象,在這裏仍是但願能夠稍微講述下剛開始的示例代碼,其先在容器中注入了數據庫鏈接類,finder類,listener類,而finder類構造函數依賴於 數據庫鏈接類,listener類依賴與finder類,由獲取依賴信息方法能夠知道構造中會去取出依賴對象信息而後調用解析依賴信息從新去調用get方法返回實例化對象實現其中的注入關係。