laravel 容器
存放的 是对象、对象的描述(类、接口)或者是提供对象的回调,通过这种容器,我们得以实现许多高级的功能,其中最常提到的,就是 “解耦”、“依赖注入(DI)”。
服务容器的使用
多个超人多种能力
我们不应该手动在 “超人” 类中固化了他的 “超能力” 初始化的行为,而转由外部负责,由外部创造超能力模组、装置或者芯片等(我们后面统一称为 “模组”),植入超人体内的某一个接口,这个接口是一个既定的,只要这个 “模组” 满足这个接口的装置都可以被超人所利用,可以提升、增加超人的某一种能力。这种由外部负责其依赖需求的行为,我们可以称其为 “控制反转(IoC)”。
IoC设计思想:将依赖关系动态注入
工厂模式,依赖转移
工厂模式的缺点就是:
接口未知(即没有一个很好的契约模型,关于这个我马上会有解释)、产生对象类型单一。
总之就是,还是不够灵活。
虽然如此,工厂模式依旧十分优秀,并且适用于绝大多数情况。
class Superman4
{
protected $power;
public function __construct(array $modules)
{
// 1 单个能力,依赖
// $this->power = new Power(999, 100);
// 2 多个能力,依赖
$this->power = new Flight(9, 100);
// $this->power = new Force(45);
// $this->power = new Shot(99, 50, 2);
$this->power = [
new Force(45),
new Shot(99, 50, 2),
];
// 3 多个能力,工厂模式(依赖)
$factory = new SuperModuleFactory();
// 和之前区别不大,但没有那么多new关键字
$this->power[] = $factory->makeModule('Flight', [9, 100]);
// $this->power = $factory->makeModule('Force', [45]);
// $this->power = $factory->makeModule('Shot', [99, 50, 2]);
$this->power[] = array(
$factory->makeModule('Force', [45]),
$factory->makeModule('Shot', [99, 50, 2])
);
// 4 工厂模式更改(依赖)
// 由原来对多个外部的依赖变成了对一个 “工厂” 的依赖
$factory = new SuperModuleFactory();
foreach ($modules as $moduleName => $moduleOptions){
$this->power[] = $factory->makeModule($moduleName, $moduleOptions);
}
}
}
如果需要增加能力,还需要更改工厂类 ,噩梦般的感受!。。。
class SuperModuleFactory
{
public function makeModule($moduleName, $options)
{
switch ($moduleName) {
case 'Fight':
return new Fight($options[0], $options[1]);
case 'Force':
return new Force($options[0]);
case 'Shot':
return new Shot($options[0], $options[1], $options[2]);
// case 'more': .......
// case 'and more': .......
// case 'and more': .......
// case 'oh no! its too many!': .......
}
}
}
其实灵感就差一步!你可能会想到更为灵活的办法!
对,下一步就是我们今天的主要配角 —— DI (依赖注入)
由于对超能力模组的需求不断增大,我们需要集合整个世界的高智商人才,一起解决问题,不应该仅仅只有几个工厂垄断负责。
不过高智商人才们都非常自负,认为自己的想法是对的,创造出的超能力模组没有统一的接口,自然而然无法被正常使用。这时我们需要提出一种契约,这样无论是谁创造出的模组,都符合这样的接口
定义 规范/契约/接口
interface SuperModuleInterface
{
/**
* 超能力激活方法
*
* 任何一个超能力都得有该方法,并拥有一个参数
*@param array $target 针对目标,可以是一个或多个,自己或他人
*/
public function activate(array $target);
}
实现统一种方法且不同功能(或特性)的时候,会存在很多的类(class),这时候就需要有一个契约,即接口
可以被随时替换却不会产生影响的接口
只要不是由内部生产(比如初始化、构造函数 __construct 中通过工厂方法、自行手动 new 的),
而是由外部以参数或其他形式注入的,都属于依赖注入(DI)
依赖注入案例(手动创建并注入):
// 超能力模组
$superModule = new XPower;
// 初始化一个超人,并注入一个超能力模组依赖 (手动注入)
$superMan = new Superman($superModule);
低效率产出(手动)怎么行? 需要自动化!
这种更为高级的工厂,就是工厂模式的升华 —— IoC 容器
class main
{
/**
* main constructor.
* 创建一个实例的同时解决其依赖关系,并且更加灵活。当有新的需求,只需另外绑定一个“生产脚本”即可。
*/
public function __construct()
{
// 创建一个容器
$container = new Container;
/**
* 通过注册、绑定的方式向容器中添加一段可以被执行的回调(可以是匿名函数、非匿名函数、类的方法)
* 作为生产一个类的实例的 脚本 ,
* 只有在真正的 生产(make) 操作被调用执行时,才会触发。
*/
// 向 容器 添加 超人的生产脚本
// 缺点,手动提供超人的模组参数 (更高级的Ioc容器可以自动化处理 - 反射实现)
$container->bind('superman', function ($container, $moduleName){
// moduleName 参数
return new Superman($container->make($moduleName));
});
// 向 容器 添加 x射线能力的生产脚本
$container->bind('xpower', function ($container){
return new XPower;
});
// 向 容器 添加 炸弹能力的生产脚本
$container->bind('ultrabomb', function ($container){
return new UltraBomb;
});
// 开始启动生产
$superman1 = $container->make('superman', 'xpower');
$superman2 = $container->make('superman', 'ultrabomb');
$superman3 = $container->make('superman', 'xpower');
// ...随意添加
}
}
自动注入参数 - 反射
手动提供超人所需要的模组参数,但真正的 IoC
容器会根据类的依赖需求,自动在注册、绑定的一堆实例中搜寻符合的依赖需求,并自动注入到构造函数参数中去。
Laravel框架的服务容器正是这么做的。
这种自动搜寻依赖需求的功能,是通过反射(Reflection)实现的
功能模块
比如 Route(路由)、Eloquent ORM(数据库 ORM 组件)、Request(请求)以及 Response(响应)等,
都是与核心无关的类模块提供
这些类从注册到实例化,都是 Laravel 的服务容器负责的
服务提供者
一个类要被容器所能够提取,必须要先注册至这个容器
一个类需要绑定、注册至容器中,才能被“制造”
例如
需要某个服务,就得先注册、绑定这个服务到容器,
那么提供服务并绑定服务至容器的东西,就是服务提供者(Service Provider)。
服务提供者
主要分为两个部分,register(注册) 和 boot(引导、初始化)
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
* 引导、初始化
*
* @return void
*/
public function boot()
{
//
}
/**
* Register any application services.
* 向容器注册"脚本"
* 注册部分如果存在对未知事物的依赖,写在boot中
*
* @return void
*/
public function register()
{
//
}
}
route静态访问
route类 get,post,any 等都不是静态(static)方法,
却能以静态方法访问
# vendor/laravel/framework/src/Illuminate/Routing/Router.php
/**
* Register a new GET route with the router.
*
* @param string $uri
* @param \Closure|array|string|null $action
* @return \Illuminate\Routing\Route
*/
public function get($uri, $action = null)
{
return $this->addRoute(['GET', 'HEAD'], $uri, $action);
}
Route::get('/', function() {
// bla bla bla...
});
使用了 门面(Facade)
模拟一个类,提供一个静态魔术方法__callStatic,
并将该静态方法映射到真正的方法上。
Route 类实际上是 Illuminate\Support\Facades\Route 通过 class_alias()
函数创造的别名而已,这个类被定义在文件
vendor/laravel/framework/src/Illuminate/Support/Facades/Route.php
namespace Illuminate\Support\Facades;
/**
* @see \Illuminate\Routing\Router
*/
class Route extends Facade {
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'router';
}
}
这个类继承了一个叫做 Facade 的类
getFacadeAccessor 方法返回了一个 route,这是什么意思呢?
这个值被一个 ServiceProvider 注册了真正的路由类 ????