读本篇文章,建议先看看我之前的文章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); } } }
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。