前言

在前面几个博客中,我详细讲了 Ioc 容器各个功能的使用、绑定的源码、解析的源码,今天这篇博客会详细介绍 Ioc 容器的一些细节,一些特性,以便更好地掌握容器的功能。

注:本文使用的测试类与测试对象都取自 laravel 的单元测试文件src/illuminate/tests/Container/ContainerTest.php

rebind绑定特性

rebind 在绑定之前

instance 和 普通 bind 绑定一样,当重新绑定的时候都会调用 rebind 回调函数,但是有趣的是,对于普通 bind 绑定来说,rebind 回调函数被调用的条件是当前接口被解析过:

public function testReboundListeners()
{
unset($_SERVER['__test.rebind']); $container = new Container;
$container->rebinding('foo', function () {
$_SERVER['__test.rebind'] = true;
});
$container->bind('foo', function () {
});
$container->make('foo');
$container->bind('foo', function () {
}); $this->assertTrue($_SERVER['__test.rebind']);
}

所以遇到下面这样的情况,rebinding 的回调函数是不会调用的:

public function testReboundListeners()
{
unset($_SERVER['__test.rebind']); $container = new Container;
$container->rebinding('foo', function () {
$_SERVER['__test.rebind'] = true;
});
$container->bind('foo', function () {
});
$container->bind('foo', function () {
}); $this->assertFalse(isset($_SERVER['__test.rebind']));
}

有趣的是对于 instance 绑定:

public function testReboundListeners()
{
unset($_SERVER['__test.rebind']); $container = new Container;
$container->rebinding('foo', function () {
$_SERVER['__test.rebind'] = true;
});
$container->bind('foo', function () {
});
$container->instance('foo', function () {
}); $this->assertTrue(isset($_SERVER['__test.rebind']));
}

rebinding 回调函数却是可以被调用的。其实原因就是 instance 源码中 rebinding 回调函数调用的条件是 rebound 为真,而普通 bind 函数调用 rebinding 回调函数的条件是 resolved 为真. 目前笔者不是很清楚为什么要对 instance 和 bind 区别对待,希望有大牛指导。

rebind 在绑定之后

为了使得 rebind 回调函数在下一次的绑定中被激活,在 rebind 函数的源码中,如果判断当前对象已经绑定过,那么将会立即解析:

public function rebinding($abstract, Closure $callback)
{
$this->reboundCallbacks[$abstract = $this->getAlias($abstract)][] = $callback; if ($this->bound($abstract)) {
return $this->make($abstract);
}
}

单元测试代码:

public function testReboundListeners1()
{
unset($_SERVER['__test.rebind']); $container = new Container;
$container->bind('foo', function () {
return 'foo';
}); $container->resolving('foo', function () {
$_SERVER['__test.rebind'] = true;
}); $container->rebinding('foo', function ($container,$object) {//会立即解析
$container['foobar'] = $object.'bar';
}); $this->assertTrue($_SERVER['__test.rebind']); $container->bind('foo', function () {
}); $this->assertEquals('bar', $container['foobar']);
}

resolving 特性

resolving 回调的类型

resolving 不仅可以针对接口执行回调函数,还可以针对接口实现的类型进行回调函数。

public function testResolvingCallbacksAreCalledForType()
{
$container = new Container;
$container->resolving('StdClass', function ($object) {
return $object->name = 'taylor';
});
$container->bind('foo', function () {
return new StdClass;
});
$instance = $container->make('foo'); $this->assertEquals('taylor', $instance->name);
}
public function testResolvingCallbacksShouldBeFiredWhenCalledWithAliases()
{
$container = new Container;
$container->alias('StdClass', 'std');
$container->resolving('std', function ($object) {
return $object->name = 'taylor';
});
$container->bind('foo', function () {
return new StdClass;
});
$instance = $container->make('foo'); $this->assertEquals('taylor', $instance->name);
}

resolving 回调与 instance

前面讲过,对于 singleton 绑定来说,resolving 回调函数仅仅运行一次,只在 singleton 第一次解析的时候才会调用。如果我们利用 instance 直接绑定类的对象,不需要解析,那么 resolving 回调函数将不会被调用:

