容器和依赖注入
容器和依赖注入
容器和依赖注入(container)
依赖注入, 是通过容器来实现的, 可以实现对引用的类的自动引入, 以及该类的依赖对象的自动加载
依赖注入是指对类的依赖通过构造器完成自动注入:
<?php namespace app\index\controller; use app\index\model\User; class Index { protected $user; // 通过依赖注入方式,实现User实例的自动注入到当前对象中 public function __construct(User $user) { $this->user = $user; } public function hello() { return 'Hello,' . $this->user->name . '!'; } }
依赖注入的对象参数支持多个,并且和顺序无关。
绑定
依赖注入的类统一由容器进行管理,大多数情况下是在自动绑定并且实例化的。不过你可以随时进行手动绑定类到容器中(通常是在服务类的register方法中进行绑定),支持多种绑定方式。
绑定类标识
可以对已有的类库绑定一个标识(唯一),便于快速调用。
// 绑定类库标识 $this->app->bind('cache', 'think\Cache');
或者使用助手函数
// 绑定类库标识 bind('cache', 'think\Cache');
绑定的类标识可以自己定义(只要不冲突)。
绑定闭包
可以绑定一个闭包到容器中
bind('sayHello', function ($name) { return 'hello,' . $name; });
绑定实例
也可以直接绑定一个类的实例
$cache = new think\Cache; // 绑定类实例 bind('cache', $cache);
绑定至接口实现
对于依赖注入使用接口类的情况,我们需要告诉系统使用哪个具体的接口实现类来进行注入,这个使用可以把某个类绑定到接口
// 绑定think\LoggerInterface接口实现到think\Log bind('think\LoggerInterface','think\Log');
使用接口作为依赖注入的类型
<?php namespace app\index\controller; use think\LoggerInterface; class Index { public function hello(LoggerInterface $log) { $log->record('hello,world!'); } }
批量绑定
在实际应用开发过程,不需要手动绑定,我们只需要在应用目录下面定义provider.php文件(返回一个数组),系统会自动批量绑定类库到容器中。
return [ 'route' => \think\Route::class, 'session' => \think\Session::class, 'url' => \think\Url::class, ];
绑定标识调用的时候区分大小写,系统已经内置绑定了核心常用类库,无需重复绑定
系统内置绑定到容器中的类库包括
系统类库 | 容器绑定标识 |
---|---|
think\App | app |
think\Cache | cache |
think\Config | config |
think\Cookie | cookie |
think\Console | console |
think\Db | db |
think\Debug | debug |
think\Env | env |
think\Event | event |
think\Http | http |
think\Lang | lang |
think\Log | log |
think\Middleware | middleware |
think\Request | request |
think\Response | response |
think\Filesystem | filesystem |
think\Route | route |
think\Session | session |
think\Validate | validate |
think\View | view |
解析
使用app助手函数进行容器中的类解析调用,对于已经绑定的类标识,会自动快速实例化
$cache = app('cache');
带参数实例化调用
$cache = app('cache',['file']);
对于没有绑定的类,也可以直接解析
$arrayItem = app('org\utils\ArrayItem');
调用和绑定的标识必须保持一致(包括大小写)
容器中已经调用过的类会自动使用单例,除非你使用下面的方式强制重新实例化。
// 每次调用都会重新实例化 $cache = app('cache', [], true);
对象化调用
使用app助手函数获取容器中的对象实例(支持依赖注入)。
$app = app(); // 判断对象实例是否存在 isset($app->cache); // 注册容器对象实例 $app->cache = think\Cache::class; // 获取容器中的对象实例 $cache = $app->cache;
也就是说,你可以在任何地方使用app()方法调用容器中的任何类,但大多数情况下面,我们更建议使用依赖注入。
// 调用配置类 app()->config->get('app_name'); // 调用session类 app()->session->get('user_name');
自动注入
容器主要用于依赖注入,依赖注入会首先检查容器中是否注册过该对象实例,如果没有就会自动实例化,然后自动注入,例如:
我们可以给路由绑定模型对象实例
Route::get('user/:id','index/Index/hello') ->model('\app\index\model\User');
然后在操作方法中自动注入User模型
<?php namespace app\index\controller; use app\index\model\User; class Index { public function hello(User $user) { return 'Hello,'.$user->name; } }
自定义实例化
容器中的对象实例化支持自定义,可以在你需要依赖注入的对象中增加__make方法定义,例如:
如果你希望User模型类在依赖注入的时候 使用自定义实例化的方式,可以用下面的方法。
<?php namespace app\index\model; use think\Model; use think\db\Query; class User extends Model { public static function __make(Query $query) { return (new self())->setQuery($query); } }
容器对象回调机制
容器中的对象实例化之后,支持回调机制,利用该机制可以实现诸如注解功能等相关功能。
你可以通过resolving方法注册一个全局回调
Container::getInstance()->resolving(function($instance,$container) { // ... });
回调方法支持两个参数,第一个参数是容器对象实例,第二个参数是容器实例本身。
或者单独注册一个某个容器对象的回调
Container::getInstance()->resolving(\think\Cache::class,function($instance,$container) { // ... });