热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Laravel核心——服务容器的细节特性

前言首先欢迎关注我的博客:www.leoyang90.cn在前面几个博客中,我详细讲了Ioc容器各个功能的使用、绑定的源码、解析的源码,今天这篇博客会详细介绍Ioc容器的一些细节,
前言

首先欢迎关注我的博客: www.leoyang90.cn

在前面几个博客中,我详细讲了 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']);
}

推荐阅读
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • 开发笔记:图像识别基于主成分分析算法实现人脸二维码识别
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了图像识别基于主成分分析算法实现人脸二维码识别相关的知识,希望对你有一定的参考价值。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 由于同源策略的限制,满足同源的脚本才可以获取资源。虽然这样有助于保障网络安全,但另一方面也限制了资源的使用。那么如何实现跨域呢,以下是实现跨域的一些方法。 ... [详细]
  • RabbitMq之发布确认高级部分1.为什么会需要发布确认高级部分?在生产环境中由于一些不明原因,导致rabbitmq重启,在RabbitMQ重启期间生产者消息投递失败,导致消息丢 ... [详细]
  • Matlab 中的一些小技巧(2)
    1.Ctrl+D打开子程序  在MATLAB的Editor中,将输入光标放到一个子程序名称中间,然后按Ctrl+D可以打开该子函数的m文件。当然这个子程序要在路径列表中(或在当前工作路径中)。实际上 ... [详细]
author-avatar
死性不改2502857027
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有