public function testResolvingCallbacksAreCalledForSpecificAbstracts()
{
$container = new Container;
$container->resolving('foo', function ($object) {
return $object->name = 'taylor';
});
$obj = new StdClass;
$container->instance('foo', $obj);
$instance = $container->make('foo'); $this->assertFalse(isset($instance->name));
}

extend 扩展特性

extend 用于扩展绑定对象的功能,对于普通绑定来说,这个函数的位置很灵活:

在绑定前扩展

public function testExtendIsLazyInitialized()
{
ContainerLazyExtendStub::$initialized = false; $container = new Container;
$container->extend('Illuminate\Tests\Container\ContainerLazyExtendStub', function ($obj, $container) {
$obj->init();
return $obj;
});
$container->bind('Illuminate\Tests\Container\ContainerLazyExtendStub'); $this->assertFalse(ContainerLazyExtendStub::$initialized);
$container->make('Illuminate\Tests\Container\ContainerLazyExtendStub');
$this->assertTrue(ContainerLazyExtendStub::$initialized);
}

在绑定后解析前扩展

public function testExtendIsLazyInitialized()
{
ContainerLazyExtendStub::$initialized = false; $container = new Container;
$container->bind('Illuminate\Tests\Container\ContainerLazyExtendStub');
$container->extend('Illuminate\Tests\Container\ContainerLazyExtendStub', function ($obj, $container) {
$obj->init();
return $obj;
}); $this->assertFalse(ContainerLazyExtendStub::$initialized);
$container->make('Illuminate\Tests\Container\ContainerLazyExtendStub');
$this->assertTrue(ContainerLazyExtendStub::$initialized);
}

在解析后扩展

public function testExtendIsLazyInitialized()
{
ContainerLazyExtendStub::$initialized = false; $container = new Container;
$container->bind('Illuminate\Tests\Container\ContainerLazyExtendStub'); $container->make('Illuminate\Tests\Container\ContainerLazyExtendStub');
$this->assertFalse(ContainerLazyExtendStub::$initialized); $container->extend('Illuminate\Tests\Container\ContainerLazyExtendStub', function ($obj, $container) {
$obj->init();
return $obj;
});
$this->assertFalse(ContainerLazyExtendStub::$initialized); $container->make('Illuminate\Tests\Container\ContainerLazyExtendStub');
$this->assertTrue(ContainerLazyExtendStub::$initialized);
}

可以看出,无论在哪个位置,extend 扩展都有 lazy 初始化的特点,也就是使用 extend 函数并不会立即起作用,而是要等到 make 解析才会激活。

extend 与 instance 绑定

对于 instance 绑定来说,暂时 extend 的位置需要位于 instance 之后才会起作用,并且会立即起作用,没有 lazy 的特点:

public function testExtendInstancesArePreserved()
{
$container = new Container; $obj = new StdClass;
$obj->foo = 'foo';
$container->instance('foo', $obj);
$container->extend('foo', function ($obj, $container) {
$obj->bar = 'baz'; return $obj;
}); $this->assertEquals('foo', $container->make('foo')->foo);
$this->assertEquals('baz', $container->make('foo')->bar);
}

extend 绑定与 rebind 回调

无论扩展对象是 instance 绑定还是 bind 绑定,extend 都会启动 rebind 回调函数:

public function testExtendReBindingInstance()
{
$_SERVER['_test_rebind'] = false; $container = new Container;
$container->rebinding('foo',function (){
$_SERVER['_test_rebind'] = true;
}); $obj = new StdClass;
$container->instance('foo',$obj); $container->make('foo'); $container->extend('foo', function ($obj, $container) {
return $obj;
}); this->assertTrue($_SERVER['_test_rebind']);
} public function testExtendReBinding()
{
$_SERVER['_test_rebind'] = false; $container = new Container;
$container->rebinding('foo',function (){
$_SERVER['_test_rebind'] = true;
});
$container->bind('foo',function (){
$obj = new StdClass; return $obj;
}); $container->make('foo'); $container->extend('foo', function ($obj, $container) {
return $obj;
}); this->assertFalse($_SERVER['_test_rebind']);
}

  

contextual 绑定特性

contextual 在绑定前

