如何实现 IoC 容器和服务提供者是什么概念
IoC 容器和服务提供者
上一节我们的代码还是没有完全达到解偶,假如我们项目里有很多功能用到了这个login功能,我们则在这几个页面反复写。但是突然我们有一天换需求了,觉得数据库记录日志不太好,想要改成文件的,那我们不是每个页面用到login功能的都去替换成new FileLog()呢?,那改如何修改呢?
我们可以借助一个容器,提前把log,user都绑定到Ioc容器中。User的创建交给这个容器去做。比如下面这样的,你再任何地方使用login。都不需要关心是用什么记录日志了,哪怕后期需要修改只需要在ioc容器修改绑定其他记录方式日志就行了。
具体代码实现的思路
- Ioc容器维护binding数组记录bind方法传入的键值对如:log=>FileLog, user=>User
- 在ioc->make('user')的时候,通过反射拿到User的构造函数,拿到构造函数的参数,发现参数是User的构造函数参数log,然后根据log得到FileLog。
- 这时候我们只需要通过反射机制创建 $filelog = new FileLog();
- 通过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生命周期的时候再详细说。
好了我们这个章节的内容就到这里。
2 Comments
大佬,学习了 ~~
讲得好, 例子也好.
文章开头提到 "但是突然我们有一天换需求了,觉得数据库记录日志不太好,想要改成文件的,那我们不是每个页面用到login功能的都去替换成new FileLog()呢?,那改如何修改呢?"
这里我有一个问题, 在原来开发中, 通常会将这种功能进行简单封装, 如
log
函数用于记录日志:上面是一个演示, 实际会封装得足够完善 (比如 ThinkPHP 的
S
函数), 这样, 如果需要修改日志记录方式, 其实只要修改这个log
函数实现就行.所以我觉得 IoC 容器可能好处在于依赖处理, 就是上面
getDependencies
方法.不知道理解对不对?