Laravel 核心Ioc容器

776 阅读2分钟

Hello,我是Rocket

这是我参与更文挑战的第3天,活动详情查看:更文挑战

引言

  • 要说laravel框架最核心的那肯定要属Ioc容器了
  • 听着名字很高大上,你对它了解多少呢
  • 什么是依赖注入
  • Laravel bind、singleton 、instance 有什么区别呢
  • 服务解析是怎么处理的
  • 构建一个简单的容器加深大家对IOC容器的理解

1、普通new对象

<?php

//阿里云短信
class AliyunSms {
    public function __construct(){}

    public function send()
    {
        echo 'sms gateway by aliyun';
    }
}
//百度云短信
class BaiduSms {
    public function __construct(){}

    public function send()
    {
        echo 'send gateway by baidu';
    }
}


//发短信
class SMS {

    private $sms;

    public function __construct()
    {
        $this->sms= new AliyunSms ();
    }

    public function  send()
    {
         $this->sms->send();
    }
}

$pb = new SMS ();
$pb->send();

2、 依赖注入

只要不是由内部生产(比如初始化、构造函数 __construct 中通过工厂方法、自行手动 new 的),而是由外部以参数或其他形式注入的,都属于依赖注入(DI)

<?php

//定义短信类接口
interface Smsinferface{
    public function send();
}

//阿里云短信
class AliyunSms implements Smsinferface{
    public function __construct(){}

    public function send()
    {
        echo 'sms gateway by aliyun';
    }
}
//百度云短信
class BaiduSms implements Smsinferface{
    public function __construct(){}

    public function send()
    {
        echo 'send gateway by baidu';
    }
}


//发短信
class SMS {

    private $sms;

    public function __construct(Smsinferface $sms)
    {
        $this->sms= $sms;
    }

    public function  send()
    {
         $this->sms->send();
    }
}


//生成依赖
$aliyun = new AliyunSms();
//注入依赖
$a = new SMS ($aliyun);
$a->send();

$b = new SMS(new BaiduSms());
$b->send();

3、Laravel几个绑定实例的方法的区别

bind 只要使用 bind() 方法进行绑定,每次用的时候,就会创建一个新的实例( 闭包函数就会被调用一次)

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

singleton 实际上还是调用bind 单例对象都是一次创建,反复使用

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

instance 把已经生成的实例,如果需要复用,就进行绑定

public function instance($abstract, $instance)

4、解析服务的几种方式

make 基础解析服务

        $abstract = $this->getAlias($abstract);

        $needsContextualBuild = ! empty($parameters) || ! is_null(
            $this->getContextualConcrete($abstract)
        );
      
        if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
            return $this->instances[$abstract];
        }

        $this->with[] = $parameters;

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

        if ($this->isBuildable($concrete, $abstract)) {
            $object = $this->build($concrete);
        } else {
            $object = $this->make($concrete);
        }

     
        foreach ($this->getExtenders($abstract) as $extender) {
            $object = $extender($object, $this);
        }

     
        if ($this->isShared($abstract) && ! $needsContextualBuild) {
            $this->instances[$abstract] = $object;
        }

        $this->fireResolvingCallbacks($abstract, $object);

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

        array_pop($this->with);

        return $object;

resolve()

if (! function_exists('resolve')) {
   
    function resolve($name)
    {
        return app($name);
    }
}

app()

function app($abstract = null, array $parameters = [])
{
        if (is_null($abstract)) {
            return Container::getInstance();
        }

        return Container::getInstance()->make($abstract, $parameters);
}

5、构建一个简单的容器类

实现IOC容器的基本功能,绑定、解析、依赖倒置以及控制翻转

实现容器类

class Container
{
    // 保存与接口绑定的闭包,
    // 闭包必须能够返回接口的实例。
    protected $bindings = [];

    //单例实例
    protected $instances = [];

    // 为某个接口绑定一个实现,有两种情况:
    // 
    // 第一种是绑定接口的实现的类名;
    // 第二种是绑定一个闭包,这个闭包应该返回接口的实例。
    // 
    // 不管哪种情况,实例化操作都是调用 build() 方法完成。
    // 
    // 第二种情况,主要是为了能够在实例化前后进行额外的操作,
    // 实例化的逻辑就书写在闭包中。
    public function bind($abstract, $concrete = null,$shared= false)
    {
        if (is_null($concrete)) {
            $concrete = $abstract;
        }
        // 如果提供的参数不是闭包,而是一个类,
        // 则构建一个闭包,直接调用 build() 方法进行实例化
        if (!$concrete instanceof Closure) {
            // 调用闭包时,传入的参数是容器本身,即 $this生成默认的
            $concrete = function ($c) use ($concrete) {
                return $c->build($concrete);
            };
        }

        $this->bindings[$abstract] = compact('concrete','shared');
    }

    //单例绑定
    public function singleton($abstract, $concrete = null)
    {
        $this->bind($abstract, $concrete, true);
    }

    //获取对应的闭包
    public function getConcrete($abstract){
        if (isset($this->bindings[$abstract])) {
            return $this->bindings[$abstract]['concrete'];
        }

        return $abstract;
    }

    public function isShared($abstract)
    {
        return isset($this->instances[$abstract]) ||
              (isset($this->bindings[$abstract]['shared']) &&
               $this->bindings[$abstract]['shared'] === true);
    }

    // 生成指定接口的实例
    public function make($abstract)
    {
        if (isset($this->instances[$abstract])) {
            return $this->instances[$abstract];
        }
        // 取出闭包
        $concrete = $this->getConcrete($abstract);
        // 取得实例
        $object = $this->build($concrete);
        // 判断是否需要分享对象(单例)
        if ($this->isShared($abstract)) {
            $this->instances[$abstract] = $object;
        }
        return $object;
    }

    // 生成实例
    public function build($concrete)
    {
        //如果是闭包直接调用闭包函数
        if ($concrete instanceof Closure) {
            return $concrete($this);
        }
        // 取得类的反射
        $reflector = new ReflectionClass($concrete);

        // 检查类是否可实例化
        if (! $reflector->isInstantiable()) {
            // 如果不能,意味着接口不能正常工作,报错
            echo "Target [$concrete] is not instantiable";
        }

        // 取得构造函数的反射
        $constructor = $reflector->getConstructor();

        // 检查是否有构造函数
        if (is_null($constructor)) {
            // 如果没有,就说明没有依赖,直接实例化
            return new $concrete;
        }

        // 取得包含每个参数的反射的数组
        $parameters = $constructor->getParameters();
        // 返回一个真正的参数列表,那些被类型提示的参数已经被注入相应的实例
        $realParameters = $this->resolveDependencies($parameters);

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

    //解析参数依赖
    protected function resolveDependencies($parameters)
    {
        $realParameters = [];
        foreach($parameters as $parameter) {
            // 如果一个参数被类型提示为类 foo,
            // 则这个方法将返回类 foo 的反射
            $dependency = $parameter->getClass();
            if(is_null($dependency)) {
                $realParameters[] = NULL;
            } else {
                $realParameters[] = $this->make($dependency->name);
            }
        }

        return (array)$realParameters;
    }
}

例子

$app = new Container();

$app->bind(Smsinferface::class, BaiduSms::class);

$app->bind('sms', SMS::class);//可以理解成别名

$sms = $app->make('sms');

$sms->send();

//单例绑定
$app->singleton('newsms', SMS::class);
$newsms = $app->make('newsms');

$newsms2 = $app->make('newsms');


var_dump($sms === $newsms);
var_dump($newsms === $newsms2);

6、结尾

与诸君共勉之,希望对您有所帮助