contextual 绑定不仅可以与 bind 绑定合作,相互不干扰,还可以与 instance 绑定相互合作。而且 instance 的位置也很灵活,可以在 contextual 绑定前,也可以在contextual 绑定后:

public function testContextualBindingWorksForExistingInstancedBindings()
{
$container = new Container; $container->instance('Illuminate\Tests\Container\IContainerContractStub', new ContainerImplementationStub); $container->when('Illuminate\Tests\Container\ContainerTestContextInjectOne')->needs('Illuminate\Tests\Container\IContainerContractStub')->give('Illuminate\Tests\Container\ContainerImplementationStubTwo'); $this->assertInstanceOf(
'Illuminate\Tests\Container\ContainerImplementationStubTwo',
$container->make('Illuminate\Tests\Container\ContainerTestContextInjectOne')->impl
);
}

  

contextual 在绑定后

public function testContextualBindingWorksForNewlyInstancedBindings()
{
$container = new Container; $container->when('Illuminate\Tests\Container\ContainerTestContextInjectOne')->needs('Illuminate\Tests\Container\IContainerContractStub')->give('Illuminate\Tests\Container\ContainerImplementationStubTwo'); $container->instance('Illuminate\Tests\Container\IContainerContractStub', new ContainerImplementationStub); $this->assertInstanceOf(
'Illuminate\Tests\Container\ContainerImplementationStubTwo',
$container->make('Illuminate\Tests\Container\ContainerTestContextInjectOne')->impl
);
}

  

contextual 绑定与别名

contextual 绑定也可以在别名上进行,无论赋予别名的位置是 contextual 的前面还是后面:

public function testContextualBindingDoesntOverrideNonContextualResolution()
{
$container = new Container; $container->instance('stub', new ContainerImplementationStub);
$container->alias('stub', 'Illuminate\Tests\Container\IContainerContractStub'); $container->when('Illuminate\Tests\Container\ContainerTestContextInjectTwo')->needs('Illuminate\Tests\Container\IContainerContractStub')->give('Illuminate\Tests\Container\ContainerImplementationStubTwo'); $this->assertInstanceOf(
'Illuminate\Tests\Container\ContainerImplementationStubTwo',
$container->make('Illuminate\Tests\Container\ContainerTestContextInjectTwo')->impl
); $this->assertInstanceOf(
'Illuminate\Tests\Container\ContainerImplementationStub',
$container->make('Illuminate\Tests\Container\ContainerTestContextInjectOne')->impl
);
} public function testContextualBindingWorksOnNewAliasedBindings()
{
$container = new Container; $container->when('Illuminate\Tests\Container\ContainerTestContextInjectOne')->needs('Illuminate\Tests\Container\IContainerContractStub')->give('Illuminate\Tests\Container\ContainerImplementationStubTwo'); $container->bind('stub', ContainerImplementationStub::class);
$container->alias('stub', 'Illuminate\Tests\Container\IContainerContractStub'); $this->assertInstanceOf(
'Illuminate\Tests\Container\ContainerImplementationStubTwo',
$container->make('Illuminate\Tests\Container\ContainerTestContextInjectOne')->impl
);
}

  

争议

目前比较有争议的是下面的情况:

public function testContextualBindingWorksOnExistingAliasedInstances()
{
$container = new Container; $container->alias('Illuminate\Tests\Container\IContainerContractStub', 'stub');
$container->instance('stub', new ContainerImplementationStub); $container->when('Illuminate\Tests\Container\ContainerTestContextInjectOne')->needs('stub')->give('Illuminate\Tests\Container\ContainerImplementationStubTwo'); $this->assertInstanceOf(
'Illuminate\Tests\Container\ContainerImplementationStubTwo',
$container->make('Illuminate\Tests\Container\ContainerTestContextInjectOne')->impl
);
}

  

由于instance的特性,当别名被绑定到其他对象上时,别名 stub 已经失去了与 IlluminateTestsContainerIContainerContractStub 之间的关系,因此不能使用 stub 代替作上下文绑定。
但是另一方面:

