這篇文章主要介紹了關於如何在yii2架構的di容器源碼中瞭解反射的作用,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下
反射簡介
參考官方簡介的話,PHP 5 具有完整的反射 API,添加了對類、介面、函數、方法和擴充進行反向工程的能力。 此外,反射 API 提供了方法來取出函數、類和方法中的文檔注釋。
YII2架構中樣本
對於yii2架構,應該都知道di容器,對於di容器的源碼這裡也主要講明Container類,先看看平時怎麼使用di,就用yii2架構中注釋的範例程式碼來展示;
container調用樣本
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代碼。
set方法
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方法,這個一會再說,先說明在該方法中出現的三個屬性的含義
_definitions數組,儲存依賴定義
_params數組,儲存建構函式的參數
_singletons,儲存單例
再看normalizeDefinition方法,該方法主要作用是規範類定義
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方法吧。
get 方法
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鍵的情況,下面會做幾種情況的處理,
可調用函數情況下,調用resolveDependencies方法,再call_user_func調用函數
數組情況下,擷取值與class比較,相等的情況去調用build方法,不想等重新調用get方法使用該值
為對象的化直接儲存到_singletons屬性集合中去,並直接返回對象,這個不作贅述
下面分別來簡要分析一下上述調用的幾個方法,bulid與resolveDependencies方法
bulid方法的調用邏輯
先看下build方法調用源碼
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
處理變數。
getDependencies方法調用
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];}
該方法主要作用為解析依賴資訊,主要是擷取類的建構函式的資訊,這樣才能調用建構函式建立執行個體。
resilveDependencies方法調用
該方法主要是執行個體化依賴,也就是建立建構函式的參數對象,不作過多贅述
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方法返回執行個體化對象實現其中的注入關係。
以上就是本文的全部內容,希望對大家的學習有所協助,更多相關內容請關注topic.alibabacloud.com!