Yii2 学习笔记 01 -- 依赖注入在yii2中的应用

什么是依赖注入?
        
         依赖注入(Dependency Injection)是设计模式的一种。名字比较抽象,但是,要解决的问题却是比较明确。对于给定的应用程序,需要借助一些相对独立的组件来完成功能。一般来说,使用这些组件的过程就包含在应用程序的逻辑语句之中。问题是,当这些组件想要做成类似插件功能,以达到应用程序的业务逻辑不变就能随意的更改组件的实现的效果。这种灵活性取决于应用程序如何组装这些组件。如果说应用程序依赖于这些组件的话,依赖注入就是把这些依赖关系从应用程序的逻辑中剥离,放到插件的实现中去的一种方法。

一个简单的例子是: 一个用户类表类,依赖于一个finder类来生成这个列表。而这个finder类呢,依赖于某个db来获取数据。在不考虑依赖注入的时候,程序可能会这样写:

1            $db = new \yii\db\Connection([‘dsn‘ => ‘...‘]);
2            $finder = new UserFinder($db);
3            $lister = new UserLister($finder);

可是,当db的实现发生变化的时候,会引起应用程序的变化,finder类的实现发生变化的时候,会引起应用程序的变化。如果把db,finder看做插件,就需要一套机制来告诉应用程序,lister依赖于finder和db。
        
         依赖注入的基本理就是用一个独立的对象作为组装器,该组装器用一个合适的实例提供给应用程序来使用。有三种类型的依赖注入,

  1. 构造函数注入: 就是在构造函数的参数中提供组件的实现,在构造函数中把这种实现固化到应用程序中。
  2. Setter 注入: 专门通过setter函数实现以上的注入,
  3. 接口注入: 通过接口的实现,达到依赖注入。

有关依赖注入的详细的信息,请参考 Martin Flower 2004年的文章 Inversion of Control Containers and the Dependency Injection pattern

Yii2中的依赖注入

在yii中,我们可以发现第一和第二种类型应用,也就是构造函数注入和setter注入。yii2中的组装器是容器类(Container)。依赖的声明和依赖的注入是通过调用容器类的set和get方法来实现的。

整个过程分三步,

  • 第一步是类的声明,在类的构造函数中,声明依赖关系。
  • 第二部是容器参数的载入, 告诉容器, 要生成某个对象时,是用哪些参数,以及类来实现的。这个过程通过调用 set来完成
  • 第三部 实例的获取。调用容器的get函数,获取新创建的实例。

用容器来实现上面的用户列表的例子,可能是这样的。

 1           // 创建容器
 2              $container = new Container;
 3
 4              //指定如何生成yii\db\Connection,其中,yii\db\Connection 是类的名字
 5              $container->set(‘yii\db\Connection‘, [
 6                   ‘dsn‘ => ‘...‘,
 7              ]);
 8
 9              //指定如何生成 app\models\UserFinderInterface, 所用的类是userFinder
10              $container->set(‘app\models\UserFinderInterface‘, [
11                   ‘class‘ => ‘app\models\UserFinder‘,
12              ]);
13
14              //指定如何生成 UserLister
15              $container->set(‘userLister‘, ‘app\models\UserLister‘);
16
17              //生成lister
18              $lister = $container->get(‘userLister‘);

看起来最后生成lister跟前面db 和UserFinderInterface 没有任何关系。这里面的秘密在于每个类的构造函数隐式地声明了类之间的依赖关系。

              //首先声明一个接口类
              interface UserFinderInterface
              {
                  function findUser();
              }

             //UserFinder 实现这个接口类
              class UserFinder extends Object implements UserFinderInterface
              {
                  public $db;
                  //注意,第一个参数是Connection $db, 包含类的名字的
                  public function __construct(Connection $db, $config = [])
                  {
                      $this->db = $db;
                      parent::__construct($config);
                  }

                  public function findUser()
                  {
                  }
              }
             // UserLists 类, 构造函数的第一个参数声明了它依赖于UserFinderInterface.
              class UserLister extends Object
              {
                  public $finder;

                  public function __construct(UserFinderInterface $finder, $config = [])
                  {
                      $this->finder = $finder;
                      parent::__construct($config);
                  }
              }

容器的作用就是

    1. set,指明实例的生成类和参数
    2. 在get的时候,根据构造函数分析依赖,根据依赖关系,生成相应的对象。

容器类的内部实现:

数据结构:容器类有几个比较重要的内部数组

  /**
                 * @var array singleton objects indexed by their types
                 */
                private $_singletons = [];
                /**
                 * @var array object definitions indexed by their types
                 */
                private $_definitions = [];
                /**
                 * @var array constructor parameters indexed by object types
                 */
                private $_params = [];
                /**
                 * @var array cached ReflectionClass objects indexed by class/interface names
                 */
                private $_reflections = [];
                /**
                 * @var array cached dependencies indexed by class/interface names. Each class name
                 * is associated with a list of constructor parameter types or default values.
                 */
                private $_dependencies = [];  

