yii2源碼分析之組件實例化流程
阿新 • • 發佈:2018-04-23
yii2讀本篇文章,建議先看看我之前的文章php依賴註入
到此,現在我們正式開始分析yii2框架組件構造流程
我們先從yii\di\ServiceLocator(服務定位器)入手吧!!讓我們先看個實例:
use yii\di\ServiceLocator; use yii\caching\FileCache; $locator = new ServiceLocator; // 通過一個可用於創建該組件的類名,註冊 "cache" (緩存)組件。 $locator->set('cache', 'yii\caching\ApcCache'); // 通過一個可用於創建該組件的配置數組,註冊 "db" (數據庫)組件。 $locator->set('db', [ 'class' => 'yii\db\Connection', 'dsn' => 'mysql:host=localhost;dbname=demo', 'username' => 'root', 'password' => '', ]);
我們直接打開ServiceLocator的set方法:
//這個函數很簡單,就是定義一個id對應$definition,並保存到$this->_definitions數組中 public function set($id, $definition) { if ($definition === null) { unset($this->_components[$id], $this->_definitions[$id]); return; } unset($this->_components[$id]); if (is_object($definition) || is_callable($definition, true)) { // an object, a class name, or a PHP callable $this->_definitions[$id] = $definition; } elseif (is_array($definition)) { // a configuration array if (isset($definition['class'])) { $this->_definitions[$id] = $definition; } else { throw new InvalidConfigException("The configuration for the \"$id\" component must contain a \"class\" element."); } } else { throw new InvalidConfigException("Unexpected configuration type for the \"$id\" component: " . gettype($definition)); } }
現在我們有set方法,如果我們需要指定類名的對象呢,這時候需要看看ServiceLocator的get方法:
public function get($id, $throwException = true) { if (isset($this->_components[$id])) { return $this->_components[$id]; } if (isset($this->_definitions[$id])) { $definition = $this->_definitions[$id]; //如果是一個對象,則直接返回 if (is_object($definition) && !$definition instanceof Closure) { return $this->_components[$id] = $definition; } else { //Yii::createObject創建對象實例(重點方法) return $this->_components[$id] = Yii::createObject($definition); } } elseif ($throwException) { throw new InvalidConfigException("Unknown component ID: $id"); } else { return null; } }
跟蹤Yii::createObject方法
public static function createObject($type, array $params = []) { if (is_string($type)) { //static::$container就是yii\di\Container對象(依賴註入容器) return static::$container->get($type, $params); } elseif (is_array($type) && isset($type['class'])) { $class = $type['class']; unset($type['class']); return static::$container->get($class, $params, $type); } elseif (is_callable($type, true)) { return call_user_func($type, $params); } elseif (is_array($type)) { throw new InvalidConfigException('Object configuration must be an array containing a "class" element.'); } else { throw new InvalidConfigException('Unsupported configuration type: ' . gettype($type)); } }
我們來看看yii2的依賴註入容器怎麽實現的,打開yii\di\Container的get方法:
public function get($class, $params = [], $config = []) { //$this->_singletons保存單例對象 if (isset($this->_singletons[$class])) { // singleton return $this->_singletons[$class]; } elseif (!isset($this->_definitions[$class])) { //通過php反射機制實現$class的實例化 return $this->build($class, $params, $config); } //代表已經使用過set方法定義過$class $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)) { // 單例對象 $this->_singletons[$class] = $object; } return $object; }
我們打開這個$this->build方法:
protected function build($class, $params, $config) { /* $reflection為反射類 $denpendencies為$class類構造函數參數,如果構造函數參數裏面有依賴別的類,舉例: [new Instance(id屬性=該類名),new Instance(id屬性=該類名)] */ list ($reflection, $dependencies) = $this->getDependencies($class); //將$params的key和值加到$class構造函數參數數組列表中 foreach ($params as $index => $param) { $dependencies[$index] = $param; } //將構造函數參數中有yii\di\Instance的id屬性不為空,則將繼續遞歸調用$this->get將所依賴的類實例化 $dependencies = $this->resolveDependencies($dependencies, $reflection); if (empty($config)) { return $reflection->newInstanceArgs($dependencies); } 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); } else { $object = $reflection->newInstanceArgs($dependencies); //將$config數組賦值到實例化對象的屬性上 foreach ($config as $name => $value) { $object->$name = $value; } return $object; } }
繼續追$this->getDependencies方法:
//得到[$reflection, $dependencies]; 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 ($param->isDefaultValueAvailable()) { $dependencies[] = $param->getDefaultValue(); } else { $c = $param->getClass(); $dependencies[] = Instance::of($c === null ? null : $c->getName()); } } } //緩存$reflection反射類,以及$class類構造函數參數數組 $this->_reflections[$class] = $reflection; $this->_dependencies[$class] = $dependencies; return [$reflection, $dependencies]; }
我們打開$this->resolveDependencies方法:
protected function resolveDependencies($dependencies, $reflection = null) { foreach ($dependencies as $index => $dependency) { 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; }
最後來個yii\di\Instance類的具體內容(跟我之前那個類代碼基本一樣,多了個ensure方法):
namespace yii\di; use Yii; use yii\base\InvalidConfigException; class Instance { public $id; protected function __construct($id) { $this->id = $id; } public static function of($id) { return new static($id); } /** * * ```php * use yii\db\Connection; * * // returns Yii::$app->db * $db = Instance::ensure('db', Connection::className()); * // returns an instance of Connection using the given configuration * $db = Instance::ensure(['dsn' => 'sqlite:path/to/my.db'], Connection::className()); * ``` */ public static function ensure($reference, $type = null, $container = null) { if (is_array($reference)) { $class = isset($reference['class']) ? $reference['class'] : $type; if (!$container instanceof Container) { $container = Yii::$container; } unset($reference['class']); return $container->get($class, [], $reference); } elseif (empty($reference)) { throw new InvalidConfigException('The required component is not specified.'); } if (is_string($reference)) { $reference = new static($reference); } elseif ($type === null || $reference instanceof $type) { return $reference; } if ($reference instanceof self) { $component = $reference->get($container); if ($type === null || $component instanceof $type) { return $component; } else { throw new InvalidConfigException('"' . $reference->id . '" refers to a ' . get_class($component) . " component. $type is expected."); } } $valueType = is_object($reference) ? get_class($reference) : gettype($reference); throw new InvalidConfigException("Invalid data type: $valueType. $type is expected."); } public function get($container = null) { if ($container) { return $container->get($this->id); } if (Yii::$app && Yii::$app->has($this->id)) { return Yii::$app->get($this->id); } else { return Yii::$container->get($this->id); } } }
yii2源碼分析之組件實例化流程