public function testContextualBindingWorksOnBoundAlias()
{
$container = new Container; $container->alias('Illuminate\Tests\Container\IContainerContractStub', 'stub');
$container->bind('stub', ContainerImplementationStub::class); $container->when('Illuminate\Tests\Container\ContainerTestContextInjectOne')->needs('stub')->give('Illuminate\Tests\Container\ContainerImplementationStubTwo'); $this->assertInstanceOf(
'Illuminate\Tests\Container\ContainerImplementationStubTwo',
$container->make('Illuminate\Tests\Container\ContainerTestContextInjectOne')->impl
);
}

  

代码只是从 instance 绑定改为 bind 绑定,由于 bind 绑定只切断了别名中的 alias 数组的联系,并没有断绝abstractAlias数组的联系,因此这段代码却可以通过,很让人难以理解。本人在给 Taylor Otwell 提出 PR 时,作者原话为“I'm not making any of these changes to the container on a patch release.”。也许,在以后(5.5或以后)版本作者会更新这里的逻辑,我们就可以看看服务容器对别名绑定的态度了,大家也最好不要这样用。

服务容器中的闭包函数参数

服务容器中很多函数都有闭包函数,这些闭包函数可以放入特定的参数,在绑定或者解析过程中,这些参数会被服务容器自动带入各种类对象或者服务容器实例。

bind 闭包参数

public function testAliasesWithArrayOfParameters()
{
$container = new Container;
$container->bind('foo', function ($app, $config) {
return $config;
}); $container->alias('foo', 'baz');
$this->assertEquals([1, 2, 3], $container->makeWith('baz', [1, 2, 3]));
}

  

extend 闭包参数

public function testExtendedBindings()
{
$container = new Container;
$container['foo'] = 'foo’;
$container->extend('foo', function ($old, $container) {
return $old.'bar’;
}); $this->assertEquals('foobar', $container->make('foo')); $container = new Container; $container->singleton('foo', function () {
return (object) ['name' => 'taylor'];
});
$container->extend('foo', function ($old, $container) {
$old->age = 26;
return $old;
}); $result = $container->make('foo');
$this->assertEquals('taylor', $result->name);
$this->assertEquals(26, $result->age);
$this->assertSame($result, $container->make('foo'));
}

  

bindmethod 闭包参数

public function testCallWithBoundMethod()
{
$container = new Container;
$container->bindMethod('Illuminate\Tests\Container\ContainerTestCallStub@unresolvable', function ($stub,$container) {
$container['foo'] = 'foo';
return $stub->unresolvable('foo', 'bar');
});
$result = $container->call('Illuminate\Tests\Container\ContainerTestCallStub@unresolvable');
$this->assertEquals(['foo', 'bar'], $result);
$this->assertEquals('foo',$container['foo']);
}

  

resolve 闭包参数

public function testResolvingCallbacksAreCalledForSpecificAbstracts()
{
$container = new Container;
$container->resolving('foo', function ($object,$container) {
return $object->name = 'taylor';
}); $container->bind('foo', function () {
return new StdClass;
});
$instance = $container->make('foo'); $this->assertEquals('taylor', $instance->name);
}

  

rebinding 闭包参数

 public function testReboundListeners()
{
$container = new Container;
$container->bind('foo', function () {
return 'foo';
}); $container->rebinding('foo', function ($container,$object) {
$container['bar'] = $object.'bar';
}); $container->bind('foo', function () {
}); $this->assertEquals('bar',$container['foobar']);
}

  

本文转自:https://segmentfault.com/a/1190000009558886

