Laravel之容器

作者: 小聪明李良才 | 来源:发表于2016-06-28 00:18 被阅读3292次

    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 学习笔记 —— 神奇的服务容器

    相关文章

      网友评论

        本文标题:Laravel之容器

        本文链接:https://www.haomeiwen.com/subject/ngftjttx.html