page contents

深入laravel6.0框架中的IOC和DI原理

IOC - 控制反转 DI - 依赖注入

attachments-2020-07-R9bylj9M5f16affbb5fa2.png

官方解释:
IOC - 控制反转 DI - 依赖注入


通俗举例:
小明以前很穷,风餐露宿,居无定所。现在发财了,自己也想拥有属于自己的房子,这个时候小明想,要不回老家盖一栋房子,一来可以住,二来可以光宗耀祖,这个时候,小明需要自己去打造一栋房子;后来小明又想,为何不在城市里直接买套房呢,生活更加丰富多彩也方便。于是,小明就找了房产中介(IOC容器)买了房子(依赖注入),最终小明很快就住上了属于自己的房子,开心快乐极了。。。


小明 依赖 房子,小明从自己盖房子(自己“控制”房子)到找中介买房子(让中介“控制”房子),这就叫做控制反转,也就是IOC;而房产中介根据小明的需求,直接把房子提供给小明(当然小明付钱了),这就叫做依赖注入,也就是DI。


当然,这个房子并不是房产中介建设的,而是开发商建设的,这个开发商就是服务提供者
三十年后,小明的这套房子格局跟不上时代了,住得不舒服,想改造/重新装修房子,但是时间成本太高了,于是,小明又找房产中介买了房子,小明又很快住上新房子了。。。这也体现了面向对象中类的单一职责原则


目的
采用IOC思想和DI设计模式,主要目的是:解耦
开车式:异地恋。就算中间隔着一个距离,但也不影响真心的相爱着。
原生代码实现


传统写法

<?php
/**
 * Create by PhpStorm
 * User : Actor
 * Date : 2019-11-01
 * Time : 22:03
 */

/**
 * Class 购房者
 */
class 购房者
{
    private $姓名;

    public function __construct($姓名)
    {
        $this->姓名 = $姓名;
    }

    public function 买房()
    {
        $新房 = new 商品房('0001', '四室两厅', '180平方米');
        echo '我是'.$this->姓名."\r\n";
        echo '我买了'. $新房->获取户型(). '的房子了'."\r\n";
        echo '我买了'. $新房->获取面积(). '的房子了'."\r\n";
    }

}

/**
 * Class 商品房
 */
class 商品房
{
    private $房源编号;
    private $户型;
    private $面积;

    public function __construct($房源编号, $户型, $面积)
    {
        $this->房源编号 = $房源编号;
        $this->户型 = $户型;
        $this->面积 = $面积;
    }

    public function 获取面积()
    {
        return $this->面积;
    }

    public function 获取户型()
    {
        return $this->户型;
    }
}

$大明 = new 购房者('大明');
$大明->买房();
?>

以上代码输出

[actor20:49:55] /projects/phpAdvanced$ php ioc/iocDriveCar.php 
我是大明
我买了四室两厅的房子了
我买了180平方米的房子了
[actor20:56:18] /projects/phpAdvanced$

采用IOC和DI的思想来实现

<?php
/**
 * Create by PhpStorm
 * User : Actor
 * Date : 2019-11-01
 * Time : 22:03
 */


/**
 * Class 购房者
 */
class 购房者
{
    private $姓名;

    public function __construct($姓名)
    {
        $this->姓名 = $姓名;
    }

    public function 买房(商品房 $新房)
    {
        echo '我是'.$this->姓名."\r\n";
        echo '我买了'. $新房->获取户型(). '的房子了'."\r\n";
        echo '我买了'. $新房->获取面积(). '的房子了'."\r\n";
    }

}

/**
 * Class 商品房
 */
class 商品房
{
    private $房源编号;
    private $户型;
    private $面积;

    public function __construct($房源编号, $户型, $面积)
    {
        $this->房源编号 = $房源编号;
        $this->户型 = $户型;
        $this->面积 = $面积;
    }

    public function 获取面积()
    {
        return $this->面积;
    }

    public function 获取户型()
    {
        return $this->户型;
    }
}

/**
 * 房产中介,就是我们讲的ioc
 * Class 房产中介
 */
class 房产中介
{
    private $在售房源 = [];//这个类似于laravel的Container对象中的$bindings
    private $认筹房源 = [];//类似于laravel的Container对象中的$resolved
    private $公租房 = [];//类似于laravel的Container对象中的$instances
    private $网红房源 = [];//类似于laravel的Container对象中的$aliases / $abstractAliases
    private $意向购房群体 = [];

    public function 预售登记($户型, $详细信息)
    {
        $this->在售房源[$户型] = $详细信息;
    }

    public function 获取在售房源($户型)
    {
        return ($this->在售房源[$户型])();//因为是闭包,所以,要增加()来执行闭包函数
    }

    public function 意向登记($意向人, $个人信息)
    {
        $this->意向购房群体[$意向人] = $个人信息;
    }

