emao-container_1

16年的时候整理过一篇IOC的实现原理,在进一步之前可先 查看
然后结合laravel再来看IOC的实现,便会有一种 原来是这样 的感受。从而也会对一直不接触的设计模式有根清晰的认识。

先来看几个名词

依赖注入

这是一个很花哨的名词,如很多设计模式一样,但不了解具体实现的时候就会觉得雨里雾里,不知所云。

先来看一段简单的代码,也是很「经典」的代码

class UserController
{
    publc function getUserInfo($id)
    {
           $model = new UserDao;

           return $model->where('id', $id)->first();
    }
}

也许你看出来了,所谓「经典」并不是因为代码有奇妙之处,而是,这样的代码在很多「工程师」的代码里,随处可见。

这样的代码一个很明显的问题便是,「高级代码」依赖「低级代码」,「低级代码」如果出错,哪「高级代码」就完全用不了。

{tip} 「高级代码」 业务逻辑代码实现,也就是最终呈现给用户的代码。 「低级代码」 数据库操作,文件操作,网络操作等一切为业务服务的代码。

来看一段改进

class UserController
{
    protected $user;

    publc function getUserInfo(User $user, $id)
    {         
           return $user->where('id', $id)->first();
    }
}

修改以后的业务代码,不需要知道数据从何而来,只需要知道从一个 User 对象能获取到所需要的数据。

这样的方式便叫「依赖注入」,「我」依赖你,但并不是离了「你」就不能活。

控制反转

控制反转是依赖注入的一种改进,在上面的代码中,「高级代码」 想要获取到「低级代码」数据,必须手动传入「User」,这样当「User」作出修改,哪「高级代码」依赖的部分也要做出修改。

利用「控制反转」来实现

class UserController
{
    protected $user;

    publc function getUserInfo(UserInterface $user, $id)
    {         
           return $user->where('id', $id)->first();
    }
}

这里注入的是一个「接口」,「高级代码」不依赖具体的实现,而依赖的是实现的接口。这样无论「低级代码」作出怎样的修改,「高级代码」都不需要做任何修改。

这就是「面向对象」常说的:要面向接口编程,而不是面向实现。

Laravel容器实现

容器的终极目标就是解决「依赖」。让「低级代码」的实现,不会出现在「高级代码」的逻辑中。

通过Laravel的「Route」模块,来分析容器的实现。看 routes/web.php 的内容

以前,每当学习一个框架的时候,首先看的是控制器,因为比较容易看到效果。

Route::get('/',funtion(){
     return view('welcome');
})

初学者往往会以为这里就是框架的入口—因为我们写的业务代码确实是从这里开始的,从而导致了很多人认为请求直接就到了这里。其实不然。

既然 web.php 里可以使用 Route::method ,通过 这篇文章 可以知道,既然这里可以使用,必然有地方进行了「绑定」。

有一个现成的「轮子」,Illuminate\Container (容器) Laravel框架的核心程序便是继承了这个「轮子」,然后把「其他模块」通过「绑定」的方式,组合进了框架。所以 Laravel 的核心代码很少。其中「路由」,「请求」,「响应」等等各个模块都是通过「绑定」的方式组合到一起实现强大的功能。

在进一步之前,确保你已经看懂了Laravel的文档,请求的生命周期,很多时候最好的学习资料是框架本身提供的文档。

Laravel 通过 Illuminate\Container 提供的 bind instance singleton 等等方式进行「注入」到容器里,再通过make 获取到注入的「实例」「匿名函数」「接口实现」等。

本来打算写每个函数的用法,然后发现,无论怎么写,都没有官方文档总结的到位。遂放弃,所以建议多读几遍官方文档吧。

接着上面的 Route 继续看,通过查看 Illuminate\FoundationApplication 可以看到,Laravel通过「Container」注入了路由的「服务提供者」,并把「Container」传给了「服务提供者」

$this->register(new RoutingServiceProvider($this));

而后由 RoutingServiceProvider 自己注册实现和所需要的依赖。

class RoutingServiceProvider extends ServiceProvider
{
    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->registerRouter();

        $this->registerUrlGenerator();

        $this->registerRedirector();

        $this->registerPsrRequest();

        $this->registerPsrResponse();

        $this->registerResponseFactory();
    }

这时候,我们便可以通过 $app['route'] 或者 $app->route 来获取到路由的实现,这会执行「容器」的 make 方法。获取到之前注册的内容。

而在web.php里没有这样写,这就涉及到另一个概念 Facade

看到配置文件 bootstrap/app.php

aliases' => [
    'Route' => Illuminate\Support\Facades\Route::class
]

当使用 Route 的实际调用的是 Illuminate\Support\Facades\Route::class , Route::method

执行的就是 Illuminate\Support\Facades::__callStatic

public static function __callStatic($method, $args)
{
    $instance = static::getFacadeRoot();

    if (! $instance) {
        throw new RuntimeException('A facade root has not been set.');
    }

    return $instance->$method(...$args);
}

其中

$instance = static::getFacadeRoot();

最终调用

protected static function resolveFacadeInstance($name)
{
    if (is_object($name)) {
        return $name;
    }

    if (isset(static::$resolvedInstance[$name])) {
        return static::$resolvedInstance[$name];
    }

    return static::$resolvedInstance[$name] = static::$app[$name];
}

可以看到,其实就是调用了 $app[name] ;

到此,通过容器的分析,便可以了解laravel的核心概念了。