Laravel开发:Laravel核心——服务容器的细节特性的更多相关文章

  1. 转 Laravel 的核心 —— 服务容器

    具体内容请参考 1.laravel 学习笔记 —— 神奇的服务容器 - 灵感 - 来自生活的馈赠https://www.insp.top/article/learn-laravel-container ...

  2. Laravel源码解析 — 服务容器

    前言 本文对将系统的对 Laravel 框架知识点进行总结,如果错误的还望指出 阅读书籍 <Laravel框架关键技术解析> 陈昊 学习课程 Laravel5.4快速开发简书网站 轩脉刃 ...

  3. laravel框架总结(四) -- 服务容器

    1.依赖 我们定义两个类:class Supperman 和 class Power,现在我们要使用Supperman ,而Supperman 依赖了Power class Supperman { p ...

  4. 简单理解laravel框架中的服务容器,服务提供者以及怎样调用服务

      laravel被称为最优雅的框架,最近正在学习中,对于用惯了thinkphp.ci框架的人来说,服务容器.服务提供者,依赖注入这些概念简直是一脸懵逼.我花了些时间梳理了一下,也不敢确定自己说的是对 ...

  5. Laravel之Service Container服务容器

    managing class dependencies and performing dependency injection. Dependency injection is a fancy phr ...

  6. Ioc容器与laravel服务容器初探

    一.Ioc容器 某天,小J心血来潮,决定建造一艘星舰,这艘星舰要搭载"与众不同最时尚,开火肯定棒"的电磁炮.于是他写了一个星舰类: class ElectromagneticGun ...

  7. Laravel开发:Laravel框架门面Facade源码分析

    前言 这篇文章我们开始讲 laravel 框架中的门面 Facade,什么是门面呢?官方文档: Facades(读音:/fəˈsäd/ )为应用程序的服务容器中可用的类提供了一个「静态」接口.Lara ...

  8. laravel5.5的服务容器分析

    简介 服务容器是Laravel的核心.见名知意,服务容器就是一个存放服务的地方,当我们需要某个服务的时候,我们就可以从这个容器中取出我们需要的服务.用更专业一点的术语来说,官网定义服务容器是这样的: ...

  9. Laravel开发:Laravel核心——Ioc服务容器源码解析(服务器解析)

    make解析 服务容器对对象的自动解析是服务容器的核心功能,make 函数.build 函数是实例化对象重要的核心,先大致看一下代码: public function make($abstract) ...

随机推荐

  1. Linux目录处理命令

    1 创建命令 mkdir  -p  目录名 其中 -p表示递归创建,英文为make directories td@td-Lenovo-IdeaPad-Y410P:~$ mkdir Test 上述命令在 ...

  2. 在vs2012中配置使用iisexpress

    在vs2012中配置使用iisexpress   vs2012支持基于iisexpress的web站点调试,这样可以尽可能与生产环境具备一样的环境. 但是,如果在vs2012中直接配置iis目录,通常 ...

  3. solr 常用命令

    1.启动和关闭 a.启动和重启 启动和重启命令有很多选项让你运行在SolrCloud模式,使用示例配置,以hostname为开头或者非默认端口,指向本地ZooKeeper. bin/solr star ...

  4. java-selenium(一)元素定位

    在定位单个元素时,selenium-webdriver 提示了如下一些方法对元素进行定位.下面这些定位方式中,优先使用 id.name.classname,对于网上的链接元素,推荐使用linkText ...

  5. EffectiveJava(9)覆盖equals是总要覆盖hashCode

    覆盖equals是总要覆盖hashCode 通过散列函数将集合中不相等的实例均匀的分布在所有可能的散列值上 1.把某个非零的常数值保存在一个名为result的int类型变量中 2.对于对象中每个关键域 ...

  6. Android SQLiteDatabase中query、insert、update、delete方法参数说明

    1.SQLiteDataBase对象的query()接口: public Cursor query (String table, String[] columns, String selection, ...

  7. 倍福TwinCAT(贝福Beckhoff)基础教程 松下驱动器如何执行绝对值清零

    点击参数-参数一览,然后修改015为0(设置为绝对编码器方式),点击设定值变更,然后传送,EEP,将参数写入驱动器(保持USB线连接,重启驱动器并确认参数确是改成0了)   监视器-清除多圈数,清除之 ...

  8. vue笔记一

    一.Vue基本知识点 1.声明式渲染 <div id="app">{{ msg }}</div> <script> var app = new ...

  9. Phalcon 上下文编码(Contextual Escaping)

    站点及其他B/S应用极易受到 XSS 攻击,虽然PHP提供了转义功能.在某些情况下依旧不够安全.在Phalcon中 Phalcon\Escaper 提供了上下文转义功能,这个模块是由C语言实现的, 这 ...

  10. 嵌套矩形——DAG上的动态规划

    有向无环图(DAG,Directed Acyclic Graph)上的动态规划是学习动态规划的基础.非常多问题都能够转化为DAG上的最长路.最短路或路径计数问题. 题目描写叙述: 有n个矩形,每一个矩 ...