Yii源码阅读笔记(三十三)

时间:2022-05-27 15:32:41

ServiceLocator,服务定位类,用于yii2中的依赖注入,通过以ID为索引的方式缓存服务或则组件的实例来定位服务或者组件:

 namespace yii\di;

 use Yii;
 use Closure;
 use yii\base\Component;
 use yii\base\InvalidConfigException;

 /**
  * ServiceLocator implements a [service locator](http://en.wikipedia.org/wiki/Service_locator_pattern).
  *
  * To use ServiceLocator, you first need to register component IDs with the corresponding component
  * definitions with the locator by calling [[set()]] or [[setComponents()]].
  * You can then call [[get()]] to retrieve a component with the specified ID. The locator will automatically
  * instantiate and configure the component according to the definition.
  *
  * For example,
  *
  * ```php
  * $locator = new \yii\di\ServiceLocator;
  * $locator->setComponents([
  *     'db' => [
  *         'class' => 'yii\db\Connection',
  *         'dsn' => 'sqlite:path/to/file.db',
  *     ],
  *     'cache' => [
  *         'class' => 'yii\caching\DbCache',
  *         'db' => 'db',
  *     ],
  * ]);
  *
  * $db = $locator->get('db');  // or $locator->db
  * $cache = $locator->get('cache');  // or $locator->cache
  * ```
  *
  * Because [[\yii\base\Module]] extends from ServiceLocator, modules and the application are all service locators.
  *
  * @property array $components The list of the component definitions or the loaded component instances (ID =>
  * definition or instance).
  *
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @since 2.0
  */
 class ServiceLocator extends Component
 {
     /**
      * @var array shared component instances indexed by their IDs
      * @var array 用于缓存服务、组件等的实例,索引为ID
      */
     private $_components = [];
     /**
      * @var array component definitions indexed by their IDs
      * @var array 用于保存服务和组件的定义,通常为配置数组,可以用来创建具体的实例
      */
     private $_definitions = [];

     /**
      * Getter magic method.
      * 重写了 getter 方法,使得访问服务和组件就跟访问类的属性一样。同时,也保留了原来Component的 getter所具有的功能。
      * This method is overridden to support accessing components like reading properties.
      * @param string $name component or property name
      * @return mixed the named property value
      */
     public function __get($name)
     {
         if ($this->has($name)) {//调用has方法判断是否有某个组件或服务的实例,如果有调用get方法返回该实例
             return $this->get($name);
         } else {//否则把$name当普通属性处理
             return parent::__get($name);
         }
     }

     /**
      * Checks if a property value is null.
      * 重写了 isset 方法,增加了对是否具有某个服务和组件的判断
      * This method overrides the parent implementation by checking if the named component is loaded.
      * @param string $name the property name or the event name
      * @return boolean whether the property value is null
      */
     public function __isset($name)
     {
         if ($this->has($name, true)) {//调用has方法判断是否有某个组件或服务的实例,如果有返回true
             return true;
         } else {//否则判断是否有$name这个属性
             return parent::__isset($name);
         }
     }

     /**
      * Returns a value indicating whether the locator has the specified component definition or has instantiated the component.
      * This method may return different results depending on the value of `$checkInstance`.
      * 当 $checkInstance === false 时,用于判断是否已经定义了某个服务或组件
      * 当 $checkInstance === true 时,用于判断是否已经有了某个服务或组件的实例
      *
      * - If `$checkInstance` is false (default), the method will return a value indicating whether the locator has the specified
      *   component definition.
      * - If `$checkInstance` is true, the method will return a value indicating whether the locator has
      *   instantiated the specified component.
      *
      * @param string $id component ID (e.g. `db`).
      * @param boolean $checkInstance whether the method should check if the component is shared and instantiated.
      * @return boolean whether the locator has the specified component definition or has instantiated the component.
      * @see set()
      */
     public function has($id, $checkInstance = false)
     {
         return $checkInstance ? isset($this->_components[$id]) : isset($this->_definitions[$id]);
     }

     /**
      * Returns the component instance with the specified ID.
      * 根据 $id 获取对应的服务或组件的实例
      *
      * @param string $id component ID (e.g. `db`).
      * @param boolean $throwException whether to throw an exception if `$id` is not registered with the locator before.
      * @return object|null the component of the specified ID. If `$throwException` is false and `$id`
      * is not registered before, null will be returned.
      * @throws InvalidConfigException if `$id` refers to a nonexistent component ID
      * @see has()
      * @see set()
      */
     public function get($id, $throwException = true)
     {
         if (isset($this->_components[$id])) {//如果_components中有该组件或者服务的实例
             return $this->_components[$id];//返回该实例
         }

         if (isset($this->_definitions[$id])) {//如果_definitions中定义了该组件或者服务
             $definition = $this->_definitions[$id];//取得该组件或者服务的值
             if (is_object($definition) && !$definition instanceof Closure) {//如果该值是对象实例且不是Closure(匿名)的实例
                 return $this->_components[$id] = $definition;//则在_components注册该组件或者服务的实例并返回
             } else {
                 return $this->_components[$id] = Yii::createObject($definition);//否则根据定义中的配置参数创建实例并注册返回
             }
         } elseif ($throwException) {//如果允许抛出异常,则抛出异常
             throw new InvalidConfigException("Unknown component ID: $id");
         } else {//否则返回null
             return null;
         }
     }

     /**
      * Registers a component definition with this locator.
      * 用于注册一个组件或服务,其中 $id 用于标识服务或组件。
      * $definition 可以是一个类名,一个配置数组,一个PHP callable(回调函数),或者一个对象
      *
      * For example,
      *
      * ```php
      * // a class name
      * $locator->set('cache', 'yii\caching\FileCache');
      *
      * // a configuration array
      * $locator->set('db', [
      *     'class' => 'yii\db\Connection',
      *     'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
      *     'username' => 'root',
      *     'password' => '',
      *     'charset' => 'utf8',
      * ]);
      *
      * // an anonymous function
      * $locator->set('cache', function ($params) {
      *     return new \yii\caching\FileCache;
      * });
      *
      * // an instance
      * $locator->set('cache', new \yii\caching\FileCache);
      * ```
      *
      * If a component definition with the same ID already exists, it will be overwritten.
      *
      * @param string $id component ID (e.g. `db`).
      * @param mixed $definition the component definition to be registered with this locator.
      * It can be one of the following:
      *
      * - a class name
      * - a configuration array: the array contains name-value pairs that will be used to
      *   initialize the property values of the newly created object when [[get()]] is called.
      *   The `class` element is required and stands for the the class of the object to be created.
      * - a PHP callable: either an anonymous function or an array representing a class method (e.g. `['Foo', 'bar']`).
      *   The callable will be called by [[get()]] to return an object associated with the specified component ID.
      * - an object: When [[get()]] is called, this object will be returned.
      *
      * @throws InvalidConfigException if the definition is an invalid configuration array
      */
     public function set($id, $definition)
     {
         if ($definition === null) {//如果$definition为null,即只传入$id,表示删除
             unset($this->_components[$id], $this->_definitions[$id]);
             return;
         }

         unset($this->_components[$id]);//开始时清空_components中的id

         if (is_object($definition) || is_callable($definition, true)) {//如果传入的$definition是一个类名一个PHP callable(回调函数),或者一个对象
             // an object, a class name, or a PHP callable
             $this->_definitions[$id] = $definition;//直接储存到_definitions中
         } elseif (is_array($definition)) {//如果传入的$definition是一个数组
             // a configuration array
             if (isset($definition['class'])) {//且数组中存在类名
                 $this->_definitions[$id] = $definition;//则将该数组储存到_definitions中
             } 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));
         }
     }

     /**
      * Removes the component from the locator.
      * 删除一个服务或组件
      * @param string $id the component ID
      */
     public function clear($id)
     {
         unset($this->_definitions[$id], $this->_components[$id]);
     }

     /**
      * Returns the list of the component definitions or the loaded component instances.
      * 用于返回Service Locator的 $_components 数组或 $_definitions 数组,
      * 同时也是 components 属性的getter函数
      * @param boolean $returnDefinitions whether to return component definitions instead of the loaded component instances.
      * @return array the list of the component definitions or the loaded component instances (ID => definition or instance).
      */
     public function getComponents($returnDefinitions = true)
     {
         return $returnDefinitions ? $this->_definitions : $this->_components;
     }

     /**
      * Registers a set of component definitions in this locator.
      * 批量方式注册组件,同时也是 components 属性的setter函数
      * 其实就是以数组的方式传入set方法的参数,遍历后调用set
      *
      * This is the bulk version of [[set()]]. The parameter should be an array
      * whose keys are component IDs and values the corresponding component definitions.
      *
      * For more details on how to specify component IDs and definitions, please refer to [[set()]].
      *
      * If a component definition with the same ID already exists, it will be overwritten.
      *
      * The following is an example for registering two component definitions:
      *
      * ```php
      * [
      *     'db' => [
      *         'class' => 'yii\db\Connection',
      *         'dsn' => 'sqlite:path/to/file.db',
      *     ],
      *     'cache' => [
      *         'class' => 'yii\caching\DbCache',
      *         'db' => 'db',
      *     ],
      * ]
      * ```
      *
      * @param array $components component definitions or instances
      */
     public function setComponents($components)
     {
         foreach ($components as $id => $component) {
             $this->set($id, $component);
         }
     }
 }