容器
容器类提供了框架类(这里通常指动态类)的依赖管理和依赖注入,容器类功能本身是由think\Container类完成,但实际在应用的运行过程中,都是委托think\App类来进行容器类的管理。所以,大部分情况下,你都无需直接操作容器类think\Container本身。
新版的容器支持PSR-11规范,容器类的功能特性主要包括:
绑定类、对象实例、接口到容器
创建类的实例(存在则直接获取)
容器对象绑定别名
支持容器对象(实例化)回调
获取容器对象实例
删除容器中的对象实例
依赖注入实现
调用容器对象实例的方法(或者闭包)
提供容器对象的ArrayAccess支持
获取容器对象
获取容器中对象的最简单方法就是通过依赖注入,如果你要手动获取的话,事实上在容器中获取对象实例仅仅需要统一使用一个app助手函数就可以了,你不需要每次手动new一个对象实例,要获取的对象实例要么已经存在于容器中,要么会在你第一次调用的时候自动实例化。
// 获取缓存对象实例
$cache = app('cache');
cache是容器内置给think\Cache类绑定的一个容器标识,当然,你完全可以使用完整类名的方式
$test = app('think\Cache');
获取当前应用对象实例,只需要调用一个没有任何参数的app函数即可
$app = app();
如果你需要对某个容器对象的实例化进行自定义,可以定义一个静态的__make方法,在该方法的参数中可以支持依赖注入。
namespace app;
class Cookie
{
/**
* 构造方法
* @access public
*/
public function __construct(Request $request, array $config )
{
$this->request = $request;
$this->config = $config;
}
public static function __make(Request $request, Config $config)
{
return new static($request, $config->get('cookie'));
}
}
如果你直接实例化app\Cookie类,必须传入request参数以及config参数,但定义了__make后,就可以通过依赖注入或者app方法自动实例化Cookie类。
依赖注入
在应用开发的过程中,有很多的场景支持直接指定方法参数的类型为某个对象,在调用该方法的时候就会自动进行实例化,也就是通常所说的依赖注入。
支持使用依赖注入的场景通常包括(但不限于):
控制器架构方法;
控制器操作方法;
路由的闭包定义;
事件类的执行方法;
中间件的执行方法;
你可以像下面这样,在控制器的架构函数和操作方法中进行灵活的依赖注入。
<?php
namespace app\controller;
use think\Cache;
use think\Request;
class Index
{
protected $request;
public function __construct(Request $request)
{
$this->request = $request;
}
public function test(Cache $cache)
{
$cache->set('name', 'test');
return 'This is ' . $this->request->action() . '!';
}
}
事实上你可以在方法中依赖注入多个对象参数,并且和顺序无关。
如果你需要对自己的类库支持依赖注入,只需要在调用的时候使用invoke方法即可,例如
class Foo
{
public function __construct(Bar $bar)
{
}
}
如果使用容器来实例化的话,可以自动进行依赖注入。
// 实例化Foo对象,并支持依赖注入
$foo = invoke('Foo');
如果要对某个方法支持依赖注入,可以使用
class Foo
{
public function bar(Bar $bar)
{
// ...
}
}
// 调用Foo对象的bar方法,支持依赖注入
$result = invoke(['Foo', 'bar']);
服务提供者
容器中的对象都是可以提前注入的,所以很容易替换成另外一个实现。你可以通过服务提供者来指定或者替换容器对象,例如,我们在app\provider.php文件中定义如下:
use app\Request;
return [
'think\Request' => Request::class,
];
作用其实是把容器中的think\Request对象替换为app\Request对象,但实际上,你仍然只需要和往常一样操作容器中的think\Request类即可。
namespace app;
use think\Request;
class Index
{
public function index(Request $request)
{
}
}
这里的$request对象其实已经是一个app\Request对象实例,但我们依赖注入的其实是think\Request类,没错,但这并不是什么魔法,不过是一种叫做容器绑定的功能而已。
学会了这招,你就可以对容器中的对象进行偷天换日。
门面
为了方便单元测试和长连接使用,核心框架没有任何的静态类,系统给核心常用类库都定义了门面(Facade),其作用可以简单的理解为给类的动态方法调用提供了静态代理,门面对象操作的每个对象都是容器中的对象实例(单例)。
你在官方手册中或者本书中如果看到核心类库使用了静态方法,都是使用了门面操作的,在使用之前务必引入相关的门面类,而不是实际的动态类。
很典型的例子是,如果你看到下面的代码,就必须明白这里的Db类其实是think\facade\Db而非think\Db,类似的问题后面不会重复说明。
Db::name('user')->find();
但所有的依赖注入使用的类必须是实际的动态类或者接口,不能使用门面类。关于依赖注入的细节后面再讲,虽然门面对象和依赖注入都是从容器中获取对象,不过如果从性能上说,依赖注入略有优势。
不要给模型定义门面,是不符合规范的,况且模型的查询操作本身都是静态方法调用的。
发表评论