如何实现Ioc容器和服务提供者是什么概念

未匹配的标注

什么是Ioc容器和服务提供者?

上一节我们的代码还是没有完全达到解偶,假如我们项目里有很多功能用到了这个login功能,我们则在这几个页面反复写。但是突然我们有一天换需求了,觉得数据库记录日志不太好,想要改成文件的,那我们不是每个页面用到login功能的都去替换成new FileLog()呢?,那该如何修改呢?

我们可以借助一个容器,提前把log,user都绑定到Ioc容器中。User的创建交给这个容器去做。比如下面这样的,你再任何地方使用login。都不需要关心是用什么记录日志了,哪怕后期需要修改只需要在ioc容器修改绑定其他记录方式日志就行了。

具体代码实现的思路

  1. Ioc容器维护binding数组记录bind方法传入的键值对如:log=>FileLog, user=>User
  2. 在ioc->make(‘user’)的时候,通过反射拿到User的构造函数,拿到构造函数的参数,发现参数是User的构造函数参数log,然后根据log得到FileLog。
  3. 这时候我们只需要通过反射机制创建 $filelog = new FileLog();
  4. 通过newInstanceArgs然后再去创建new User($filelog);

//实例化ioc容器
$ioc = new Ioc();
$ioc->bind('Log','FileLog');
$ioc->bind('user','User');
$user = $ioc->make('user');
$user->login();

这里的容器就是指Ioc容器,服务提供者就是User。

对啦,上一节遗留一个问题如果参数是接口该怎么处理,其实就是通过Ioc容器提前绑定好。

核心的Ioc容器代码编写


interface Log
{
    public function write();
}

// 文件记录日志
class FileLog implements Log
{
    public function write(){
        echo 'file log write...';
    }
}

// 数据库记录日志
class DatabaseLog implements Log
{
    public function write(){
        echo 'database log write...';
    }
}

class User
{
    protected $log;
    public function __construct(Log $log)
    {
        $this->log = $log;
    }
    public function login()
    {
        // 登录成功,记录登录日志
        echo 'login success...';
        $this->log->write();
    }
}

class Ioc
{
    public $binding = [];

    public function bind($abstract, $concrete)
    {
        //这里为什么要返回一个closure呢?因为bind的时候还不需要创建User对象,所以采用closure等make的时候再创建FileLog;
        $this->binding[$abstract]['concrete'] = function ($ioc) use ($concrete) {
            return $ioc->build($concrete);
        };

    }

    public function make($abstract)
    {
        // 根据key获取binding的值
        $concrete = $this->binding[$abstract]['concrete'];
        return $concrete($this);
    }

    // 创建对象
    public function build($concrete) {
        $reflector = new ReflectionClass($concrete);
        $constructor = $reflector->getConstructor();
        if(is_null($constructor)) {
            return $reflector->newInstance();
        }else {
            $dependencies = $constructor->getParameters();
            $instances = $this->getDependencies($dependencies);
            return $reflector->newInstanceArgs($instances);
        }
    }

    // 获取参数的依赖
    protected function getDependencies($paramters) {
        $dependencies = [];
        foreach ($paramters as $paramter) {
            $dependencies[] = $this->make($paramter->getClass()->name);
        }
        return $dependencies;
    }

}

//实例化IoC容器
$ioc = new Ioc();
$ioc->bind('Log','FileLog');
$ioc->bind('user','User');
$user = $ioc->make('user');
$user->login();

至此,我们的Ioc就已经实现了。

laravel中的服务容器和服务提供者是什么样子呢?

可以在config目录找到app.php中providers,这个数组定义的都是已经写好的服务提供者

$providers = [
    Illuminate\Auth\AuthServiceProvider::class,
    Illuminate\Broadcasting\BroadcastServiceProvider::class,
    Illuminate\Bus\BusServiceProvider::class,
    Illuminate\Cache\CacheServiceProvider::class,
    ...
]
...

// 随便打开一个类比如CacheServiceProvider,这个服务提供者都是通过调用register方法注册到ioc容器中,其中的app就是Ioc容器。singleton可以理解成我们的上面例子中的bind方法。只不过这里singleton指的是单例模式。

class CacheServiceProvider{
    public function register()
    {
        $this->app->singleton('cache', function ($app) {
            return new CacheManager($app);
        });

        $this->app->singleton('cache.store', function ($app) {
            return $app['cache']->driver();
        });

        $this->app->singleton('memcached.connector', function () {
            return new MemcachedConnector;
        });
    }
}

具体服务提供者register方法是什么时候执行的,我们到讲laravel生命周期的时候再详细说。

好了我们这个章节的内容就到这里。

示例代码下载链接

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
cxp1539
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
贡献者:11
讨论数量: 2
发起讨论 只看当前版本


lookearth
为什么 interface log 的 log 是小写。implements Log 却是大写?
1 个点赞 | 4 个回复 | 问答 | 课程版本 5.5
龙卷风
IoC 容器
0 个点赞 | 1 个回复 | 问答 | 课程版本 5.5