容器的重要函数:
                        public function set($class, $definition = [], array $params = [])
                        public function get($class, $params = [], $config = [])

        set函数的作用是把一个类注册到容器中,这样容器就知道生成一个类的实例时,应该如何找依赖信息了。 set函数可以注册一个类名字,一个接口名字, 一个别名。注册的时候,还可以指定相应的配置信息 (配置信息的目的是把这些信息赋给生成的类实例)

最基本的就是,第一个参数就是键值,可以是类的名字,接口的名字,别名。而第二个参数是定义,可以是字符串,比如一个类的名字,接口的名字或者是别名;可以是数组,表示相应的配置信息;还可以可以是一个PHP的可调用对象,该对象的参数为function ($container, $params, $config)。该对象在get()调用的时候被执行。$params 是构造函数的参数,$config 是对象的配置信息,常常是个数组。 而$container是容器对象。 返回值是生成的对象,被get()返回。 set函数的第三个参数是生成对象的时候提供给构造函数的参数。

从set函数的源代码可以看出,第一个参数,即class,是作为前文提到的几个关键数组的键值的。定义放在_definitions中,参数放在_params中,

                        public function set($class, $definition = [], array $params = [])
                        {
                            $this->_definitions[$class] = $this->normalizeDefinition($class, $definition);
                            $this->_params[$class] = $params;
                            unset($this->_singletons[$class]);
                            return $this;
                        }

get函数负责根据set设置的依赖关系生成响应的对象实例。其中第一个参数class是用来访问容器中不同数组的键值,第二个参数是$params,跟set中提供的params合并以后,提供给类的构造函数。第三个参数config是配置信息(注 配置就是要给新生成的对象赋一些属性,而参数是类的构造函数处理要处理的参数)

get函数首先从_definition中取出定义,根据其类型,做不同的处理,比如,如果它是一个函数,则把参数合并以后,调用set提供的函数。

新创建的对象实例是调用build方法来构建的。在这个build函数中, 首先要获取依赖。那么这个依赖从哪里来呢?

依赖从类的构造函数的反射分析中来。

方法是根据类的名字空间创建反射对象,取得构造函数,逐个分析构造函数的参数。某个参数有缺省的值,则把缺省值记录到依赖中来。如果一个参数有类,则根据类的名字,生成一个Instance 对象,记录类的名字,为后面依赖的解析作准备。

生成的反射信息放到_reflection数组中,依赖放到_dependency数组中。依赖需要解析,解析的过程就是生成实例的过程,递归调用get的过程。
                    处理过的依赖数组,作为参数,传给反射对象的newInstanceArgs,进而生成类的实例。其实质就是把带有类的指示的参数实例化了,而实例化是根据set函数预先定义的方法。

以上介绍的整个过程就是Yii2中利用container实现依赖注入的过程。可以看到,对依赖的注入是通过分析类的构造函数参数来实现的。

依赖注入的使用:

在yii中,主程序的配置就使用了这种基于容器的依赖注入。

在yii/config/web.php 中config数组中组件 (components) 就是在指定元素的定义。在application的 preInit方法中,用户指定的这些components会跟系统的核心components融合。
                这些组件可以通过     \Yii::$app->componentID 的形式访问, 比如 \Yii::$app->cache. (这属于service locator 概念了) 组件在第一次访问的时候通过Yii::createObject静态函数实例化,实例化的过程就遵循了上文所说的依赖注入的过程。
                当然了,如果你指定要bootstrap某个组件, 比如下面。这样每一个请求来的时候,都会实例化该log组件。

‘bootstrap‘ => [
        ‘log‘,
    ],),   

