yii2 php反射,Yii2中的依赖注入

基本概念

1.依赖倒置(反转)原则(DIP):一种软件架构设计的原则(抽象概念,是一种思想)

在面向对象编程领域中,依赖反转原则(Dependency inversion principle,DIP)是指一种特定的解耦(传统的依赖关系创建在高层次上,而具体的策略设置则应用在低层次的模块上)形式,使得高层次的模块不依赖于低层次的模块的实现细节,依赖关系被颠倒(反转),从而使得低层次模块依赖于高层次模块的需求抽象。

该原则规定:

1.高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口。

2.抽象接口不应该依赖于具体实现。而具体实现则应该依赖于抽象接口。

bVCSNm?w=716&h=281

在上图中,高层对象A依赖于底层对象B的实现;图2中,把高层对象A对底层对象的需求抽象为一个接口A,底层对象B实现了接口A,这就是依赖反转。

该原则颠倒了一部分人对于面向对象设计的认识方式。如高层次和低层次对象都应该依赖于相同的抽象接口。它转换了依赖,高层模块不依赖于低层模块的实现,而低层模块依赖于高层模块定义的接口。通俗的讲,就是高层模块定义接口,低层模块负责实现。

2.控制反转(IoC):一种反转流、依赖和接口的方式(DIP的具体实现方式,一种设计原则)

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器。

实现控制反转主要有两种方式:

1.依赖注入:

2.依赖查找

两者的区别在于,前者是被动的接收对象,在类A的实例创建过程中即创建了依赖的B对象,通过类型或名称来判断将不同的对象注入到不同的属性中,而后者是主动索取相应类型的对象,获得依赖对象的时间也可以在代码中自由控制。

3.依赖注入(DI):IoC的一种实现方式,用来反转依赖(IoC的具体实现方式)

依赖注入有如下实现方式:

接口注入(Interface Injection):实现特定接口以供外部容器注入所依赖类型的对象。

设值注入(Setter Injection): 实现特定属性的public set方法,来让外部容器调用传入所依赖类型的对象。

构造器注入(Constructor Injection): 实现特定参数的构造函数,在新建对象时传入所依赖类型的对象。

基于注解 : 基于Java的注解功能,在私有变量前加“@Autowired”等注解,不需要显式的定义以上三种代码,便可以让外部容器传入对应的对象。该方案相当于定义了public的set方法,但是因为没有真正的set方法,从而不会为了实现依赖注入导致暴露了不该暴露的接口(因为set方法只想让容器访问来注入而并不希望其他依赖此类的对象访问)。

3.依赖查找(DL):IoC的一种实现方式,用来反转依赖(IoC的具体实现方式)

依赖查找更加主动,在需要的时候通过调用框架提供的方法来获取对象,获取时需要提供相关的配置文件路径、key等信息来确定获取对象的状态

小结

依赖倒置原则(DIP):一种软件架构设计的原则(抽象概念,一种思想)。

控制反转(IoC):一种反转流、依赖和接口的方式(DIP的具体实现方式)。

依赖注入(DI):IoC的一种实现方式,用来反转依赖(IoC的具体实现方式)。

IoC容器(也称DI Container):提供了动态地(自动化)创建、注入依赖单元,映射依赖关系等功能,减少了许多代码量(DI框架)。

需要注意的一些地方

1.控制反转的层面

在传统的应用中,程序流程的顺序是由开发者主导的,由于IoC,主导权转移到了框架的手里(因为IoC容器)

2.控制反转需要解决的问题

查找,生成所需的实例,返回给需要者(因此又叫依赖注入)

3.实现依赖注入的目的

尽管一个类A对它所依赖的类B是如何实现的一无所知,类A依然能够与类B通信(通过定义一些通用接口)。类B在开发中可能会有多种实现,依赖注入(同时也是IoC)解决的问题就是自动地将这些类B的实现在需要的时候传递给类A。

4.如何实现依赖注入

最基本的思路是构造一个独立的类,它的功能就是统一为其他所有类的依赖生成所需的实例(assembler,类似容器),然后构造并返回这个类

依赖注入的具体实现方式(主要介绍设值注入和构造器注入)

备注:对类A,类B,类C的定义如下

类A (需要通过容器获取的)

类B (类A的依赖,广义上的接口)

类C (类B的具体实现)

1.构造器注入

a)在类A的构造器参数列表中定义了该类所有需要被依赖注入的东西(类B)

