Laravel之容器

1. 背景

惯例介绍下容器的背景,回答第一个问题:什么是容器?

顾名思义,容器即存放东西的地方,里面存放的可以是文本、数值,甚至是对象、接口、回调函数。

那通过容器,解决了什么问题呢?

通过容器最主要解决的就是“解耦” 、“依赖注入(DI)“,从而实现”控制反转(IoC)“

2. DI

上面将了容器是用来解决依赖注入的,那到底什么是依赖注入呢?我们以下面的例子来说明下:

我们假设有一个订单,在构造函数中我们新建了OrderRepository,通过仓库我们就可以对订单进行持久化了,但是突然有一天,我们想把订单的存储从数据库换到redis,我们这时候就必须改订单的构造函数,将OrderRepository换为OrderRedisRepository,而且可能两者的接口还不一样,改动成本非常大。如果哪天我们又想将存储换到mongodb,那我们又得改Order的构造函数,这个时候,我们可以定义一个接口Repository,而Order的构造函数接受Repository作为参数,

class Order {

    /**
     * @type Repository
     */
    private $repository;

    /**
     * Order constructor.
     *
     * @param Repository $repository
     */
    public function __construct(Repository $repository)
    {
//        $this->repository = new OrderMysqlRepository();
//        $this->repository = new OrderRedisRepository();

        $this->repository = $repository;
    }
}

这样客户端在使用上就变成

$repository = new OrderMysqlRepository();
$order = new Order($repository);

上面就是依赖注入了,我们通过构造函数传参的方式将$repository注入到了Order中。

了解了依赖注入,下面就到了我们今天的重点依赖反转。

3. 依赖反转

上面客户端在使用的时候,还是需要手动的创建OrderMysqlRepository,有没有可能将这个创建的逻辑也从客户端抽离出来呢?看下面的代码

class Container
{
    protected $binds;

    protected $instances;

    public function bind($abstract, $concrete)
    {
        if ($concrete instanceof \Closure) {
            $this->binds[$abstract] = $concrete;
        } else {
            $this->instances[$abstract] = $concrete;
        }
    }

    public function make($abstract, $parameters = [])
    {
        if (isset($this->instances[$abstract])) {
            return $this->instances[$abstract];
        }

        array_unshift($parameters, $this);

        return call_user_func_array($this->binds[$abstract], $parameters);
    }
}

上面就是一个简单的容器,在使用上

public function testContainer()
    {
        $container = new Container();
        $container->bind('order',function(Container $c,$repository){
            return new Order($c->make($repository));
        });
        $container->bind('Repository',new OrderRedisRepository);
        $order = $container->make('order',['Repository']);
    }

以上就是一个基本简单可用的Ioc容器了。

我们可以看到IoC核心就是通过事先将一些代码片段注册到容器中,当我们需要实例化类的时候,通过容器,自动的将对象需要的参数实例化出来,并注入进去。

4. Laravel中的容器

Laravel中容器共有15个方法,简单分类了下

Container

4.1 注册

4.1.1 bind

先来看下注册,Laravel的容器支持好多种注册方式,先看最常用的bind,其函数签名是:

public function bind($abstract, $concrete = null, $shared = false);

看到签名中有3个参数,在函数内部经过各种操作后,最终落地到存储上,形式是:

$bindings = [
  'abstract' => [
    'concrete' => $concrete,
    'shared' => $shared;
   ],   
];

bind在注册上,像之前提到过的,可以注册文本、数值,甚至是对象、接口、回调函数,下面就每种形式给出测试,

先看闭包形式:

public function testClosureResolution()
    {
        $container = new Container;
        $container->bind('name', function () {
            return 'Taylor';
        });
//          dd($container);
        $this->assertEquals('Taylor', $container->make('name'));
    }

上面为了测试,通过dd可以打印出好container来,我们看到

Illuminate\Container\Container {#20
  #resolved: []
  #bindings: array:1 [
    "name" => array:2 [
      "concrete" => Closure {#21
        class: "LaravelContainerTest"
      }
      "shared" => false
    ]
  ]
  #instances: []
  #aliases: []
  #extenders: []
  #tags: []
  #buildStack: []
  +contextual: []
  #reboundCallbacks: []
  #globalResolvingCallbacks: []
  #globalAfterResolvingCallbacks: []
  #resolvingCallbacks: []
  #afterResolvingCallbacks: []
}

上面是container的内部,经过bind后,里面的bindings多了我们注册过的name,下一步注册过了,就应该要调用make实例化出来,调用make后,container中resolved多个key

 #resolved: array:1 [
    "name" => true
  ]

在实现make的时候,通过判断是否是闭包来判断,如果是闭包,则直接调用,否则通过反射机制实例化出来