以下是配置中components中的内容示例。

              ‘components‘ => [
                    ‘request‘ => [
                        // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
                        ‘cookieValidationKey‘ => ‘...‘,
                    ],
                    ‘cache‘ => [
                        ‘class‘ => ‘yii\caching\FileCache‘,
                    ],
                    ‘user‘ => [
                        ‘identityClass‘ => ‘app\models\User‘,
                        ‘enableAutoLogin‘ => true,
                    ],
                    ‘errorHandler‘ => [
                        ‘errorAction‘ => ‘site/error‘,
                    ],
                    ‘mailer‘ => [
                        ‘class‘ => ‘yii\swiftmailer\Mailer‘,
                        // send all mails to a file by default. You have to set
                        // ‘useFileTransport‘ to false and configure a transport
                        // for the mailer to send real emails.
                        ‘useFileTransport‘ => true,
                    ],
                    ‘log‘ => [
                        ‘traceLevel‘ => YII_DEBUG ? 3 : 0,
                        ‘targets‘ => [
                            [
                                ‘class‘ => ‘yii\log\FileTarget‘,
                                ‘levels‘ => [‘error‘, ‘warning‘,‘profile‘],
                            ],
                        ],
                    ],
                    ‘db‘ => require(__DIR__ . ‘/db.php‘),
时间: 2024-08-28 09:07:33

Yii2 学习笔记 01 -- 依赖注入在yii2中的应用的相关文章

[学习笔记]Spring依赖注入

依赖: 典型的企业应用程序不可能由单个对象(在spring中,也可称之bean)组成,再简单的应用也是由几个对象相互配合工作的,这一章主要介绍bean的定义以及bean之间的相互协作. 依赖注入: spring中的依赖注入(Dependency injection (DI))主要有两种形式:构造器注入和setter方法注入. 构造器注入: 基于构造函数的方式有其自己的优势,它能够明确地创建出带有特定构造参数的对象,另外它对于创建一些在类中不需要经常变化的域有明显的优势.如果用setter方法来做

AngularJS学习笔记之依赖注入

最近在看AngularJS权威指南,由于各种各样的原因(主要是因为我没有money,好讨厌的有木有......),于是我选择了网上下载电子版的(因为它不要钱,哈哈...),字体也蛮清晰的,总体效果还不错.但是,当我看到左上角的总页码的时候,479页....479....479....俺的小心脏被击穿了二分之一有木有啊,上半身都石化了有木有啊,那种特别想学但是看到页码又不想学的纠结的心情比和女朋友吵架了还复杂有木有啊,我平常看的电子书百位数都不大于3的好伐! 哎,原谅我吧,我应该多看几本新华字典习

Spring.Net学习笔记五(依赖注入)

谈到高级语言编程,我们就会联想到设计模式:谈到设计模式,我们就会说道怎么样解耦合.而Spring.NET的IoC容器其中的一种用途就是解耦合,其最经典的应用就是:依赖注入(Dependeny Injection)简称DI,目前DI是最优秀的解耦方式之一.下面我就来谈谈依赖注入的应用场景. 我模拟了三种不同的场景,可以一起学习使用依赖注入的重要性. 下面是应用场景的条件:人类使用工具劳动.      /**//// <summary> /// 抽象人类 /// </summary>

Spring学习笔记——Spring依赖注入原理分析

我们知道Spring的依赖注入有四种方式,分别是get/set方法注入.构造器注入.静态工厂方法注入.实例工厂方法注入 下面我们先分析下这几种注入方式 1.get/set方法注入 public class SpringAction { //注入对象springDao private SpringDao springDao; //一定要写被注入对象的set方法 public void setSpringDao(SpringDao springDao) { this.springDao = spri

Unity学习笔记(4):依赖注入

Unity具体实现依赖注入包含构造函数注入.属性注入.方法注入,所谓注入相当赋值,下面一个一个来介绍 1:构造函数注入 1.1当类有多个构造函数时,可以通过InjectionConstructor特性来指定某个构造函数来解析注入对象. [InjectionConstructor] public Student(IClass _class,string name) { ToClass = _class; Name = name; } 1.2构造函数中IClass参数,如果IUnityContain

Spring.Net学习笔记(2)-依赖注入

一.开发环境 操作系统:Win10 编译器:VS2013 framework版本:.net 4.5 Spring版本:1.3.1 二.涉及程序集 Spring.Core.dll Common.Loggin.dll 三.项目结构 四.开发过程 1.新建一个接口文件 namespace SpringNetIoc.IScene { public interface IBall { void DoSport(); } } 2.新建两个实现接口IBall的具体类 namespace SpringNetIo

angular2 学习笔记 ( DI 依赖注入 )

refer : http://blog.thoughtram.io/angular/2016/09/15/angular-2-final-is-out.html ( search Dependency Injection ) 小说明 : 大致流程 : 把 providers 写入 injector, 然后通过 injector 来注入 service. 单列 : 一个 service 在一个 injector 里是单列. 查找逻辑 : injector 有父子关联, 如果子 injector 没

yii2学习笔记

之前看过Yii2框架,也在其他框架实现其Gii手脚架功能,现在开始使用Yii做项目,顺便记录一下学习笔记 先推荐一个网址 Yii2速查表(中文版)http://nai8.me/tool-sc.html Yii和Laravel类似,都有一个全局的app实例化对象,下面先来看看Yii::app() Yii::app() 是一个实例化的对象,是我们在当前框架里边可以直接操作的对象, 我们可以把这个对象理解成请求应用的第一个对象. Yii框架是纯OOP面向对象框架,也就是利用对象调用类的属性.方法,完成

【opengl 学习笔记01】HelloWorld示例

<<OpenGL Programming Guide>>这本书是看了忘,忘了又看,赶脚还是把笔记做一做心里比较踏实,哈哈. 我的主题是,好记性不如烂笔头. ================================================================ 1. 下载glut库 glut库地址为:www.opengl.org/resources/libraries/glut/glutdlls37beta.zip glut全称为:OpenGL Utilit