    public function 获取意向人信息($意向人)
    {
        return ($this->意向购房群体[$意向人])();//因为是闭包,所以,要增加()来执行闭包函数
    }
}

$app = new 房产中介();
$app->预售登记('三室一厅', function(){
    return new 商品房('1001', '三室一厅', '100平方米');
});
$app->预售登记('四室两厅', function(){
    return new 商品房('1002', '四室两厅', '150平方米');
});

$app->意向登记('小明', function(){
    return new 购房者('小明');
});

$app->意向登记('张三', function(){
    return new 购房者('张三');
});

//echo $app->获取意向人信息('小明')->买房($app->获取在售房源('四室两厅'));

$意向人 = $app->获取意向人信息('小明');
$新房 = $app->获取在售房源('四室两厅');
$意向人->买房($新房);
?>

以上程序输出

[actor20:49:43] /projects/phpAdvanced$ php ioc/iocDriveCar.php 
我是小明
我买了四室两厅的房子了
我买了150平方米的房子了
[actor20:49:55] /projects/phpAdvanced$

对IOC和DI的本质分析


从上面的代码,我们看到,房产中介作为IOC,其实本质就是数组(可以是一维数组,也可以是多维数组)。


其实,在laravel框架中,Container对象中的属性$bindings 、$resolved 、$instances、$aliases、$abstractAliases 其实也是从不同维度来管理注册到容器中的对象的。


上面的例子中,如果从业务逻辑角度来讲,无非就是购房者要买房,主要的类有:购房者、商品房。


如果按照传统代码来实现,那么购房者对象对商品房对象的依赖是强依赖,因为在购房者类中需要new 商品房()


而在采用IOC和DI的思想来实现的话,增加了房产中介对象这个IOC容器,商品房首先在房产中介那边进行一下预售登记,购房者也在房产中介那边进行一下意向登记,购房者对象需要依赖商品房对象,采用依赖注入,即:让房产中介直接把实例化后到商品房对象注入到购房者对象,购房者对象无需关注怎么实例化,只管拿过来用就行。


Laravel框架IOC核心源码——绑定
我们来简单看下Laravel框架的核心容器的绑定是怎么实现的?


以下代码都是在Illuminate\Container\Container类中

  1. 通过instance方法绑定
/**
        * Register an existing instance as shared in the container.
        *
        * @param  string  $abstract
        * @param  mixed   $instance
        * @return mixed
        */
       public function instance($abstract, $instance)
       {
           $this->removeAbstractAlias($abstract);
   
           $isBound = $this->bound($abstract);
   
           unset($this->aliases[$abstract]);
   
           // We'll check to determine if this type has been bound before, and if it has
           // we will fire the rebound callbacks registered with the container and it
           // can be updated with consuming classes that have gotten resolved here.
           $this->instances[$abstract] = $instance;
   
           if ($isBound) {
               $this->rebound($abstract);
           }
   
           return $instance;
       }

使用该方法注册绑定到容器中的对象实例是共享的。

2.bind方法

/**
       * Register a binding with the container.
       *
       * @param  string  $abstract
       * @param  \Closure|string|null  $concrete
       * @param  bool  $shared
       * @return void
       */
      public function bind($abstract, $concrete = null, $shared = false)
      {
          $this->dropStaleInstances($abstract);
  
          // If no concrete type was given, we will simply set the concrete type to the
          // abstract type. After that, the concrete type to be registered as shared
          // without being forced to state their classes in both of the parameters.
          if (is_null($concrete)) {
              $concrete = $abstract;
          }
  
          // If the factory is not a Closure, it means it is just a class name which is
          // bound into this container to the abstract type and we will just wrap it
          // up inside its own Closure to give us more convenience when extending.
          if (! $concrete instanceof Closure) {
              $concrete = $this->getClosure($abstract, $concrete);
          }
  
          $this->bindings[$abstract] = compact('concrete', 'shared');
  
          // If the abstract type was already resolved in this container we'll fire the
          // rebound listener so that any objects which have already gotten resolved
          // can have their copy of the object updated via the listener callbacks.
          if ($this->resolved($abstract)) {
              $this->rebound($abstract);
          }
      }

如何使用bind方法来将对象注册绑定到容器中呢?如下,bind方法是将闭包绑定到容器当中

$this->app->bind('User\API', function ($app) {
        return new User\API($app->make('UserLogin'));
  });

同样,bind方法会先删除旧的实例,然后再新的实例放入闭包中,再绑定到容器中。如果第二个参数不是闭包,会通过getClosure方法将类名封装到闭包中,然后在闭包中通过make方法或build方法解析绑定的类。绑定时会将闭包和是否是shared放入$this->bind[]数组中,解析时调用。

singleton方法

/**
       * Register a shared binding in the container.
       *
       * @param  string  $abstract
       * @param  \Closure|string|null  $concrete
       * @return void
       */
      public function singleton($abstract, $concrete = null)
      {
          $this->bind($abstract, $concrete, true);
      }