b)在容器中需要先定义好某个接口(广义上的interface,即类B)关联的某个具体实现类(有时还需要配置一些具体参数,即类C),这些容器配置在不同的开发中很可能是不一样的。通常这些配置会是一个独立的文件

c)在需要某个类A的时候通过容器来生成而不是直接new

class MovieLister... (MovieLister相当于类A,MovieFinder相当于类B)

public MovieLister(MovieFinder finder) {

this.finder = finder;

}

class ColonMovieFinder... (这个相当于类c)

public ColonMovieFinder(String filename) {

this.filename = filename;

}

(这里返回的pico就是IoC容器)

private MutablePicoContainer configureContainer() {

MutablePicoContainer pico = new DefaultPicoContainer();

Parameter[] finderParams = {new ConstantParameter("movies1.txt")};

//在使用容器前需要先配置,下面的代码就是对容器的配置

pico.registerComponentImplementation(MovieFinder.class, ColonMovieFinder.class, finderParams);

pico.registerComponentImplementation(MovieLister.class);

return pico;

}

下面是通过容器来获得类A的过程

public void testWithPico() {

MutablePicoContainer pico = configureContainer();//获得一个配置好的容器

MovieLister lister = (MovieLister) pico.getComponentInstance(MovieLister.class);//通过容器来获得类A

Movie[] movies = lister.moviesDirectedBy("Sergio Leone");

assertEquals("Once Upon a Time in the West", movies[0].getTitle());

}

2.setter注入

a)在类A中为所有需要注入的依赖类(类B)创建setter方法

b)在独立的文件配置类A中的依赖的具体实现(即配置类B的具体实现类C)

c)通过容器生成生成类A

class MovieLister... (同样的MovieLister为类A,MovieFinder为类B)

private MovieFinder finder;

public void setFinder(MovieFinder finder) {

this.finder = finder;

}

class ColonMovieFinder... (这个同样的相当于类C)

public void setFilename(String filename) {

this.filename = filename;

}

//下面是容器的配置

movies1.txt

public void testWithSpring() throws Exception {

ApplicationContext ctx = new FileSystemXmlApplicationContext("spring.xml");//获得配置好的容器

MovieLister lister = (MovieLister) ctx.getBean("MovieLister"); //通过容器获取类A

Movie[] movies = lister.moviesDirectedBy("Sergio Leone");

assertEquals("Once Upon a Time in the West", movies[0].getTitle());

}

Service Locator

与DI类似,Service Locator也是用来打破依赖的

基本思想

提供一个独立的类(即Service Locator),它能够为整个应用提供所需的所有service(也可以理解为component)。

具体实现

1.在类A中,依赖的所有类都是通过Service Locator获取的

MovieFinder finder = ServiceLocator.movieFinder();

2.通过配置可以定制在Service Locator中实现如何返回一个特定实例,这个与DI类似

小结

1.实际上可以将Service Locator和DI结合使用,在类A中通过Service Locator获取依赖,而在Service Locator中则可以通过DI来实现获取具体的实例(或者将Service Locator与DI互换也可以?)

2.动态的Service Locator:使用一张映射表,通过查表实现(或直接获取)具体的实例

3.Service Locator与DI 的区别:使用Service Locator时是显式地调用Locator,而Di并没有显式地调用

Yii2中的依赖注入

相关的类:

yii\di\Container 容器

yii\di\instance 容器或Service Locator中的东西: 本质上是对于某一个类实例的引用

yii\di\ServiceLocator

1.yii\di\instance

主要用在两个地方:

1.在配置DI容器的时候,使用Instance来引用一个类名,接口名或者是别名(即Instance的id属性)。因此后续DI容器可以将这个引用解析成相应的对象

2.用在那些使用service locator获取依赖对象的类中

对于 yii\di\Instance:

1.表示的是容器中的内容,代表的是对于实际对象的引用。

2.DI容器可以通过他获取所引用的实际对象。

3.Instance类仅有的一个属性id一般表示的是实例的类型(即component ID, class name, interface name or alias name)。

2.yii\di\Container

注意:下面所说的“对象类型”的具体定义为“类名,接口名,别名”

对于yiidiContainer

a) 5个私有属性(都是数组):$_singletons,$_definitions,$_params,$_reflections,$_dependencies

b) $_singletons // 用于保存单例Singleton对象,以对象类型为键

c) $_definitions // 用于保存依赖的定义,以对象类型为键

d) $_params // 用于保存构造函数的参数,以对象类型为键

e) $_reflections // 用于缓存ReflectionClass对象,以对象类型为键

