最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 深入laravel框架中的IOC和DI原理

    正文概述 转载于:掘金(程序员安安)   2021-07-31   428

    官方解释:
    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;
    

    代表已经解析成功了。

    以上内容希望帮助到大家,更多免费PHP大厂PDF,PHP进阶架构视频资料,PHP精彩好文可以微信搜索关注:PHP开源社区

    2021金三银四大厂面试真题集锦,必看!

    四年精华PHP技术文章整理合集——PHP框架篇

    四年精华PHP技术文合集——微服务架构篇

    四年精华PHP技术文合集——分布式架构篇

    四年精华PHP技术文合集——高并发场景篇

    四年精华PHP技术文章整理合集——数据库篇


    起源地下载网 » 深入laravel框架中的IOC和DI原理

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元