对Laravel服务提供者的认识
参考网址:
服务提供者
(如有哪里分析不对的地方,请各位大佬指正)
环境
# php artisan --version
bootLaravel Framework 8.83.27
# php -v
PHP 7.4.27 (cli) (built: Dec 16 2021 22:42:18) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
with Zend OPcache v7.4.27, Copyright (c), by Zend Technologies
with Xdebug v2.9.2, Copyright (c) 2002-2020, by Derick Rethans
简介
服务提供者是所有 Laravel 应用程序的引导中心。你的应用程序,以及通过服务器引导的 Laravel 核心服务都是通过服务提供器引导。
源码调用流程
首先,请求到达index.php
文件,加载bootstrap/app.php
文件,实例化容器类。
// 从容器中解析出Kernel类,在文件bootstrap/app.php中已绑定到容器类中
$kernel = $app->make(Kernel::class);
// Request::capture()获取请求对象
$response = $kernel->handle(
$request = Request::capture()
)->send();
查看handle()方法
这里调用的handle()
方法,是Illuminate\Foundation\Http\Kernel
类的handle()
方法,查看该类
// handle
public function handle($request)
{
try {
$request->enableHttpMethodParameterOverride();
// 重点看这行
$response = $this->sendRequestThroughRouter($request);
} catch (Throwable $e) {
$this->reportException($e);
$response = $this->renderException($request, $e);
}
$this->app['events']->dispatch(
new RequestHandled($request, $response)
);
return $response;
}
// sendRequestThroughRouter方法详细
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
// 重点在这行
$this->bootstrap();
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
// bootstrap方法详细
public function bootstrap()
{
// 初始化时$this->app->hasBeenBootstrapped()为false
if (!$this->app->hasBeenBootstrapped()) {
/**
* 看这行
* 参数$this->bootstrappers()为数组。
* protected function bootstrappers()
* {
* return $this->bootstrappers;
* }
*
* $this->bootstrappers为类Kernel的属性,值如下
* protected $bootstrappers = [
* \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
* \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
* \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
* \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
* \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
* \Illuminate\Foundation\Bootstrap\BootProviders::class,
* ];
* $this->app实际是Illuminate\Foundation\Application类的实例,调用该类的bootstrapWith()方法
*/
$this->app->bootstrapWith($this->bootstrappers());
}
}
查看bootstrapWith()方法
public function bootstrapWith(array $bootstrappers)
{
$this->hasBeenBootstrapped = true;
/**
* $bootstrappers = [
* \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
* \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
* \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
* \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
* \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
* \Illuminate\Foundation\Bootstrap\BootProviders::class,
* ];
*/
foreach ($bootstrappers as $bootstrapper) {
$this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]);
// 从服务容器中解析出对应实例,并调用bootstrap方法
$this->make($bootstrapper)->bootstrap($this);
$this['events']->dispatch('bootstrapped: '.$bootstrapper, [$this]);
}
}
该方法循环数组$bootstrappers
的每一个元素,并调用其bootstrap()
方法。重点看\Illuminate\Foundation\Bootstrap\RegisterProviders::class
类的bootstrap()
方法
RegisterProviders::class类
查看该类的bootstrap()
方法
这是服务提供者加载的核心方法(重要!)
public function bootstrap(Application $app)
{
// 这里实际调用的是Application类的registerConfiguredProviders()方法
$app->registerConfiguredProviders();
}
// 该方法从配置文件config/app.php中读取providers配置,调用集合的partittion方法,根据条件分组
public function registerConfiguredProviders()
{
// 从容器中解析出别名为config的实例,实际是illuminate/config/Repository类
$providers = Collection::make($this->make('config')->get('app.providers'))
->partition(function ($provider) {
return strpos($provider, 'Illuminate\\') === 0;
});
$providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);
/**
* 查看bootstrap/cache目录下是否存在services.php文件,如果存在,则require该文件
* $this->getCachedServicesPath()返回缓存文件位置
* public function getCachedServicesPath()
* {
* return $this->normalizeCachePath('APP_SERVICES_CACHE', 'cache/services.php');
* }
*
* protected function normalizeCachePath($key, $default)
* {
* 如果.env文件未设置APP_SERVICES_CACHE的值,则调用bootstrapPath方法
* if (is_null($env = Env::get($key))) {
* return $this->bootstrapPath($default);
* }
*
* return Str::startsWith($env, $this->absoluteCachePathPrefixes)
* ? $env
* : $this->basePath($env);
* }
* public function bootstrapPath($path = '')
* { $this->basePath为当前项目根目录 DIRECTORY_SEPARATOR为 / ,$path的值为cache/services.php
* return $this->basePath.DIRECTORY_SEPARATOR.'bootstrap'.($path ? DIRECTORY_SEPARATOR.$path : $path);
* }
*/
(new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
->load($providers->collapse()->toArray());
}
ProviderRepository类
new
时,调用构造函数,将容器对象和文件系统对象传入,并将缓存服务提供者的路径传入。
// 该类的构造函数
public function __construct(ApplicationContract $app, Filesystem $files, $manifestPath)
{
$this->app = $app;
$this->files = $files;
$this->manifestPath = $manifestPath;
}
之后调用load
方法,
public function load(array $providers)
{
/**
* 该方法判断缓存文件是否存在,存在则require文件
public function loadManifest()
{
if ($this->files->exists($this->manifestPath)) {
$manifest = $this->files->getRequire($this->manifestPath);
if ($manifest) {
return array_merge(['when' => []], $manifest);
}
}
}
*/
$manifest = $this->loadManifest();
/**
* shouldRecompile()方法,如果缓存文件不存在,或者缓存文件跟传入的服务提供者数组不一致,则返回true,否则返回false
* return is_null($manifest) || $manifest['providers'] != $providers;
*/
if ($this->shouldRecompile($manifest, $providers)) {
/**
* 如果不一致,或者缓存文件不存在,则解析文件,方法内解析看下面
*/
$manifest = $this->compileManifest($providers);
}
// Next, we will register events to load the providers for each of the events
// that it has requested. This allows the service provider to defer itself
// while still getting automatically loaded when a certain event occurs.
foreach ($manifest['when'] as $provider => $events) {
$this->registerLoadEvents($provider, $events);
}
// 循环providers数组,调用app->register()方法
foreach ($manifest['eager'] as $provider) {
// 调用Application类的register()方法
$this->app->register($provider);
}
// 添加到延迟服务提供者
$this->app->addDeferredServices($manifest['deferred']);
}
compileManifest()方法
/**
* compileManifest方法
*/
protected function compileManifest($providers)
{
// 刷新$manifest的数组结构
// $manifest = ['providers' => $providers, 'eager' => [], 'deferred' => []];
$manifest = $this->freshManifest($providers);
// 然后循环所有服务提供者
foreach ($providers as $provider) {
// 从容器中解析出对应提供者实例
// return new $provider($this->app);
$instance = $this->createProvider($provider);
/**
* 然后调用对应服务提供者的isDeferred()方法,该方法是所有服务提供者父类ServiceProvider的方法
* 这里是延迟服务加载实现的方式,通过实现DeferrableProvider接口,然后判断,如果实现了该接口,则判断为是延迟服务提供者
* public function isDeferred()
* {
* return $this instanceof DeferrableProvider;
* }
*/
if ($instance->isDeferred()) {
// 调用服务提供者的provides方法,将定义的键绑定到该服务提供者
foreach ($instance->provides() as $service) {
$manifest['deferred'][$service] = $provider;
}
$manifest['when'][$provider] = $instance->when();
}
else {
// else就是作为普通服务提供者,在框架初始化时加载
$manifest['eager'][] = $provider;
}
}
// 写入到config/cache/services.php文件中
return $this->writeManifest($manifest);
}
public function register($provider, $force = false)
{
// 如果已经注册过,则直接返回
if (($registered = $this->getProvider($provider)) && ! $force) {
return $registered;
}
// 如果传入的是字符串,则调用resolveProvider()方法,将字符串转换为Provider实例
if (is_string($provider)) {
$provider = $this->resolveProvider($provider);
}
// 这里会调用Provider类的register()方法
$provider->register();
// 如果Provider类有bindings属性,则调用bind()方法
if (property_exists($provider, 'bindings')) {
foreach ($provider->bindings as $key => $value) {
$this->bind($key, $value);
}
}
// 如果Provider类有singletons属性,则调用singleton()方法
if (property_exists($provider, 'singletons')) {
foreach ($provider->singletons as $key => $value) {
$this->singleton($key, $value);
}
}
// 标记为已注册
$this->markAsRegistered($provider);
// If the application has already booted, we will call this boot method on
// the provider class so it has an opportunity to do its boot logic and
// will be ready for any usage by this developer's application logic.
if ($this->isBooted()) {
$this->bootProvider($provider);
}
return $provider;
}
总结
上面我们看到,registerConfiguredProviders()
方法会从配置文件config/app.php
中读取providers
配置,并调用load()
方法,将providers
分为eager
和deferred
两组,然后分别调用register()
方法注册eager
服务提供者,并将deferred
服务提供者添加到延迟服务提供者列表中。
创建服务提供者
所有服务提供者都会继承Illuminate\Support\ServiceProvider
类,并且需要包含一个register()
方法和一个boot()
方法,该方法将会在服务容器中注册服务。在register()
方法中,只需要将服务绑定到服务容器上即可。
使用Artisan
命令即可创建服务提供者,也可以使用框架提供的AppServiceProvider
类,来绑定一些服务到服务容器中。
php artisan make:provider ExampleServiceProvider
使用
修改ExampleServiceProvider
类
不要尝试在 register 方法中注册任何监听器,路由,或者其他任何功能。否则,你可能会意外地使用到尚未加载的服务提供者提供的服务。
namespace App\Providers;
use App\Services\ExampleService;
use Illuminate\Support\ServiceProvider;
class ExampleServiceProvider extends ServiceProvider
{
public function register()
{
// 将服务绑定到服务容器上
$this->app->bind('example', function () {
return new ExampleService();
});
}
public function boot()
{
// 这里可以写一些服务启动的逻辑
}
}
分析源码可知,ExampleServiceProvider
继承了Illuminate\Support\ServiceProvider
类,当new
类时,调用了ServiceProvider
的构造函数,将服务容器绑定到$this->app
中,并将example
服务绑定到ExampleService
实例上。
新建服务类
新建位于app/Services
目录下的ExampleService.php
类,写一个测试方法
namespace App\Http\Service;
class ExampleService
{
public function test()
{
echo 'test';
}
}
注册服务提供者
然后在config/app.php
中添加服务提供者的配置
'providers' => [
//省略...
App\Providers\ExampleServiceProvider::class,
],
修改路由
修改路由web.php
配置,
Route::get('/', function () {
var_dump(app('example'));
app('example')->test();
});
访问首页,可以看到输出test
以及ExampleService
对象实例。
$bindings 和 $singletons属性
// 如果Provider类有bindings属性,则调用bind()方法
if (property_exists($provider, 'bindings')) {
foreach ($provider->bindings as $key => $value) {
$this->bind($key, $value);
}
}
// 如果Provider类有singletons属性,则调用singleton()方法
if (property_exists($provider, 'singletons')) {
foreach ($provider->singletons as $key => $value) {
$this->singleton($key, $value);
}
}
通过上面的源码分析可知,当服务提供者有bindings
属性时,会调用bind()
方法,将服务提供者的bindings
属性中的服务绑定到容器中。
同样,当服务提供者有singletons
属性时,会调用singleton()
方法,将服务提供者的singletons
属性中的服务绑定到容器中,并且在每次容器实例化时,都会返回同一个实例。
设置变量$bindings
public $bindings = [
'example' => \App\Http\Service\ExampleService::class
];
注释掉register
方法中的绑定,访问首页依旧可以正常解析
设置变量$singletons
public $singletons = [
'example' => \App\Http\Service\ExampleService::class
];
注释掉register
方法中的绑定,访问首页依旧可以正常解析,如果有很多简单的绑定,可以使用这两个属性来简化操作
延迟加载提供者
如果只注册,可以选择延迟加载该服务。延迟加载可以提高应用性能。
要延迟加载提供者,需要实现
\Illuminate\Contracts\Support\DeferrableProvider
接口并置一个provides
方法。这个provides
方法返回该提供者注册的服务容器绑定
namespace App\Providers;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;
// 如果使用延迟加载该服务,需要实现DeferrableProvider接口
class ExampleServiceProvider extends ServiceProvider implements DeferrableProvider
{
public $bindings = [
'ex' => \App\Http\Service\ExampleService::class,
'ex2' => \App\Http\Service\ExampleService::class
];
/**
* Register services.
*
* @return void
*/
public function register()
{
// $this->app->bind('example', function () {
// return new ExampleService();
// });
}
public function provides()
{
return [
'ex','ex2'
];
}
}
// 路由配置
Route::get('/', function () {
var_dump(app('ex2'));
app('ex2')->test();
});
这时访问首页也可以解析出ExampleService
实例,并调用其中的test
方法。
本作品采用《CC 协议》,转载必须注明作者和本文链接