f) $_dependencies // 用于缓存依赖信息,以对象类型为键

bVTrsM?w=608&h=656

注意

1.在DI容器中,依赖关系的定义是唯一的。 后定义的同名依赖,会覆盖前面定义好的依赖。

2.上面的键具体就是:带命名空间的类名,接口名,或者是一个别名

3.对于 $_definitions 数组中的元素,它要么是一个包含了”class” 元素的数组,要么是一个PHP callable, 再要么就是一个具体对象。这就是规范化后的最终结果

4.对于$_singletons数组中的元素,要不就是null(表示还未实例化),要不就是一个具体的实例

5.对于$_params数组中的元素,就是一个数组,包含构造函数的所有参数

6.对于$_reflections数组中的元素,就是一个ReflectionClass对象

7.setter注入可以在实例化后

yiidiContainer使用的具体过程

一个简单的例子

namespace app\models;

use yii\base\Object;

use yii\db\Connection;

use yii\di\Container;

interface UserFinderInterface

{

function findUser();

}

class UserFinder extends Object implements UserFinderInterface

{

public $db;

public function __construct(Connection $db, $config = [])

{

$this->db = $db;

parent::__construct($config);

}

public function findUser()

{

}

}

class UserLister extends Object

{

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');

// which is equivalent to:

$db = new \yii\db\Connection(['dsn' => '...']);

$finder = new UserFinder($db);

$lister = new UserLister($finder);

1.在类A的构造器参数列表中定义了该类所有需要被依赖注入的东西(类B)

2.注册依赖:

a)yii\di\Container::set()

b)yii\di\Container::setSinglton()

使用到了$_definitions ,$_params, $_singletons

3.对象的实例化

a)解析依赖信息

yii\di\Container::getDependencies() (会被后续的build()调用)

getDependencies():操作$_reflections与$_dependencies

1.会向$_reflections 和 $_dependencies写入信息

2.使用PHP的反射机制来获取类的有关信息,主要就是为了从构造器中获取依赖信息,会将反射得到的信息写入$_reflections

3.将从构造器中获取的依赖信息(即构造函数的参数列表)写入$_dependencies

4.返回值: 数组[$reflection, $dependencies]

yii\di\Container::resolveDependencies() (同样的会被后续的build()调用)

resolveDependencies()利用getDependencies()获得的信息进一步具体处理(递归调用)。处理依赖信息, 将依赖信息中保存的Instance实例所引用的类或接口进行实例化。

b)创建实例

yii\di\Container::build()

由getDependencies()获得第一层依赖

由resolveDependencies()递归分析依赖,最终生成所有依赖的实例

$reflection->newInstanceArgs($dependencies);//生成所有依赖后生成这个实例

注意:DI容器只支持 yiibaseObject 类,也就是说如果你想你的类可以放在DI容器里,那么必须继承自 yiibaseObject 类。

4.获取依赖实例化对象

yii\di\Container::get()

a)如果是已经实例化的单例,直接返回($_singletons)

b)如果是尚未定义(不存在于$_definition),则说明其实例化没有依赖,调用build()

c)存在$_definition

i.$definition为callable,直接调用

ii.$definition为数组,根据$definition数组中的‘class’,递归调用get(),递归终止的条件是(当具体实现类就是当前的依赖类时),递归结束时调用build()进行实例化

iii.$definition为对象,直接返回该对象,并将该对象设置为单例

setSinglton()类似

public function set($class, $definition = [], array $params = [])

{

//normalizeDefinition()处理后,返回值要么是一个包含了”class” 元素的数组,要么是一个PHP callable, 再要么就是一个具体对象

$this->_definitions[$class] = $this->normalizeDefinition($class, $definition);

$this->_params[$class] = $params;

unset($this->_singletons[$class]);

return $this;

}

public function get($class, $params = [], $config = [])

{

if (isset($this->_singletons[$class])) {//是单例,且已经实例化(不为null)

// singleton

return $this->_singletons[$class];

} elseif (!isset($this->_definitions[$class])) {//还没有定义过,需要build

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) {//$concrete相当于之前提到的具体实现类C,而$class则相当于接口类B

$object = $this->build($class, $params, $config);

} else {

$object = $this->get($concrete, $params, $config);//递归,直到找到具体的实现类C

}

} 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;

}

protected function build($class, $params, $config)