if ($concrete instanceof Closure) {
   return $concrete($this, $parameters);
}

$reflector = new ReflectionClass($concrete);

4.1.2 instance

instance是将我们已经实例化出来的对象、文本等注册进入容器,使用方法如下

    public function testSimpleInstance()
    {
        $c = new Container();
        $name = 'zhuanxu';
        $c->instance('name',$name);
        $this->assertEquals($name,$c->make('name'));
    }

instance方法将其写入到instances: []

4.1.3 singleton

$container = new Container;
$container->singleton('ContainerConcreteStub');

$var1 = $container->make('ContainerConcreteStub');
$var2 = $container->make('ContainerConcreteStub');
$this->assertSame($var1, $var2);

singleton是对bind的简单封装

public function singleton($abstract, $concrete = null)
    {
        $this->bind($abstract, $concrete, true);
    }

4.1.4 alias

public function testAliases()
    {
        $container = new Container;
        $container['foo'] = 'bar';
        $container->alias('foo', 'baz');
        $container->alias('baz', 'bat');
        $this->assertEquals('bar', $container->make('foo'));
        $this->assertEquals('bar', $container->make('baz'));
        $this->assertEquals('bar', $container->make('bat'));
        $container->bind(['bam' => 'boom'], function () {
            return 'pow';
        });
        $this->assertEquals('pow', $container->make('bam'));
        $this->assertEquals('pow', $container->make('boom'));
        $container->instance(['zoom' => 'zing'], 'wow');
        $this->assertEquals('wow', $container->make('zoom'));
        $this->assertEquals('wow', $container->make('zing'));
    }

alias函数是通过起别名的方式来让容器make

4.1.5 share

share是通过闭包的形式,加上关键字static实现的

public function share(Closure $closure)
    {
        return function ($container) use ($closure) {
            static $object;

            if (is_null($object)) {
                $object = $closure($container);
            }

            return $object;
        };
    }

4.1.6 extend

extend是在当原来的容器实例化出来后,可以对其进行扩展

public function testExtendInstancesArePreserved()
    {
        $container = new Container;
        $container->bind('foo', function () {
            $obj = new StdClass;
            $obj->foo = 'bar';

            return $obj;
        });
        $obj = new StdClass;
        $obj->foo = 'foo';
        $container->instance('foo', $obj);
        $container->extend('foo', function ($obj, $container) {
            $obj->bar = 'baz';

            return $obj;
        });
        $container->extend('foo', function ($obj, $container) {
            $obj->baz = 'foo';

            return $obj;
        });
        
        $this->assertEquals('foo', $container->make('foo')->foo);
        $this->assertEquals('baz', $container->make('foo')->bar);
        $this->assertEquals('foo', $container->make('foo')->baz);
    }

4.2 实例化

4.2.1 call

call直接调用函数,自动注入依赖

public function testCallWithDependencies()
    {
        $container = new Container;
        $result = $container->call(function (StdClass $foo, $bar = []) {
            return func_get_args();
        });

        $this->assertInstanceOf('stdClass', $result[0]);
        $this->assertEquals([], $result[1]);

        $result = $container->call(function (StdClass $foo, $bar = []) {
            return func_get_args();
        }, ['bar' => 'taylor']);

        $this->assertInstanceOf('stdClass', $result[0]);
        $this->assertEquals('taylor', $result[1]);

        /*
         * Wrap a function...
         */
        $result = $container->wrap(function (StdClass $foo, $bar = []) {
            return func_get_args();
        }, ['bar' => 'taylor']);

        $this->assertInstanceOf('Closure', $result);
        $result = $result();

        $this->assertInstanceOf('stdClass', $result[0]);
        $this->assertEquals('taylor', $result[1]);
    }

今天就先讲到这,有用的地方再去看的。

参考

laravel 学习笔记 —— 神奇的服务容器

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 先说几句废话,调和气氛。事情的起由来自客户需求频繁变更,伟大的师傅决定横刀立马的改革使用新的框架(created ...
    wsdadan阅读 3,047评论 0 12
  • 背景 接到上面一个需求,要设计一个英雄打怪,怎么做呢? 设计 简要的设计图 我们可以看到Role通过依赖注入武器,...
    小聪明李良才阅读 501评论 0 3
  • 学习laravel快小一年了,到现在才去研究laravel 的核心 '容器 IOC' 这些概念. 写项目的时候有大...
    哎哟我的巴扎黑阅读 5,173评论 6 26
  • 梦里看花追落水 身醒魂醉 茫茫朝朝岁岁 曾几知味 窗外莺歌纷飞 窗里垣壁颓颓 只叹心累
    是卧猫先生阅读 333评论 0 0