我独自走进 Laravel5.5 的❤(六)
服务提供者是怎么回事?一起来看看吧。
2018-11-09
一、用法
首先,看个小例子。
因为用法网上有很多例子,这里就简单点。
在项目下php artisan make:provider FunnyServiceProvider
编辑 App\Providers\FunnyServiceProvider.php
public function register()
{
//字符串:别称,闭包:注册的代码
$this->app->singleton('funny', function ($app) {
return new Funny();
});
$this->app->bind('funny', function ($app) {
return new Funny();
});
$obj = new Funny();
$this->app->instance('funny', $obj);
// 上面三个都一个意思
// 语境绑定(context)
// $this->app->when()->needs()->give();
}
然后在类中使用
class PublicController extends Controller
{
public function hello()
{
$obj = app('funny');
$res = $obj->fun();
return $res;
}
}
二、源码解析(看看服务在哪里注入的)
1.当http请求进入框架
//生成一个 Illuminate\Foundation\Application 实例
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
//使用实例处理请求
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
- 关于Illuminate\Foundation\Http\Kernel::class->handle()
public function handle($request)
{
try {
$request->enableHttpMethodParameterOverride();//这个看下面的2.1
$response = $this->sendRequestThroughRouter($request);//这个看下面的2.2
} catch (Exception $e) {
$this->reportException($e);
$response = $this->renderException($request, $e);
} catch (Throwable $e) {
$this->reportException($e = new FatalThrowableError($e));
$response = $this->renderException($request, $e);
}
$this->app['events']->dispatch(
new Events\RequestHandled($request, $response)
);
return $response;
}
2.1 关于enableHttpMethodParameterOverride()
//这个静态函数会在处理post请求的时候将请求变量覆盖的flag设置为true,可以设置delete或者put这些方法
public static function enableHttpMethodParameterOverride()
{
self::$httpMethodParameterOverride = true;
}
//为了加深理解,我们可以看一下Symfony\Component\HttpFoundation\Request.php
//可以发现
protected static $httpMethodParameterOverride = false;
//这是一个初始化值为false的属性
//再看Request处理请求方法的函数
public function getMethod()
{
if (null === $this->method) {
$this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET'));
if ('POST' === $this->method) {
if ($method = $this->headers->get('X-HTTP-METHOD-OVERRIDE')) {
$this->method = strtoupper($method);
} elseif (self::$httpMethodParameterOverride) {
$this->method = strtoupper($this->request->get('_method', $this->query->get('_method', 'POST')));
}
}
}
return $this->method;
}
//通过getMethod(),相信你看出了个一二来了,如果httpMethodParameterOverride为真,会将_method覆盖post请求方法。
2.2关于sendRequestThroughRouter()
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
$this->bootstrap();//这个看下面的2.2.1
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
//1.无论按源码来看还是按处理逻辑来看或者是从函数名字来看,这个函数都理应起到一个过滤request的作用。这一点,从return可以看出,这个函数会将request倒进middleware池里,被路由注册的中间件轮过之后再返回。
//2.然鹅,其实那句看似平凡的$this->bootstrap()才这个函数的精华所在。
2.2.1关于bootstrap()
public function bootstrap()
{
if (! $this->app->hasBeenBootstrapped()) {
$this->app->bootstrapWith($this->bootstrappers());//这个看下面的2.2.2
}
}
//这是什么?如下:
array (size=6)
0 => string 'Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables' (length=56)
1 => string 'Illuminate\Foundation\Bootstrap\LoadConfiguration' (length=49)
2 => string 'Illuminate\Foundation\Bootstrap\HandleExceptions' (length=48)
3 => string 'Illuminate\Foundation\Bootstrap\RegisterFacades' (length=47)
4 => string 'Illuminate\Foundation\Bootstrap\RegisterProviders' (length=49)
5 => string 'Illuminate\Foundation\Bootstrap\BootProviders' (length=45)
//内心颤抖了一下,这几位难不成就是女娲、夸父、孙行者、玉皇大帝、菩萨,和如来吗?
//这里按道理便是在app中引入各种环境变量、配置信息、异常处理机制、注册门面、绑定服务、启动服务?
//不急,接着看。
我在 FunnyServiceProvider中十分灵性地打了一个断点,然后在$this->app->bootstrapWith($this->bootstrappers());下面一行也打了个断点,你猜怎么着。
图一:
图二:
结果它会先跑到图一,然后再跑到图二。所以,很明显,在 bootsrap() 中会注册所有的服务。也就是说,在中间件轮 request 之前,lara 就会注册所有的服务。
2.2.2 关于Illuminate\Foundation\Application::class->bootstrapWith()
public function bootstrapWith(array $bootstrappers)
{
$this->hasBeenBootstrapped = true;//此属性表示应用是否已经启动
foreach ($bootstrappers as $bootstrapper) {
$this['events']->fire('bootstrapping: '.$bootstrapper, [$this]);
$this->make($bootstrapper)->bootstrap($this);
$this['events']->fire('bootstrapped: '.$bootstrapper, [$this]);
}
}
// ok ,我们来看一下保时捷的发动机。
// $this['events'] 这是啥玩意?
//这个就是 Illuminate\Events\Dispatcher::class
//这个类可以通过其 fire() 方法来启动容器实例中的所有服务,什么监听器啊,事件啊,队列解析器啊等等与容器相关联的功能。总的来说,就是 lara 跑起来了。
//再来看一下 fire() 本真
public function fire($event, $payload = [], $halt = false)
{
return $this->dispatch($event, $payload, $halt);
}
//同志们,dispatch()应该是老相识了,只不过知人知面不知心
//ok ,再来看看dispatch()是个什么鬼
public function dispatch($event, $payload = [], $halt = false)
{
// When the given "event" is actually an object we will assume it is an event
// object and use the class as the event name and this event itself as the
// payload to the handler, which makes object based events quite simple.
//当给定的“事件”实际上是一个对象时,我们将假设它是一个事件对象
//并使用该类作为事件名称,并将此事件本身作为
//处理器的有效负载,使对象基于事件非常简单。
list($event, $payload) = $this->parseEventAndPayload(
$event, $payload
);
if ($this->shouldBroadcast($payload)) {
$this->broadcastEvent($payload[0]);
}
$responses = [];
foreach ($this->getListeners($event) as $listener) {
$response = $listener($event, $payload);
// If a response is returned from the listener and event halting is enabled
// we will just return this response, and not call the rest of the event
// listeners. Otherwise we will add the response on the response list.
//如果监听器返回响应,并启用了事件暂停($halt==true)
//我们将返回此响应,并且不会调用剩下的事件监听器
//否则,我们将在响应列表中添加响应。
if ($halt && ! is_null($response)) {
return $response;
}
// If a boolean false is returned from a listener, we will stop propagating
// the event to any further listeners down in the chain, else we keep on
// looping through the listeners and firing every one in our sequence.
//如果从监听器返回布尔值false,我们将停止传播
//事件发送到链中的任何其他监听器,否则我们继续
//循环遍历侦听器并按顺序触发每个侦听器。
if ($response === false) {
break;
}
$responses[] = $response;
}
return $halt ? null : $responses;
}
2018-11-12
因为最近项目做完了,所以今天还是有时间写文章的,嘻嘻,那就接着上星期的来吧。
3.1
虽然在我们严谨的推理下,我们知道了lara的服务按理应该是在Illuminate\Foundation\Application::class->bootstrapWith()这个函数里面被正式注册到实例的。
但是,我们自己注册的服务被哪个类所控制的呢?
在 2.2.1 中其实有一个是答案
我胡乱一猜,应该是 Illuminate\Foundation\Bootstrap\RegisterProviders 吧,按字面意思嘛。
但是,我们知道,我们作为求知者是不能带有强烈的主观意识的。于是我客观地打了几个断点,顺便看了一下被分发的事件的负载是啥。
图三:
果不其然,当 lara 跑到
debug_dispacher_196>>>>>>>>evnt>>>>>>>>
B:\projects\web\php\hello_laravel_2\vendor\laravel\framework\src\Illuminate\Events\Dispatcher.php:193:string 'bootstrapping: Illuminate\Foundation\Bootstrap\RegisterProviders' (length=64)
lara 会跳到 2.2.1 中的的图一,我们在 2.2.1 已经知道 lara 是是循环了几个框架控制类。所以,当 lara 注册到
Illuminate\Foundation\Bootstrap\RegisterProvider.php 时会注册自定义 provider。
为什么?
如果在高中课堂上,老师把一个知识点讲得比较深入,我还去问一个细节问题的为什么,我便会被老师和同学笑话。以前觉得愤愤不平,现在想来的确挺可笑的。再深入,应当自己去探索与思索,而不是再去靠别人告诉你了。
那么好的,我们继续看一下为啥子。
打开 Illuminate\Foundation\Bootstrap\RegisterProvider.php 我们可以看到
public function bootstrap(Application $app)
{
$app->registerConfiguredProviders();
}
索性 control B 一下 registerConfiguredProviders,我们可以发现它是非常核心的一个接口类 Application 声明的方法,自然它的实现应当在
Illumidation\Foundation\Application:class 中被实现。于是,我满怀自信地打开了 Illumidation\Foundation\Application.php,果不其然。
/**
* Register all of the configured providers.
*
* @return void
*/
/**
* 注册绑定所有的设定的服务.
*在英文中,configured 一般指的是可以被使用配置的,也就是说可以被使用者自行的配置的(自定义的),也就是非官方的服务。
* @return void
*/
public function registerConfiguredProviders()
{
$providers = Collection::make($this->config['app.providers'])
->partition(function ($provider) {
return Str::startsWith($provider, 'Illuminate\\');//为啥两斜杠? 是转义字符哦
});
$providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);
(new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
->load($providers->collapse()->toArray());
}
这个明显是个操作函数,于是我打印了一下 $this->config['app.providers'],这个的确是我们想象中的 config/app.php 中的 providers,突然感觉自己好聪明?不信?有图有真相
打印结果如下:
B:\projects\web\php\hello_laravel_2\vendor\laravel\framework\src\Illuminate\Foundation\Application.php:558:
array (size=27)
0 => string 'Illuminate\Auth\AuthServiceProvider' (length=35)
1 => string 'Illuminate\Broadcasting\BroadcastServiceProvider' (length=48)
2 => string 'Illuminate\Bus\BusServiceProvider' (length=33)
3 => string 'Illuminate\Cache\CacheServiceProvider' (length=37)
4 => string 'Illuminate\Foundation\Providers\ConsoleSupportServiceProvider' (length=61)
5 => string 'Illuminate\Cookie\CookieServiceProvider' (length=39)
6 => string 'Illuminate\Database\DatabaseServiceProvider' (length=43)
7 => string 'Illuminate\Encryption\EncryptionServiceProvider' (length=47)
8 => string 'Illuminate\Filesystem\FilesystemServiceProvider' (length=47)
9 => string 'Illuminate\Foundation\Providers\FoundationServiceProvider' (length=57)
10 => string 'Illuminate\Hashing\HashServiceProvider' (length=38)
11 => string 'Illuminate\Mail\MailServiceProvider' (length=35)
12 => string 'Illuminate\Notifications\NotificationServiceProvider' (length=52)
13 => string 'Illuminate\Pagination\PaginationServiceProvider' (length=47)
14 => string 'Illuminate\Pipeline\PipelineServiceProvider' (length=43)
15 => string 'Illuminate\Queue\QueueServiceProvider' (length=37)
16 => string 'Illuminate\Redis\RedisServiceProvider' (length=37)
17 => string 'Illuminate\Auth\Passwords\PasswordResetServiceProvider' (length=54)
18 => string 'Illuminate\Session\SessionServiceProvider' (length=41)
19 => string 'Illuminate\Translation\TranslationServiceProvider' (length=49)
20 => string 'Illuminate\Validation\ValidationServiceProvider' (length=47)
21 => string 'Illuminate\View\ViewServiceProvider' (length=35)
22 => string 'App\Providers\AppServiceProvider' (length=32)
23 => string 'App\Providers\AuthServiceProvider' (length=33)
24 => string 'App\Providers\EventServiceProvider' (length=34)
25 => string 'App\Providers\RouteServiceProvider' (length=34)
26 => string 'App\Providers\FunnyServiceProvider' (length=34)
>>>>>>>>>>>>>> providers_1 >>>>>>>>>>>>>>
B:\projects\web\php\hello_laravel_2\vendor\laravel\framework\src\Illuminate\Foundation\Application.php:560:
object(Illuminate\Support\Collection)[21]
protected 'items' =>
array (size=22)
0 => string 'Illuminate\Auth\AuthServiceProvider' (length=35)
1 => string 'Illuminate\Broadcasting\BroadcastServiceProvider' (length=48)
2 => string 'Illuminate\Bus\BusServiceProvider' (length=33)
3 => string 'Illuminate\Cache\CacheServiceProvider' (length=37)
4 => string 'Illuminate\Foundation\Providers\ConsoleSupportServiceProvider' (length=61)
5 => string 'Illuminate\Cookie\CookieServiceProvider' (length=39)
6 => string 'Illuminate\Database\DatabaseServiceProvider' (length=43)
7 => string 'Illuminate\Encryption\EncryptionServiceProvider' (length=47)
8 => string 'Illuminate\Filesystem\FilesystemServiceProvider' (length=47)
9 => string 'Illuminate\Foundation\Providers\FoundationServiceProvider' (length=57)
10 => string 'Illuminate\Hashing\HashServiceProvider' (length=38)
11 => string 'Illuminate\Mail\MailServiceProvider' (length=35)
12 => string 'Illuminate\Notifications\NotificationServiceProvider' (length=52)
13 => string 'Illuminate\Pagination\PaginationServiceProvider' (length=47)
14 => string 'Illuminate\Pipeline\PipelineServiceProvider' (length=43)
15 => string 'Illuminate\Queue\QueueServiceProvider' (length=37)
16 => string 'Illuminate\Redis\RedisServiceProvider' (length=37)
17 => string 'Illuminate\Auth\Passwords\PasswordResetServiceProvider' (length=54)
18 => string 'Illuminate\Session\SessionServiceProvider' (length=41)
19 => string 'Illuminate\Translation\TranslationServiceProvider' (length=49)
20 => string 'Illuminate\Validation\ValidationServiceProvider' (length=47)
21 => string 'Illuminate\View\ViewServiceProvider' (length=35)
>>>>>>>>>>>>>> providers_2 >>>>>>>>>>>>>>
B:\projects\web\php\hello_laravel_2\vendor\laravel\framework\src\Illuminate\Foundation\Application.php:562:
object(Illuminate\Support\Collection)[31]
protected 'items' =>
array (size=5)
22 => string 'App\Providers\AppServiceProvider' (length=32)
23 => string 'App\Providers\AuthServiceProvider' (length=33)
24 => string 'App\Providers\EventServiceProvider' (length=34)
25 => string 'App\Providers\RouteServiceProvider' (length=34)
26 => string 'App\Providers\FunnyServiceProvider' (length=34)
>>>>>>>>>>>> debug_Application_559 >>>>>>>>>>>>
我们看到 providers_2,FunnyServiceProvider 赫然纸上。是不是觉得很神奇,还学会了新技能
Collection::make($arr = [])
->partition(function ($item) {
return Str::startsWith($item, $str);
});
//顺便,我们来学习一下 lara 优秀的代码结构,看一下 partition()
public function partition($callback)
{
$partitions = [new static, new static];//static: 传入的对象
$callback = $this->valueRetriever($callback);//这个看下面的3.1.1
foreach ($this->items as $key => $item) {
$partitions[(int) ! $callback($item, $key)][$key] = $item;
//如果闭包条件判断 item 返回 false 的放到 offset 为 0 子数组,否则放 1 中。
}
return new static($partitions);
}
//我们可以看到,在 lara 中也是随处可见 foreach,但是仔细理性分析一下,我们似乎难以寻找到内嵌的 foreach。所以为啥 lara 每个请求都要跑那么多行代码却速度飞快?代码中蕴含的逻辑性、功能性铸就了强大的 lara。
通过 partition() 中的闭包可以将 $arr 分割成两个特征不同的数组。
3.1.1关于valueRetriever()
protected function valueRetriever($value)
{
if ($this->useAsCallable($value)) {
return $value;
}
return function ($item) use ($value) {
return data_get($item, $value);
};
}
//这又是啥玩意?
//两底层函数
protected function useAsCallable($value)
{
return ! is_string($value) && is_callable($value);
//判断 value 是否不为字符串并且可以被被当作函数调用,就是判断是不是个闭包?
}
function data_get($target, $key, $default = null)
{
if (is_null($key)) {
return $target;
}
$key = is_array($key) ? $key : explode('.', $key); //判断 key 是否为数组,并根据"."分割数组
while (! is_null($segment = array_shift($key))) { //将第一个子数组扔了(在"."左边存在值时)
if ($segment === '*') { //如果key 形如 "*.abc"
if ($target instanceof Collection) { //如果对象为集合
$target = $target->all();
} elseif (! is_array($target)) { //如果对象为数组
return value($default);
}
$result = Arr::pluck($target, $key); //从对象中取出相对应该的值
return in_array('*', $key) ? Arr::collapse($result) : $result;
}
if (Arr::accessible($target) && Arr::exists($target, $segment)) {
$target = $target[$segment];
} elseif (is_object($target) && isset($target->{$segment})) {
$target = $target->{$segment};
} else {
return value($default);
}
}
return $target;
}
//这个不结合上下文,是真的很难理解。(对我而言。。。)
//来看个例子
foreach ((array) data_get($composer, 'autoload.psr-4') as $namespace => $path) {
foreach ((array) $path as $pathChoice) {
if (realpath(app_path()) == realpath(base_path().'/'.$pathChoice)) {
return $this->namespace = $namespace;
}
}
}
//所以这是个根据”点“作为标记从数组或对象中获取元素的方法。
3.2 加载
当然,在3.1中只是将需要加载的 providers 列出来而已。
真正的加载其实是
(new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
->load($providers->collapse()->toArray());
<--------------------- End ---------------------->
以上便是ioc启动服务提供者涉及到的流程,如果有啥不对,请指出,互相学习。
至此,
致敬知识。
本作品采用《CC 协议》,转载必须注明作者和本文链接
你这个头像我能看一天
@wanghan :joy:兄dei,佛系的吧
我强迫出来了,为什么这个头像加载不上来,我要薅头发。。。。。。
看这些底层好累
@yuanshang 不怕秃了吗?
@万能付博 不是挺好玩的吗,嘻嘻
写的不错 继续哦
@Echos hoho,好滴
@Wiki_Yao 兄弟,能换个头像不,脑袋晕
@xiaobei 兄dei 你可以别关注我头像不
今天看了facades感觉挺棒的。