{

/* @var $reflection ReflectionClass */

list ($reflection, $dependencies) = $this->getDependencies($class); //获取第一层依赖关系

foreach ($params as $index => $param) {

$dependencies[$index] = $param; //额外提供的构造函数参数,添加到依赖中

}

$dependencies = $this->resolveDependencies($dependencies, $reflection);//递归解析依赖,并会在此返回依赖的实例

if (!$reflection->isInstantiable()) {

throw new NotInstantiableException($reflection->name);

}

if (empty($config)) {

return $reflection->newInstanceArgs($dependencies);//通过反射实例生成对象

}

//config中的对象作为该类的property使用

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);

foreach ($config as $name => $value) {

$object->$name = $value;

}

return $object;

}

}

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();//这里要能获取到类名需要在构造函数中用类型限制参数,否则获取到null,而且注意对于php的基本类型,获取到的也是null

$dependencies[] = Instance::of($c === null ? null : $c->getName());

}

}

}

$this->_reflections[$class] = $reflection;

$this->_dependencies[$class] = $dependencies;

return [$reflection, $dependencies];

}

protected function resolveDependencies($dependencies, $reflection = null)

{

foreach ($dependencies as $index => $dependency) {

if ($dependency instanceof Instance) {

if ($dependency->id !== null) { //这里的dependency是Instance的实例

$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;

}

递归调用的示意图

bVTsiD?w=808&h=675

在Yii2中调用组件

先看一下各个类的继承关系

bVTvtg?w=454&h=668

下面以Yii::$app->db为例

1.配置组件

配置的内容:

'components' => [

'db' => [

'class' => 'yii\db\Connection',

'dsn' => 'mysql:host=localhost;dbname=wechat',

'username' => 'root',

'password' => 'michael',

'charset' => 'utf8',

],

2.在框架的启动过程中加载组件的定义

Yii2的启动

入口脚本:

(new yii\web\Application($config))->run();

1.new yii\web\Application($config)

2.run()

yii\web\Application的构造函数

public function __construct($config = [])

{

Yii::$app = $this;

static::setInstance($this); //将当前module存到Yii::$app->loadedModules[]

$this->state = self::STATE_BEGIN;

//1.通过$config配置别名,基本参数

//2.配置核心组件(仅仅是配置,将Yii框架写好的配置与自己的配置合并)

$this->preInit($config);

$this->registerErrorHandler($config);

//下面这一行是重点,Component是当前类的祖先

//在下面的构造函数中执行了Yii::configure($this, $config),将$config中的配置作为属性添加到$app中

Component::__construct($config);

}

Component::__construct($config)

//实际上下面这个构造函数的定义在yii\base\Object中

public function __construct($config = [])

{

if (!empty($config)) {

Yii::configure($this, $config);

}

$this->init();

}

Yii::configure($this, $config)

public static function configure($object, $properties)

{

foreach ($properties as $name => $value) {

//下面这行代码会触发魔术方法 ($object->components = $value)

//实际执行的代码是ServiceLocator::setComponents($components)

$object->$name = $value;

}

return $object;

}

ServiceLocator::setComponents($components)

public function setComponents($components)

{

foreach ($components as $id => $component) {

$this->set($id, $component);

}

}

最终加载组件配置的代码

//从下面的代码中可以看到,最终组件的配置被存储在$app->_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));

}

}

3.获取组件

Yii::$app->db会触发魔术方法,调用ServiceLocator::__get()

public function __get($name)

{

if ($this->has($name)) {

//已经定义过组件

return $this->get($name);

} else {

//没有定义过组件

return parent::__get($name);

}

}

public function has($id, $checkInstance = false)

{

//这里因为在框架启动过程中将组件的配置加载到$_definitions中了,所以会返回true

return $checkInstance ? isset($this->_components[$id]) : isset($this->_definitions[$id]);

}

//通过下面的代码可以看到,如果组件已经实例化过存储在$_components中了,就直接返回

//否则通过Yii::createObject($definition)来生成组件实例,并存储到$_components中

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 {

return $this->_components[$id] = Yii::createObject($definition);

}

} elseif ($throwException) {

throw new InvalidConfigException("Unknown component ID: $id");

} else {

return null;

}

}

Yii::createObject($definition)

//在Yii2框架中要使用DI来生成对象的话,可以通过调用Yii::createObject($definition)实现

public static function createObject($type, array $params = [])

{

if (is_string($type)) {

//使用容器

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 static::$container->invoke($type, $params);

} elseif (is_array($type)) {

throw new InvalidConfigException('Object configuration must be an array containing a "class" element.');

}

throw new InvalidConfigException('Unsupported configuration type: ' . gettype($type));

}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值