从官方的代码可以看出,singleton方法,最终还是调用了$this->bind()方法,只是通过singleton()方法绑定到容器的对象只会被解析一次,之后的调用都返回相同的实例,这就是所谓的单例。


Laravel框架IOC核心源码——解析

看源码

    /**
     * Resolve the given type from the container.
     *
     * @param  string  $abstract
     * @param  array  $parameters
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public function make($abstract, array $parameters = [])
    {
        return $this->resolve($abstract, $parameters);
    }






    /**
     * Resolve the given type from the container.
     *
     * @param  string  $abstract
     * @param  array  $parameters
     * @param  bool   $raiseEvents
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function resolve($abstract, $parameters = [], $raiseEvents = true)
    {
        $abstract = $this->getAlias($abstract);

        $needsContextualBuild = ! empty($parameters) || ! is_null(
            $this->getContextualConcrete($abstract)
        );

        // If an instance of the type is currently being managed as a singleton we'll
        // just return an existing instance instead of instantiating new instances
        // so the developer can keep using the same objects instance every time.
        if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
            return $this->instances[$abstract];
        }

        $this->with[] = $parameters;

        $concrete = $this->getConcrete($abstract);

        // We're ready to instantiate an instance of the concrete type registered for
        // the binding. This will instantiate the types, as well as resolve any of
        // its "nested" dependencies recursively until all have gotten resolved.
        if ($this->isBuildable($concrete, $abstract)) {
            $object = $this->build($concrete);
        } else {
            $object = $this->make($concrete);
        }

        // If we defined any extenders for this type, we'll need to spin through them
        // and apply them to the object being built. This allows for the extension
        // of services, such as changing configuration or decorating the object.
        foreach ($this->getExtenders($abstract) as $extender) {
            $object = $extender($object, $this);
        }

        // If the requested type is registered as a singleton we'll want to cache off
        // the instances in "memory" so we can return it later without creating an
        // entirely new instance of an object on each subsequent request for it.
        if ($this->isShared($abstract) && ! $needsContextualBuild) {
            $this->instances[$abstract] = $object;
        }

        if ($raiseEvents) {
            $this->fireResolvingCallbacks($abstract, $object);
        }

        // Before returning, we will also set the resolved flag to "true" and pop off
        // the parameter overrides for this build. After those two things are done
        // we will be ready to return back the fully constructed class instance.
        $this->resolved[$abstract] = true;

        array_pop($this->with);

        return $object;
    }




    /**
     * Instantiate a concrete instance of the given type.
     *
     * @param  string  $concrete
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public function build($concrete)
    {
        // If the concrete type is actually a Closure, we will just execute it and
        // hand back the results of the functions, which allows functions to be
        // used as resolvers for more fine-tuned resolution of these objects.
        if ($concrete instanceof Closure) {
            return $concrete($this, $this->getLastParameterOverride());
        }

        try {
            $reflector = new ReflectionClass($concrete);
        } catch (ReflectionException $e) {
            throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e);
        }

        // If the type is not instantiable, the developer is attempting to resolve
        // an abstract type such as an Interface or Abstract Class and there is
        // no binding registered for the abstractions so we need to bail out.
        if (! $reflector->isInstantiable()) {
            return $this->notInstantiable($concrete);
        }

        $this->buildStack[] = $concrete;

        $constructor = $reflector->getConstructor();

        // If there are no constructors, that means there are no dependencies then
        // we can just resolve the instances of the objects right away, without
        // resolving any other types or dependencies out of these containers.
        if (is_null($constructor)) {
            array_pop($this->buildStack);

            return new $concrete;
        }

        $dependencies = $constructor->getParameters();

        // Once we have all the constructor's parameters we can create each of the
        // dependency instances and then use the reflection instances to make a
        // new instance of this class, injecting the created dependencies in.
        try {
            $instances = $this->resolveDependencies($dependencies);
        } catch (BindingResolutionException $e) {
            array_pop($this->buildStack);

            throw $e;
        }

        array_pop($this->buildStack);

        return $reflector->newInstanceArgs($instances);
    }

从上面的第三段代码build()方法中可以看出,解析时,如果绑定注册的是闭包函数,那么就是直接返回闭包函数的执行,关键代码如下

if ($concrete instanceof Closure) {
            return $concrete($this, $this->getLastParameterOverride());
        }

如果绑定注册的是类名,那么就利用php的反射(ReflectionClass)来实例化对象,并返回给调用者。bind()方法剩下的代码干的就是这个工作。

最后,在resolve()方法中的

$this->resolved[$abstract] = true;

代表已经解析成功了。

以上!


attachments-2020-07-jGpcm2GZ5f16af2de66c4.jpg

  • 发表于 2020-07-21 17:06
  • 阅读 ( 431 )
  • 分类:PHP开发

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
Pack
Pack

1135 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. 小柒 1474 文章
  3. Pack 1135 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章