依赖注入
先看一下什么叫依赖注入?
依赖倒置原则(Dependence Inversion Principle, DIP):DIP是一种软件设计的指导思想。传统软件设计中,上层代码依赖于下层代码,当下层出现变动时, 上层代码也要相应变化,维护成本较高。 而DIP的核心思想是上层定义接口,下层实现这个接口, 从而使得下层依赖于上层,降低耦合度,提高整个系统的弹性。这是一种经实践证明的有效策略。
控制反转(Inversion of Control, IoC):IoC就是DIP的一种具体思路,DIP只是一种理念、思想,而IoC是一种实现DIP的方法。 IoC的核心是将类(上层)所依赖的单元(下层)的实例化过程交由第三方来实现。控制反转 的最终目标是为了 实现项目的高内聚低耦合,而 实现这种目标 的方式则是通过 依赖注入 这种设计模式。实现控制反转时,通过将原先在模块内部完成具体实现类的实例化,移至模块的外部,然后再通过「依赖注入」的方式将具体实例「注入」到模块内即完成了对控制的反转操作。 一个简单的特征, 就是类中不对所依赖的单元有诸如 $component = new yii\component\SomeClass() 的实例化语句。
依赖注入(Dependence Injection, DI):DI是IoC的一种设计模式,是一种套路,按照DI的套路,就可以实现IoC,就能符合DIP原则。 DI的核心是把类所依赖的单元的实例化过程,放到类的外面去实现。一言以蔽之:面向接口编程。
服务定位器模式:服务定位器模式是控制反转(IoC)模式的实现,在服务定位器中注册给定接口的服务实例,然后通过接口获取服务并在应用代码中使用而不需要关心其具体实现。
控制反转容器(IoC Container):当项目比较大时,依赖关系可能会很复杂。 而IoC Container提供了动态地创建、注入依赖单元, 映射依赖关系等功能,减少了许多代码量。 Yii 设计了一个 yii\di\Container 来实现了 DI Container。
Laravel 服务容器
参阅:
https://learnku.com/docs/laravel/6.x/container/5131
https://learnku.com/articles/12575/deep-analysis-of-the-laravel-service-container
追踪看一下代码:
index.php 入口文件中:
<?php
/**
* Laravel - A PHP Framework For Web Artisans
*
* @package Laravel
* @author Taylor Otwell <taylor@laravel.com>
*/
define('LARAVEL_START', microtime(true));
/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader for
| our application. We just need to utilize it! We'll simply require it
| into the script here so that we don't have to worry about manual
| loading any of our classes later on. It feels great to relax.
|
*/
require __DIR__.'/../vendor/autoload.php';
/*
|--------------------------------------------------------------------------
| Turn On The Lights
|--------------------------------------------------------------------------
|
| We need to illuminate PHP development, so let us turn on the lights.
| This bootstraps the framework and gets it ready for use, then it
| will load up this application so that we can run it and send
| the responses back to the browser and delight our users.
|
*/
$app = require_once __DIR__.'/../bootstrap/app.php'; // 初始化应用,返回一个应用实例。
/*
|--------------------------------------------------------------------------
| Run The Application
|--------------------------------------------------------------------------
|
| Once we have the application, we can handle the incoming request
| through the kernel, and send the associated response back to
| the client's browser allowing them to enjoy the creative
| and wonderful application we have prepared for them.
|
*/
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); // 从容器中获取 HTTP 内核实例。
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture() // 处理请求,返回响应。
);
$response->send(); // 发送响应。
$kernel->terminate($request, $response); // 终止请求,清理资源。
$app = require_once __DIR__.'/../bootstrap/app.php'; 初始化应用,返回一个应用实例。看下app.php源码:
<?php
/*
|--------------------------------------------------------------------------
| Create The Application
|--------------------------------------------------------------------------
|
| The first thing we will do is create a new Laravel application instance
| which serves as the "glue" for all the components of Laravel, and is
| the IoC container for the system binding all of the various parts.
|
*/
$app = new Illuminate\Foundation\Application(
// __DIR__ 表示脚本执行的所在目录的绝对路径,即 index.php 所在目录,
// 是 public 目录 所在绝对路径,不到末尾 / 或者 \ 。
// 再次 dirname(__DIR__) 后提升到项目根目录下
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
/*
|--------------------------------------------------------------------------
| Bind Important Interfaces
|--------------------------------------------------------------------------
|
| Next, we need to bind some important interfaces into the container so
| we will be able to resolve them when needed. The kernels serve the
| incoming requests to this application from both the web and CLI.
|
*/
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
/*
|--------------------------------------------------------------------------
| Return The Application
|--------------------------------------------------------------------------
|
| This script returns the application instance. The instance is given to
| the calling script so we can separate the building of the instances
| from the actual running of the application and sending responses.
|
*/
return $app;
里面new Illuminate\Foundation\Application() 初始化应用,看下代码:
/**
* Create a new Illuminate application instance.
*
* @param string|null $basePath
* @return void
*/
public function __construct($basePath = null)
{
if ($basePath) {
$this->setBasePath($basePath);
}
$this->registerBaseBindings();
$this->registerBaseServiceProviders();
$this->registerCoreContainerAliases();
}
一共完成 3 个操作的处理功能:
- 通过 registerBaseBindings() 方法将「App 实例(即 Laravel 服务容器)」自身注册到「Laravel 服务容器」;
- 通过 registerBaseServiceProviders() 注册应用 Laravel 框架的基础服务提供者;
- 通过 registerCoreContainerAliases() 将具体的「依赖注入容器」及其别名注册到「Laravel 服务容器」。
这里所说的「注册」归根到底还是在执行「Laravel 服务容器」的「绑定(bind)」操作,完成绑定接口到实现。
看下 registerBaseBindings() 方法:
/**
* Register the basic bindings into the container.
*
* @return void
*/
protected function registerBaseBindings()
{
static::setInstance($this);
$this->instance('app', $this);
$this->instance(Container::class, $this);
$this->singleton(Mix::class);
$this->instance(PackageManifest::class, new PackageManifest(
new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
));
}
instance('app', $this)、instance(Container::class, $this) 方法会将对象实例 $this 绑定到容器的 app 和 Container::class 接口。后续无论是通过 app()->make('app') 还是 app()->make(Container::class) 获取到的实现类都是 $this(即 Laravel 服务容器实例) 对象。
换句话说一下,Laravel服务容器中的instance()方法主要用于将一个已经实例化的对象绑定到容器中,使得后续通过容器解析时始终返回该具体实例。
看下继承的父类的static::setInstance($this)方法:
/**
* Set the shared instance of the container.
*
* @param \Illuminate\Contracts\Container\Container|null $container
* @return \Illuminate\Contracts\Container\Container|static
*/
public static function setInstance(ContainerContract $container = null)
{
return static::$instance = $container;
}
注册实例:
/**
* Register an existing instance as shared in the container.
* 注册一个已经存在的实例到容器中。
*
* @param string $abstract
* @param mixed $instance
* @return mixed
*/
public function instance($abstract, $instance)
{
$this->removeAbstractAlias($abstract);
$isBound = $this->bound($abstract); // 是否已经绑定
unset($this->aliases[$abstract]);
// We'll check to determine if this type has been bound before, and if it has
// we will fire the rebound callbacks registered with the container and it
// can be updated with consuming classes that have gotten resolved here.
$this->instances[$abstract] = $instance; // 容器数组
// 如果已经绑定,则触发 rebound 进行更新
if ($isBound) {
$this->rebound($abstract);
}
return $instance;
}
/**
* Remove an alias from the contextual binding alias cache.
*
* @param string $searched
* @return void
*/
protected function removeAbstractAlias($searched)
{
if (! isset($this->aliases[$searched])) {
return;
}
foreach ($this->abstractAliases as $abstract => $aliases) {
foreach ($aliases as $index => $alias) {
if ($alias == $searched) {
unset($this->abstractAliases[$abstract][$index]);
}
}
}
}
/**
* Determine if the given abstract type has been bound.
*
* @param string $abstract
* @return bool
*/
public function bound($abstract)
{
return isset($this->bindings[$abstract]) ||
isset($this->instances[$abstract]) ||
$this->isAlias($abstract);
}
/**
* Fire the "rebound" callbacks for the given abstract type.
*
* @param string $abstract
* @return void
*/
protected function rebound($abstract)
{
// Resolve the given type from the container. 解析返回一个实例
$instance = $this->make($abstract);
foreach ($this->getReboundCallbacks($abstract) as $callback) {
call_user_func($callback, $this, $instance);
}
}
$app 里面体现容器的字段有:
- bindings:绑定的类和接口的映射关系。
- instances:已经实例化的对象的映射关系。
- aliases:别名的映射关系。
- abstractAliases:抽象类和接口的别名的映射关系。
- reboundCallbacks:绑定的类和接口的回调函数的映射关系。
- tags:标签的映射关系。
<?php
namespace Illuminate\Container;
use ArrayAccess;
use Closure;
use Exception;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Contracts\Container\Container as ContainerContract;
use LogicException;
use ReflectionClass;
use ReflectionException;
use ReflectionParameter;
class Container implements ArrayAccess, ContainerContract
{
/**
* The current globally available container (if any).
*
* @var static
*/
protected static $instance;
/**
* An array of the types that have been resolved.
*
* @var bool[]
*/
protected $resolved = [];
/**
* The container's bindings.
*
* @var array[]
*/
protected $bindings = [];
/**
* The container's method bindings.
*
* @var \Closure[]
*/
protected $methodBindings = [];
/**
* The container's shared instances.
*
* @var object[]
*/
protected $instances = [];
/**
* The registered type aliases.
*
* @var string[]
*/
protected $aliases = [];
/**
* The registered aliases keyed by the abstract name.
*
* @var array[]
*/
protected $abstractAliases = [];
/**
* The extension closures for services.
*
* @var array[]
*/
protected $extenders = [];
/**
* All of the registered tags.
*
* @var array[]
*/
protected $tags = [];
/**
* The stack of concretions currently being built.
*
* @var array[]
*/
protected $buildStack = [];
/**
* The parameter override stack.
*
* @var array[]
*/
protected $with = [];
/**
* The contextual binding map.
*
* @var array[]
*/
public $contextual = [];
/**
* All of the registered rebound callbacks.
*
* @var array[]
*/
protected $reboundCallbacks = [];
/**
* All of the global resolving callbacks.
*
* @var \Closure[]
*/
protected $globalResolvingCallbacks = [];
/**
* All of the global after resolving callbacks.
*
* @var \Closure[]
*/
protected $globalAfterResolvingCallbacks = [];
/**
* All of the resolving callbacks by class type.
*
* @var array[]
*/
protected $resolvingCallbacks = [];
/**
* All of the after resolving callbacks by class type.
*
* @var array[]
*/
protected $afterResolvingCallbacks = [];
/**省略若干**/
}
我们所说的「Laravel 服务容器」除了担纲「依赖注入容器」职能外;同时,还会作为 Laravel 项目的注册中心去完成基础服务的注册工作。直白一点讲在它的内部会将诸多服务的实现类「绑定」到「Laravel 服务容器」。总结起来它的作用主要可以归为以下 2 方面:
- 注册基础服务;
- 管理所需创建的类及其依赖。
顺便提一下上面初始化应用时设置基础路径:
/**
* Set the base path for the application.
*
* $basePath 为 $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__) 。
* __DIR__ 是一个魔术常量,表示当前执行脚本所在的绝对目录路径,如:
* /var/www/html/laravel-project/public
* dirname(__DIR__) 提升到项目根目录下,如:
* /var/www/html/laravel-project
*
* @param string $basePath
* @return $this
*/
public function setBasePath($basePath)
{
$this->basePath = rtrim($basePath, '\/');
$this->bindPathsInContainer();
return $this;
}
/**
* Bind all of the application paths in the container.
*
* @return void
*/
protected function bindPathsInContainer()
{
$this->instance('path', $this->path());
$this->instance('path.base', $this->basePath());
$this->instance('path.lang', $this->langPath());
$this->instance('path.config', $this->configPath());
$this->instance('path.public', $this->publicPath());
$this->instance('path.storage', $this->storagePath());
$this->instance('path.database', $this->databasePath());
$this->instance('path.resources', $this->resourcePath());
$this->instance('path.bootstrap', $this->bootstrapPath());
}
/**
* Get the path to the application "app" directory.
*
* 如:
* /var/www/html/laravel-project/app
*
* @param string $path
* @return string
*/
public function path($path = '')
{
$appPath = $this->appPath ?: $this->basePath.DIRECTORY_SEPARATOR.'app';
return $appPath.($path ? DIRECTORY_SEPARATOR.$path : $path);
}
/**
* Set the application directory.
* 重置应用目录。
*
* @param string $path
* @return $this
*/
public function useAppPath($path)
{
$this->appPath = $path;
$this->instance('path', $path);
return $this;
}
/**
* Get the base path of the Laravel installation.
*
* $this->basePath 如:
* /var/www/html/laravel-project
*
* @param string $path
* @return string
*/
public function basePath($path = '')
{
return $this->basePath.($path ? DIRECTORY_SEPARATOR.$path : $path);
}
/**
* Get the path to the bootstrap directory.
*
* @param string $path
* @return string
*/
public function bootstrapPath($path = '')
{
return $this->basePath.DIRECTORY_SEPARATOR.'bootstrap'.($path ? DIRECTORY_SEPARATOR.$path : $path);
}
/**
* Get the path to the application configuration files.
*
* @param string $path
* @return string
*/
public function configPath($path = '')
{
return $this->basePath.DIRECTORY_SEPARATOR.'config'.($path ? DIRECTORY_SEPARATOR.$path : $path);
}
/**
* Get the path to the database directory.
*
* @param string $path
* @return string
*/
public function databasePath($path = '')
{
return ($this->databasePath ?: $this->basePath.DIRECTORY_SEPARATOR.'database').($path ? DIRECTORY_SEPARATOR.$path : $path);
}
/**
* Set the database directory.
*
* @param string $path
* @return $this
*/
public function useDatabasePath($path)
{
$this->databasePath = $path;
$this->instance('path.database', $path);
return $this;
}
/**
* Get the path to the language files.
*
* @return string
*/
public function langPath()
{
return $this->resourcePath().DIRECTORY_SEPARATOR.'lang';
}
/**
* Get the path to the public / web directory.
*
* @return string
*/
public function publicPath()
{
return $this->basePath.DIRECTORY_SEPARATOR.'public';
}
/**
* Get the path to the storage directory.
*
* @return string
*/
public function storagePath()
{
return $this->storagePath ?: $this->basePath.DIRECTORY_SEPARATOR.'storage';
}
/**
* Set the storage directory.
*
* @param string $path
* @return $this
*/
public function useStoragePath($path)
{
$this->storagePath = $path;
$this->instance('path.storage', $path);
return $this;
}
/**
* Get the path to the resources directory.
*
* @param string $path
* @return string
*/
public function resourcePath($path = '')
{
return $this->basePath.DIRECTORY_SEPARATOR.'resources'.($path ? DIRECTORY_SEPARATOR.$path : $path);
}
/**
* Get the path to the environment file directory.
*
* @return string
*/
public function environmentPath()
{
return $this->environmentPath ?: $this->basePath;
}
/**
* Set the directory for the environment file.
*
* @param string $path
* @return $this
*/
public function useEnvironmentPath($path)
{
$this->environmentPath = $path;
return $this;
}
使用方法
参阅 https://learnku.com/docs/laravel/5.6/container/1359
几乎所有的 服务容器 绑定都会在 服务提供者 中注册,下面示例中的大多数将演示如何在该上下文(服务提供者)中使用容器。 例外:如果某个容器不依赖于任何接口就没必要去绑定类到这个容器里。容器不需要指定如何构建这些对象,因为它可以使用反射来自动解析这些对象(如每个控制器的请求方法中的 Illuminate\Http\Request 参数)。
在服务提供者中,可以通过 $this->app 属性访问容器。
简单绑定:
$this->app->bind('HelpSpot\API', function ($app) {
return new HelpSpot\API($app->make('HttpClient'));
});
绑定一个单例:
$this->app->singleton('HelpSpot\API', function ($app) {
return new HelpSpot\API($app->make('HttpClient'));
});
绑定实例:
$api = new HelpSpot\API(new HttpClient);
$this->app->instance('HelpSpot\API', $api);
绑定初始数据:
$this->app->when('App\Http\Controllers\UserController')
->needs('$variableName')
->give($value);
绑定接口到实现:
$this->app->bind(
'App\Contracts\EventPusher',
'App\Services\RedisEventPusher'
);
上下文绑定:
use Illuminate\Support\Facades\Storage;
use App\Http\Controllers\PhotoController;
use App\Http\Controllers\VideoController;
use Illuminate\Contracts\Filesystem\Filesystem;
$this->app->when(PhotoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('local');
});
$this->app->when(VideoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('s3');
});
标记:
$this->app->bind('SpeedReport', function () {
//
});
$this->app->bind('MemoryReport', function () {
//
});
$this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');
// 服务被标记后,你可以通过 tagged 方法轻松地将它们全部解析
$this->app->bind('ReportAggregator', function ($app) {
return new ReportAggregator($app->tagged('reports'));
});
扩展绑定:
// extend 方法可以修改解析的服务。例如,当一个服务被解析后,你可以添加额外的代码去修饰或配置这个服务。
// extend 方法接受一个闭包,闭包的唯一参数和返回值都是一个服务
$this->app->extend(Service::class, function($service) {
return new DecoratedService($service);
});
自动注入和解析
「Laravel 服务容器」功能强大的原因在于除了提供手动的绑定接口到实现的方法,还支持自动注入和解析的功能。
我们在编写控制器时,经常会使用类型提示功能将某个类作为依赖传入构造函数;但在执行这个类时却无需我们去实例化这个类所需的依赖,这一切归功于自动解析的能力。
例如下面最常用的在控制器的构造函数中定义 Request类 作为依赖:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class UserController
{
private $user = null;
public function __construct(Request $request)
{
$data = $request->all();
$page = $data['limit'] ?? 10;
$page = $data['page'] ?? 1;
}
}
实现原理
参阅 https://learnku.com/articles/12575/deep-analysis-of-the-laravel-service-container
Laravel 服务容器会处理以下两方面的工作:
- 注册基础服务;
- 管理所需创建的类及其依赖。
注册基础服务提供者
分析完上面registerBaseBindings()后 ,再看下接下来的注册基础服务提供者:
/**
* Register all of the base service providers.
* 注册应用基础服务提供者
*
* @return void
*/
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
}
/**
* Register a service provider with the application.
*
* @param \Illuminate\Support\ServiceProvider|string $provider
* @param bool $force
* @return \Illuminate\Support\ServiceProvider
*/
public function register($provider, $force = false)
{
if (($registered = $this->getProvider($provider)) && ! $force) {
return $registered;
}
// If the given "provider" is a string, we will resolve it, passing in the
// application instance automatically for the developer. This is simply
// a more convenient way of specifying your service provider classes.
if (is_string($provider)) {
$provider = $this->resolveProvider($provider);
}
// 执行 register 方法,完成绑定处理
$provider->register();
// If there are bindings / singletons set as properties on the provider we
// will spin through them and register them with the application, which
// serves as a convenience layer while registering a lot of bindings.
if (property_exists($provider, 'bindings')) {
foreach ($provider->bindings as $key => $value) {
$this->bind($key, $value);
}
}
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.
// 执行服务提供者 boot 方法启动程序
if ($this->isBooted()) {
$this->bootProvider($provider);
}
return $provider;
}
/**
* Boot the given service provider.
*
* @param \Illuminate\Support\ServiceProvider $provider
* @return mixed
*/
protected function bootProvider(ServiceProvider $provider)
{
if (method_exists($provider, 'boot')) {
return $this->call([$provider, 'boot']);
}
}
Laravel 服务容器在执行注册方法时,需要进行如下处理:
- 执行register 方法,将服务提供者提供的服务绑定到服务容器中
$provider->register(); - 如果服务提供者存在 boot 方法,会在 bootProvider 方法内执行启动方法来启动这个服务。
看一下 EventServiceProvider 服务:
<?php
namespace Illuminate\Events;
use Illuminate\Contracts\Queue\Factory as QueueFactoryContract;
use Illuminate\Support\ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->singleton('events', function ($app) {
return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
return $app->make(QueueFactoryContract::class);
});
});
}
}
$this->app是从哪里来的呢?看下父类ServiceProvider代码:
<?php
namespace Illuminate\Support;
use Illuminate\Console\Application as Artisan;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Database\Eloquent\Factory as ModelFactory;
abstract class ServiceProvider
{
/**
* The application instance.
*
* @var \Illuminate\Contracts\Foundation\Application
*/
protected $app;
/**
* The paths that should be published.
*
* @var array
*/
public static $publishes = [];
/**
* The paths that should be published by group.
*
* @var array
*/
public static $publishGroups = [];
/**
* Create a new service provider instance.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function __construct($app)
{
$this->app = $app;
}
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}
/****省略若干*****/
}
实例化时把app应用实例传递给了服务提供者。
EventServiceProvider 所做的全部事情,仅仅通过 register 方法将闭包绑定到了服务容器。
在服务提供者的 register 方法中,最好仅执行「绑定」操作。
注册核心服务别名到容器
在 Laravel 中有个别名系统。最常见的使用场景就是设置路由时,可以通过 Route 类完成一个新路由的注册,如:
Route::get('/', function () {
return 'Hello World!';
});
得益于 Laravel Facades 和别名系统我们可以很方便的通过别名来使用 Laravel 内置提供的各种服务。
注册别名和对应服务的映射关系,便是在上面实例化 app 时的 $this->registerCoreContainerAliases(); 方法内完成的:
/**
* Register the core class aliases in the container.
*
* @return void
*/
public function registerCoreContainerAliases()
{
foreach ([
'app' => [self::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class],
'auth' => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
'auth.driver' => [\Illuminate\Contracts\Auth\Guard::class],
'blade.compiler' => [\Illuminate\View\Compilers\BladeCompiler::class],
'cache' => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
'cache.store' => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class, \Psr\SimpleCache\CacheInterface::class],
'cache.psr6' => [\Symfony\Component\Cache\Adapter\Psr16Adapter::class, \Symfony\Component\Cache\Adapter\AdapterInterface::class, \Psr\Cache\CacheItemPoolInterface::class],
'config' => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\Repository::class],
'cookie' => [\Illuminate\Cookie\CookieJar::class, \Illuminate\Contracts\Cookie\Factory::class, \Illuminate\Contracts\Cookie\QueueingFactory::class],
'encrypter' => [\Illuminate\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\Encrypter::class],
'db' => [\Illuminate\Database\DatabaseManager::class, \Illuminate\Database\ConnectionResolverInterface::class],
'db.connection' => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
'events' => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class],
'files' => [\Illuminate\Filesystem\Filesystem::class],
'filesystem' => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class],
'filesystem.disk' => [\Illuminate\Contracts\Filesystem\Filesystem::class],
'filesystem.cloud' => [\Illuminate\Contracts\Filesystem\Cloud::class],
'hash' => [\Illuminate\Hashing\HashManager::class],
'hash.driver' => [\Illuminate\Contracts\Hashing\Hasher::class],
'translator' => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class],
'log' => [\Illuminate\Log\LogManager::class, \Psr\Log\LoggerInterface::class],
'mailer' => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class],
'auth.password' => [\Illuminate\Auth\Passwords\PasswordBrokerManager::class, \Illuminate\Contracts\Auth\PasswordBrokerFactory::class],
'auth.password.broker' => [\Illuminate\Auth\Passwords\PasswordBroker::class, \Illuminate\Contracts\Auth\PasswordBroker::class],
'queue' => [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class],
'queue.connection' => [\Illuminate\Contracts\Queue\Queue::class],
'queue.failer' => [\Illuminate\Queue\Failed\FailedJobProviderInterface::class],
'redirect' => [\Illuminate\Routing\Redirector::class],
'redis' => [\Illuminate\Redis\RedisManager::class, \Illuminate\Contracts\Redis\Factory::class],
'redis.connection' => [\Illuminate\Redis\Connections\Connection::class, \Illuminate\Contracts\Redis\Connection::class],
'request' => [\Illuminate\Http\Request::class, \Symfony\Component\HttpFoundation\Request::class],
'router' => [\Illuminate\Routing\Router::class, \Illuminate\Contracts\Routing\Registrar::class, \Illuminate\Contracts\Routing\BindingRegistrar::class],
'session' => [\Illuminate\Session\SessionManager::class],
'session.store' => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class],
'url' => [\Illuminate\Routing\UrlGenerator::class, \Illuminate\Contracts\Routing\UrlGenerator::class],
'validator' => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class],
'view' => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
] as $key => $aliases) {
foreach ($aliases as $alias) {
$this->alias($key, $alias);
}
}
}
管理所需创建的类及其依赖
对于 Laravel 服务容器来讲,其内部实现上无论是 bind、singleton、tag 还是 extend 它们的基本原理大致类似。所以这里仅研究 bind 绑定来管中窥豹。
我们知道绑定方法定义在 Laravel 服务容器 Illuminate\Foundation\Application 类内,而 Application 继承自 Illuminate\Container\Container 类。这些与服务容器绑定相关的方法便直接继承自 Container 类。
bind 方法执行原理
bind 绑定作为最基本的绑定方法,可以很好的说明 Laravel 是如何实现绑定服务处理的。
$this->app->bind('HelpSpot\API', function ($app) {
return new HelpSpot\API($app->make('HttpClient'));
});
下面摘出 Container 容器中 bind 方法及其相关联的方法。由于绑定处理中涉及较多方法,所以我直接将重要的代码片段相关注释做了翻译及补充说明,以便阅读:
/**
* Register a binding with the container.
*
* @param string $abstract
* @param \Closure|string|null $concrete
* @param bool $shared
* @return void
*/
public function bind($abstract, $concrete = null, $shared = false)
{
// 删掉之前解析过的服务实例
$this->dropStaleInstances($abstract);
// If no concrete type was given, we will simply set the concrete type to the
// abstract type. After that, the concrete type to be registered as shared
// without being forced to state their classes in both of the parameters.
// 如果未提供实现类 $concrete,我们直接将抽象类作为实现 $abstract。
// 这之后,我们无需明确指定 $abstract 和 $concrete 是否为单例模式,
// 而是通过 $shared 标识来决定它们是单例还是每次都需要实例化处理。
if (is_null($concrete)) {
$concrete = $abstract;
}
// If the factory is not a Closure, it means it is just a class name which is
// bound into this container to the abstract type and we will just wrap it
// up inside its own Closure to give us more convenience when extending.
// 如果绑定时传入的实现类非闭包,即绑定时是直接给定了实现类的类名,
// 这时要稍微处理下将类名封装成一个闭包,保证解析时处理手法的统一。
if (! $concrete instanceof Closure) {
$concrete = $this->getClosure($abstract, $concrete);
}
$this->bindings[$abstract] = compact('concrete', 'shared');
// If the abstract type was already resolved in this container we'll fire the
// rebound listener so that any objects which have already gotten resolved
// can have their copy of the object updated via the listener callbacks.
// 最后如果抽象类已经被容器解析过,我们将触发 rebound 监听器。
// 并且通过触发 rebound 监听器回调,将任何已被解析过的服务更新最新的实现到抽象接口。
if ($this->resolved($abstract)) {
$this->rebound($abstract);
}
}
/**
* Get the Closure to be used when building a type.
* 当绑定实现为类名时,则封装成闭包并返回。
*
* @param string $abstract
* @param string $concrete
* @return \Closure
*/
protected function getClosure($abstract, $concrete)
{
return function ($container, $parameters = []) use ($abstract, $concrete) {
if ($abstract == $concrete) {
return $container->build($concrete);
}
return $container->resolve(
$concrete, $parameters, $raiseEvents = false
);
};
}
/**
* Fire the "rebound" callbacks for the given abstract type.
* 依据给定的抽象服务接口,触发其 "rebound" 回调
*
* @param string $abstract
* @return void
*/
protected function rebound($abstract)
{
$instance = $this->make($abstract);
foreach ($this->getReboundCallbacks($abstract) as $callback) {
call_user_func($callback, $this, $instance);
}
}
/**
* Get the rebound callbacks for a given type.
* 获取给定抽象服务的回调函数。
*
* @param string $abstract
* @return array
*/
protected function getReboundCallbacks($abstract)
{
return $this->reboundCallbacks[$abstract] ?? [];
}
在 bind 方法中,主要完成以下几个方面的处理:
- 删掉之前解析过的服务实例;
- 将绑定的实现类封装成闭包,以确保后续处理的统一;
- 针对已解析过的服务实例,再次触发重新绑定回调函数,同时将最新的实现类更新到接口里面。
在绑定过程中,服务容器并不会执行服务的解析操作,这样有利于提升服务的性能。直到在项目运行期间,被使用时才会真正解析出需要使用的对应服务,实现「按需加载」。
make 解析处理
解析处理和绑定一样定义在 Illuminate\Container\Container 类中,无论是手动解析还是通过自动注入的方式,实现原理都是基于 PHP 的反射机制。
所有我们还是直接从 make 方法开始去挖出相关细节:
/**
* Resolve the given type from the container.
* 从容器中解析出给定服务具体实现
*
* @param string $abstract
* @param array $parameters
* @return mixed
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function make($abstract, array $parameters = [])
{
return $this->resolve($abstract, $parameters);
}
/**
* Resolve the given type from the container.
* 从容器中解析出给定服务具体实现
*
* @param string $abstract
* @param array $parameters
* @param bool $raiseEvents
* @return mixed
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
protected function resolve($abstract, $parameters = [], $raiseEvents = true)
{
$abstract = $this->getAlias($abstract);
// 如果绑定时基于上下文绑定,此时需要解析出上下文实现类
$needsContextualBuild = ! empty($parameters) || ! is_null(
$this->getContextualConcrete($abstract)
);
// If an instance of the type is currently being managed as a singleton we'll
// just return an existing instance instead of instantiating new instances
// so the developer can keep using the same objects instance every time.
// 如果给定的类型已单例模式绑定,直接从服务容器中返回这个实例而无需重新实例化
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
return $this->instances[$abstract];
}
$this->with[] = $parameters;
$concrete = $this->getConcrete($abstract);
// We're ready to instantiate an instance of the concrete type registered for
// the binding. This will instantiate the types, as well as resolve any of
// its "nested" dependencies recursively until all have gotten resolved.
// 已准备就绪创建这个绑定的实例。下面将实例化给定实例及内嵌的所有依赖实例。
// 到这里我们已经做好创建实例的准备工作。只有可以构建的服务才可以执行 build 方法去实例化服务;
// 否则也就是说我们的服务还存在依赖,然后不断的去解析嵌套的依赖,直到它们可以去构建(isBuildable)。
if ($this->isBuildable($concrete, $abstract)) {
$object = $this->build($concrete);
} else {
$object = $this->make($concrete);
}
// If we defined any extenders for this type, we'll need to spin through them
// and apply them to the object being built. This allows for the extension
// of services, such as changing configuration or decorating the object.
// 如果我们的服务存在扩展(extend)绑定,此时就需要去执行扩展。
// 扩展绑定适用于修改服务的配置或者修饰(decorating)服务实现。
foreach ($this->getExtenders($abstract) as $extender) {
$object = $extender($object, $this);
}
// If the requested type is registered as a singleton we'll want to cache off
// the instances in "memory" so we can return it later without creating an
// entirely new instance of an object on each subsequent request for it.
// 如果我们的服务已单例模式绑定,此时要将已解析的服务缓存到单例对象池中(instances),
// 后续便可以直接获取单例服务对象了。
if ($this->isShared($abstract) && ! $needsContextualBuild) {
$this->instances[$abstract] = $object;
}
if ($raiseEvents) {
$this->fireResolvingCallbacks($abstract, $object);
}
// Before returning, we will also set the resolved flag to "true" and pop off
// the parameter overrides for this build. After those two things are done
// we will be ready to return back the fully constructed class instance.
$this->resolved[$abstract] = true;
array_pop($this->with);
return $object;
}
/**
* Determine if the given concrete is buildable.
* 判断给定的实现是否立马进行构建
*
* @param mixed $concrete
* @param string $abstract
* @return bool
*/
protected function isBuildable($concrete, $abstract)
{
// 仅当实现类和接口相同或者实现为闭包时可构建
return $concrete === $abstract || $concrete instanceof Closure;
}
/**
* Instantiate a concrete instance of the given type.
* 构建(实例化)给定类型的实现类(匿名函数)实例
*
* @param string $concrete
* @return mixed
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function build($concrete)
{
// If the concrete type is actually a Closure, we will just execute it and
// hand back the results of the functions, which allows functions to be
// used as resolvers for more fine-tuned resolution of these objects.
// 如果给定的实现是一个闭包,直接执行并闭包,返回执行结果
if ($concrete instanceof Closure) {
return $concrete($this, $this->getLastParameterOverride());
}
// 如果需要解析的类无法实例化,即试图解析一个抽象类类型如: 接口或抽象类而非实现类,直接抛出异常。
try {
$reflector = new ReflectionClass($concrete);
} catch (ReflectionException $e) {
throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e);
}
// If the type is not instantiable, the developer is attempting to resolve
// an abstract type such as an Interface or Abstract Class and there is
// no binding registered for the abstractions so we need to bail out.
if (! $reflector->isInstantiable()) {
return $this->notInstantiable($concrete);
}
$this->buildStack[] = $concrete;
// 通过反射获取实现类构造函数
$constructor = $reflector->getConstructor();
// If there are no constructors, that means there are no dependencies then
// we can just resolve the instances of the objects right away, without
// resolving any other types or dependencies out of these containers.
// 如果实现类并没有定义构造函数,说明这个实现类没有相关依赖。
// 我们可以直接实例化这个实现类,而无需自动解析依赖(自动注入)。
if (is_null($constructor)) {
array_pop($this->buildStack);
return new $concrete;
}
// 获取到实现类构造函数依赖参数
$dependencies = $constructor->getParameters();
// Once we have all the constructor's parameters we can create each of the
// dependency instances and then use the reflection instances to make a
// new instance of this class, injecting the created dependencies in.
try {
// 解析出所有依赖
$instances = $this->resolveDependencies($dependencies);
} catch (BindingResolutionException $e) {
array_pop($this->buildStack);
throw $e;
}
array_pop($this->buildStack);
// 这是我们就可以创建服务实例并返回。
return $reflector->newInstanceArgs($instances);
}
/**
* Resolve all of the dependencies from the ReflectionParameters.
* 从 ReflectionParameters 解析出所有构造函数所需依赖
*
* @param \ReflectionParameter[] $dependencies
* @return array
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
protected function resolveDependencies(array $dependencies)
{
$results = [];
foreach ($dependencies as $dependency) {
// If this dependency has a override for this particular build we will use
// that instead as the value. Otherwise, we will continue with this run
// of resolutions and let reflection attempt to determine the result.
if ($this->hasParameterOverride($dependency)) {
$results[] = $this->getParameterOverride($dependency);
continue;
}
// If the class is null, it means the dependency is a string or some other
// primitive type which we can not resolve since it is not a class and
// we will just bomb out with an error since we have no-where to go.
// 构造函数参数为非类时,即参数为 string、int 等标量类型或闭包时,按照标量和闭包解析;
// 否则需要解析类。
$results[] = is_null(Util::getParameterClassName($dependency))
? $this->resolvePrimitive($dependency)
: $this->resolveClass($dependency);
}
return $results;
}
/**
* Resolve a non-class hinted primitive dependency.
* 依据类型提示解析出标量类型(闭包)数据
*
* @param \ReflectionParameter $parameter
* @return mixed
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
protected function resolvePrimitive(ReflectionParameter $parameter)
{
if (! is_null($concrete = $this->getContextualConcrete('$'.$parameter->getName()))) {
return $concrete instanceof Closure ? $concrete($this) : $concrete;
}
if ($parameter->isDefaultValueAvailable()) {
return $parameter->getDefaultValue();
}
$this->unresolvablePrimitive($parameter);
}
/**
* Resolve a class based dependency from the container.
* 从服务容器中解析出类依赖(自动注入)
*
* @param \ReflectionParameter $parameter
* @return mixed
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
protected function resolveClass(ReflectionParameter $parameter)
{
try {
return $this->make(Util::getParameterClassName($parameter));
}
// If we can not resolve the class instance, we will check to see if the value
// is optional, and if it is we will return the optional parameter value as
// the value of the dependency, similarly to how we do this with scalars.
catch (BindingResolutionException $e) {
if ($parameter->isOptional()) {
return $parameter->getDefaultValue();
}
throw $e;
}
}
以上,便是 Laravel 服务容器解析的核心,得益于 PHP 的反射机制,实现了自动依赖注入和服务解析处理,概括起来包含以下步骤:
- 对于单例绑定数据如果已解析过服务则直接返回,否则继续执行解析;
- 非单例绑定的服务类型,通过接口获取绑定实现类;
- 接口即服务或者闭包时进行构建(build)处理,构建时依托于 PHP 反射机制进行自动依赖注入解析出完整的服务实例对象;否则继续解析(make)出所有嵌套的依赖;
- 如果服务存在扩展绑定,解析出扩展绑定结果;
- 如果绑定服务为单例绑定类型(singleton),将解析到的服务加入到单例对象池;
- 其它处理如触发绑定监听器、将服务标记为已解析状态等,并返回服务实例。
源码
bootstrap/cache/services.php 文件实例:
<?php return array (
'providers' =>
array (
0 => 'Illuminate\\Auth\\AuthServiceProvider',
1 => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
2 => 'Illuminate\\Bus\\BusServiceProvider',
3 => 'Illuminate\\Cache\\CacheServiceProvider',
4 => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
5 => 'Illuminate\\Cookie\\CookieServiceProvider',
6 => 'Illuminate\\Database\\DatabaseServiceProvider',
7 => 'Illuminate\\Encryption\\EncryptionServiceProvider',
8 => 'Illuminate\\Filesystem\\FilesystemServiceProvider',
9 => 'Illuminate\\Foundation\\Providers\\FoundationServiceProvider',
10 => 'Illuminate\\Hashing\\HashServiceProvider',
11 => 'Illuminate\\Mail\\MailServiceProvider',
12 => 'Illuminate\\Notifications\\NotificationServiceProvider',
13 => 'Illuminate\\Pagination\\PaginationServiceProvider',
14 => 'Illuminate\\Pipeline\\PipelineServiceProvider',
15 => 'Illuminate\\Queue\\QueueServiceProvider',
16 => 'Illuminate\\Redis\\RedisServiceProvider',
17 => 'Illuminate\\Auth\\Passwords\\PasswordResetServiceProvider',
18 => 'Illuminate\\Session\\SessionServiceProvider',
19 => 'Illuminate\\Translation\\TranslationServiceProvider',
20 => 'Illuminate\\Validation\\ValidationServiceProvider',
21 => 'Illuminate\\View\\ViewServiceProvider',
22 => 'Facade\\Ignition\\IgnitionServiceProvider',
23 => 'Fideloper\\Proxy\\TrustedProxyServiceProvider',
24 => 'Laravel\\Tinker\\TinkerServiceProvider',
25 => 'Carbon\\Laravel\\ServiceProvider',
26 => 'NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider',
27 => 'App\\Providers\\AppServiceProvider',
28 => 'App\\Providers\\AuthServiceProvider',
29 => 'App\\Providers\\EventServiceProvider',
30 => 'App\\Providers\\RouteServiceProvider',
31 => 'App\\Providers\\BailianServiceProvider',
32 => 'App\\Providers\\ElasticsearchServiceProvider',
),
'eager' =>
array (
0 => 'Illuminate\\Auth\\AuthServiceProvider',
1 => 'Illuminate\\Cookie\\CookieServiceProvider',
2 => 'Illuminate\\Database\\DatabaseServiceProvider',
3 => 'Illuminate\\Encryption\\EncryptionServiceProvider',
4 => 'Illuminate\\Filesystem\\FilesystemServiceProvider',
5 => 'Illuminate\\Foundation\\Providers\\FoundationServiceProvider',
6 => 'Illuminate\\Notifications\\NotificationServiceProvider',
7 => 'Illuminate\\Pagination\\PaginationServiceProvider',
8 => 'Illuminate\\Session\\SessionServiceProvider',
9 => 'Illuminate\\View\\ViewServiceProvider',
10 => 'Facade\\Ignition\\IgnitionServiceProvider',
11 => 'Fideloper\\Proxy\\TrustedProxyServiceProvider',
12 => 'Carbon\\Laravel\\ServiceProvider',
13 => 'NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider',
14 => 'App\\Providers\\AppServiceProvider',
15 => 'App\\Providers\\AuthServiceProvider',
16 => 'App\\Providers\\EventServiceProvider',
17 => 'App\\Providers\\RouteServiceProvider',
18 => 'App\\Providers\\BailianServiceProvider',
19 => 'App\\Providers\\ElasticsearchServiceProvider',
),
'deferred' =>
array (
'Illuminate\\Broadcasting\\BroadcastManager' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
'Illuminate\\Contracts\\Broadcasting\\Factory' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
'Illuminate\\Contracts\\Broadcasting\\Broadcaster' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
'Illuminate\\Bus\\Dispatcher' => 'Illuminate\\Bus\\BusServiceProvider',
'Illuminate\\Contracts\\Bus\\Dispatcher' => 'Illuminate\\Bus\\BusServiceProvider',
'Illuminate\\Contracts\\Bus\\QueueingDispatcher' => 'Illuminate\\Bus\\BusServiceProvider',
'cache' => 'Illuminate\\Cache\\CacheServiceProvider',
'cache.store' => 'Illuminate\\Cache\\CacheServiceProvider',
'cache.psr6' => 'Illuminate\\Cache\\CacheServiceProvider',
'memcached.connector' => 'Illuminate\\Cache\\CacheServiceProvider',
'command.cache.clear' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.cache.forget' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.clear-compiled' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.auth.resets.clear' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.config.cache' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.config.clear' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.db.wipe' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.down' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.environment' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.event.cache' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.event.clear' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.event.list' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.key.generate' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.optimize' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.optimize.clear' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.package.discover' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.preset' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.queue.failed' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.queue.flush' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.queue.forget' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.queue.listen' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.queue.restart' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.queue.retry' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.queue.work' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.route.cache' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.route.clear' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.route.list' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.seed' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Console\\Scheduling\\ScheduleFinishCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Console\\Scheduling\\ScheduleRunCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.storage.link' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.up' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.view.cache' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.view.clear' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.cache.table' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.channel.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.console.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.controller.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.event.generate' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.event.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.exception.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.factory.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.job.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.listener.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.mail.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.middleware.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.model.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.notification.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.notification.table' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.observer.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.policy.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.provider.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.queue.failed-table' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.queue.table' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.request.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.resource.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.rule.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.seeder.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.session.table' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.serve' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.test.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.vendor.publish' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'migrator' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'migration.repository' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'migration.creator' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.migrate' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.migrate.fresh' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.migrate.install' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.migrate.refresh' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.migrate.reset' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.migrate.rollback' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.migrate.status' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'command.migrate.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'composer' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'hash' => 'Illuminate\\Hashing\\HashServiceProvider',
'hash.driver' => 'Illuminate\\Hashing\\HashServiceProvider',
'mailer' => 'Illuminate\\Mail\\MailServiceProvider',
'swift.mailer' => 'Illuminate\\Mail\\MailServiceProvider',
'swift.transport' => 'Illuminate\\Mail\\MailServiceProvider',
'Illuminate\\Mail\\Markdown' => 'Illuminate\\Mail\\MailServiceProvider',
'Illuminate\\Contracts\\Pipeline\\Hub' => 'Illuminate\\Pipeline\\PipelineServiceProvider',
'queue' => 'Illuminate\\Queue\\QueueServiceProvider',
'queue.worker' => 'Illuminate\\Queue\\QueueServiceProvider',
'queue.listener' => 'Illuminate\\Queue\\QueueServiceProvider',
'queue.failer' => 'Illuminate\\Queue\\QueueServiceProvider',
'queue.connection' => 'Illuminate\\Queue\\QueueServiceProvider',
'redis' => 'Illuminate\\Redis\\RedisServiceProvider',
'redis.connection' => 'Illuminate\\Redis\\RedisServiceProvider',
'auth.password' => 'Illuminate\\Auth\\Passwords\\PasswordResetServiceProvider',
'auth.password.broker' => 'Illuminate\\Auth\\Passwords\\PasswordResetServiceProvider',
'translator' => 'Illuminate\\Translation\\TranslationServiceProvider',
'translation.loader' => 'Illuminate\\Translation\\TranslationServiceProvider',
'validator' => 'Illuminate\\Validation\\ValidationServiceProvider',
'validation.presence' => 'Illuminate\\Validation\\ValidationServiceProvider',
'command.tinker' => 'Laravel\\Tinker\\TinkerServiceProvider',
),
'when' =>
array (
'Illuminate\\Broadcasting\\BroadcastServiceProvider' =>
array (
),
'Illuminate\\Bus\\BusServiceProvider' =>
array (
),
'Illuminate\\Cache\\CacheServiceProvider' =>
array (
),
'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider' =>
array (
),
'Illuminate\\Hashing\\HashServiceProvider' =>
array (
),
'Illuminate\\Mail\\MailServiceProvider' =>
array (
),
'Illuminate\\Pipeline\\PipelineServiceProvider' =>
array (
),
'Illuminate\\Queue\\QueueServiceProvider' =>
array (
),
'Illuminate\\Redis\\RedisServiceProvider' =>
array (
),
'Illuminate\\Auth\\Passwords\\PasswordResetServiceProvider' =>
array (
),
'Illuminate\\Translation\\TranslationServiceProvider' =>
array (
),
'Illuminate\\Validation\\ValidationServiceProvider' =>
array (
),
'Laravel\\Tinker\\TinkerServiceProvider' =>
array (
),
),
);
Illuminate\Foundation\Application 源码:
<?php
namespace Illuminate\Foundation;
use Closure;
use Illuminate\Container\Container;
use Illuminate\Contracts\Foundation\Application as ApplicationContract;
use Illuminate\Contracts\Http\Kernel as HttpKernelContract;
use Illuminate\Events\EventServiceProvider;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables;
use Illuminate\Foundation\Events\LocaleUpdated;
use Illuminate\Http\Request;
use Illuminate\Log\LogServiceProvider;
use Illuminate\Routing\RoutingServiceProvider;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Env;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
use RuntimeException;
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\HttpKernelInterface;
class Application extends Container implements ApplicationContract, HttpKernelInterface
{
/**
* The Laravel framework version.
*
* @var string
*/
const VERSION = '6.20.45';
/**
* The base path for the Laravel installation.
*
* @var string
*/
protected $basePath;
/**
* Indicates if the application has been bootstrapped before.
*
* @var bool
*/
protected $hasBeenBootstrapped = false;
/**
* Indicates if the application has "booted".
*
* @var bool
*/
protected $booted = false;
/**
* The array of booting callbacks.
*
* @var callable[]
*/
protected $bootingCallbacks = [];
/**
* The array of booted callbacks.
*
* @var callable[]
*/
protected $bootedCallbacks = [];
/**
* The array of terminating callbacks.
*
* @var callable[]
*/
protected $terminatingCallbacks = [];
/**
* All of the registered service providers.
*
* @var \Illuminate\Support\ServiceProvider[]
*/
protected $serviceProviders = [];
/**
* The names of the loaded service providers.
*
* @var array
*/
protected $loadedProviders = [];
/**
* The deferred services and their providers.
*
* @var array
*/
protected $deferredServices = [];
/**
* The custom application path defined by the developer.
*
* @var string
*/
protected $appPath;
/**
* The custom database path defined by the developer.
*
* @var string
*/
protected $databasePath;
/**
* The custom storage path defined by the developer.
*
* @var string
*/
protected $storagePath;
/**
* The custom environment path defined by the developer.
*
* @var string
*/
protected $environmentPath;
/**
* The environment file to load during bootstrapping.
*
* @var string
*/
protected $environmentFile = '.env';
/**
* Indicates if the application is running in the console.
*
* @var bool|null
*/
protected $isRunningInConsole;
/**
* The application namespace.
*
* @var string
*/
protected $namespace;
/**
* Create a new Illuminate application instance.
*
* @param string|null $basePath
* @return void
*/
public function __construct($basePath = null)
{
if ($basePath) {
$this->setBasePath($basePath);
}
$this->registerBaseBindings();
$this->registerBaseServiceProviders();
$this->registerCoreContainerAliases();
}
/**
* Get the version number of the application.
*
* @return string
*/
public function version()
{
return static::VERSION;
}
/**
* Register the basic bindings into the container.
*
* @return void
*/
protected function registerBaseBindings()
{
static::setInstance($this);
$this->instance('app', $this);
$this->instance(Container::class, $this);
$this->singleton(Mix::class);
$this->instance(PackageManifest::class, new PackageManifest(
new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
));
}
/**
* Register all of the base service providers.
*
* @return void
*/
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
}
/**
* Run the given array of bootstrap classes.
*
* @param string[] $bootstrappers
* @return void
*/
public function bootstrapWith(array $bootstrappers)
{
$this->hasBeenBootstrapped = true;
foreach ($bootstrappers as $bootstrapper) {
$this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]);
$this->make($bootstrapper)->bootstrap($this);
$this['events']->dispatch('bootstrapped: '.$bootstrapper, [$this]);
}
}
/**
* Register a callback to run after loading the environment.
*
* @param \Closure $callback
* @return void
*/
public function afterLoadingEnvironment(Closure $callback)
{
$this->afterBootstrapping(
LoadEnvironmentVariables::class, $callback
);
}
/**
* Register a callback to run before a bootstrapper.
*
* @param string $bootstrapper
* @param \Closure $callback
* @return void
*/
public function beforeBootstrapping($bootstrapper, Closure $callback)
{
$this['events']->listen('bootstrapping: '.$bootstrapper, $callback);
}
/**
* Register a callback to run after a bootstrapper.
*
* @param string $bootstrapper
* @param \Closure $callback
* @return void
*/
public function afterBootstrapping($bootstrapper, Closure $callback)
{
$this['events']->listen('bootstrapped: '.$bootstrapper, $callback);
}
/**
* Determine if the application has been bootstrapped before.
*
* @return bool
*/
public function hasBeenBootstrapped()
{
return $this->hasBeenBootstrapped;
}
/**
* Set the base path for the application.
*
* @param string $basePath
* @return $this
*/
public function setBasePath($basePath)
{
$this->basePath = rtrim($basePath, '\/');
$this->bindPathsInContainer();
return $this;
}
/**
* Bind all of the application paths in the container.
*
* @return void
*/
protected function bindPathsInContainer()
{
$this->instance('path', $this->path());
$this->instance('path.base', $this->basePath());
$this->instance('path.lang', $this->langPath());
$this->instance('path.config', $this->configPath());
$this->instance('path.public', $this->publicPath());
$this->instance('path.storage', $this->storagePath());
$this->instance('path.database', $this->databasePath());
$this->instance('path.resources', $this->resourcePath());
$this->instance('path.bootstrap', $this->bootstrapPath());
}
/**
* Get the path to the application "app" directory.
*
* @param string $path
* @return string
*/
public function path($path = '')
{
$appPath = $this->appPath ?: $this->basePath.DIRECTORY_SEPARATOR.'app';
return $appPath.($path ? DIRECTORY_SEPARATOR.$path : $path);
}
/**
* Set the application directory.
*
* @param string $path
* @return $this
*/
public function useAppPath($path)
{
$this->appPath = $path;
$this->instance('path', $path);
return $this;
}
/**
* Get the base path of the Laravel installation.
*
* @param string $path
* @return string
*/
public function basePath($path = '')
{
return $this->basePath.($path ? DIRECTORY_SEPARATOR.$path : $path);
}
/**
* Get the path to the bootstrap directory.
*
* @param string $path
* @return string
*/
public function bootstrapPath($path = '')
{
return $this->basePath.DIRECTORY_SEPARATOR.'bootstrap'.($path ? DIRECTORY_SEPARATOR.$path : $path);
}
/**
* Get the path to the application configuration files.
*
* @param string $path
* @return string
*/
public function configPath($path = '')
{
return $this->basePath.DIRECTORY_SEPARATOR.'config'.($path ? DIRECTORY_SEPARATOR.$path : $path);
}
/**
* Get the path to the database directory.
*
* @param string $path
* @return string
*/
public function databasePath($path = '')
{
return ($this->databasePath ?: $this->basePath.DIRECTORY_SEPARATOR.'database').($path ? DIRECTORY_SEPARATOR.$path : $path);
}
/**
* Set the database directory.
*
* @param string $path
* @return $this
*/
public function useDatabasePath($path)
{
$this->databasePath = $path;
$this->instance('path.database', $path);
return $this;
}
/**
* Get the path to the language files.
*
* @return string
*/
public function langPath()
{
return $this->resourcePath().DIRECTORY_SEPARATOR.'lang';
}
/**
* Get the path to the public / web directory.
*
* @return string
*/
public function publicPath()
{
return $this->basePath.DIRECTORY_SEPARATOR.'public';
}
/**
* Get the path to the storage directory.
*
* @return string
*/
public function storagePath()
{
return $this->storagePath ?: $this->basePath.DIRECTORY_SEPARATOR.'storage';
}
/**
* Set the storage directory.
*
* @param string $path
* @return $this
*/
public function useStoragePath($path)
{
$this->storagePath = $path;
$this->instance('path.storage', $path);
return $this;
}
/**
* Get the path to the resources directory.
*
* @param string $path
* @return string
*/
public function resourcePath($path = '')
{
return $this->basePath.DIRECTORY_SEPARATOR.'resources'.($path ? DIRECTORY_SEPARATOR.$path : $path);
}
/**
* Get the path to the environment file directory.
*
* @return string
*/
public function environmentPath()
{
return $this->environmentPath ?: $this->basePath;
}
/**
* Set the directory for the environment file.
*
* @param string $path
* @return $this
*/
public function useEnvironmentPath($path)
{
$this->environmentPath = $path;
return $this;
}
/**
* Set the environment file to be loaded during bootstrapping.
*
* @param string $file
* @return $this
*/
public function loadEnvironmentFrom($file)
{
$this->environmentFile = $file;
return $this;
}
/**
* Get the environment file the application is using.
*
* @return string
*/
public function environmentFile()
{
return $this->environmentFile ?: '.env';
}
/**
* Get the fully qualified path to the environment file.
*
* @return string
*/
public function environmentFilePath()
{
return $this->environmentPath().DIRECTORY_SEPARATOR.$this->environmentFile();
}
/**
* Get or check the current application environment.
*
* @param string|array $environments
* @return string|bool
*/
public function environment(...$environments)
{
if (count($environments) > 0) {
$patterns = is_array($environments[0]) ? $environments[0] : $environments;
return Str::is($patterns, $this['env']);
}
return $this['env'];
}
/**
* Determine if application is in local environment.
*
* @return bool
*/
public function isLocal()
{
return $this['env'] === 'local';
}
/**
* Determine if application is in production environment.
*
* @return bool
*/
public function isProduction()
{
return $this['env'] === 'production';
}
/**
* Detect the application's current environment.
*
* @param \Closure $callback
* @return string
*/
public function detectEnvironment(Closure $callback)
{
$args = $this->runningInConsole() && $_SERVER['argv']
? $_SERVER['argv']
: null;
return $this['env'] = (new EnvironmentDetector)->detect($callback, $args);
}
/**
* Determine if the application is running in the console.
*
* @return bool
*/
public function runningInConsole()
{
if ($this->isRunningInConsole === null) {
$this->isRunningInConsole = Env::get('APP_RUNNING_IN_CONSOLE') ?? (\PHP_SAPI === 'cli' || \PHP_SAPI === 'phpdbg');
}
return $this->isRunningInConsole;
}
/**
* Determine if the application is running unit tests.
*
* @return bool
*/
public function runningUnitTests()
{
return $this['env'] === 'testing';
}
/**
* Register all of the configured providers.
*
* @return void
*/
public function registerConfiguredProviders()
{
$providers = Collection::make($this->config['app.providers'])
->partition(function ($provider) {
return strpos($provider, 'Illuminate\\') === 0;
});
$providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);
(new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
->load($providers->collapse()->toArray());
}
/**
* Register a service provider with the application.
*
* @param \Illuminate\Support\ServiceProvider|string $provider
* @param bool $force
* @return \Illuminate\Support\ServiceProvider
*/
public function register($provider, $force = false)
{
if (($registered = $this->getProvider($provider)) && ! $force) {
return $registered;
}
// If the given "provider" is a string, we will resolve it, passing in the
// application instance automatically for the developer. This is simply
// a more convenient way of specifying your service provider classes.
if (is_string($provider)) {
$provider = $this->resolveProvider($provider);
}
$provider->register();
// If there are bindings / singletons set as properties on the provider we
// will spin through them and register them with the application, which
// serves as a convenience layer while registering a lot of bindings.
if (property_exists($provider, 'bindings')) {
foreach ($provider->bindings as $key => $value) {
$this->bind($key, $value);
}
}
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;
}
/**
* Get the registered service provider instance if it exists.
*
* @param \Illuminate\Support\ServiceProvider|string $provider
* @return \Illuminate\Support\ServiceProvider|null
*/
public function getProvider($provider)
{
return array_values($this->getProviders($provider))[0] ?? null;
}
/**
* Get the registered service provider instances if any exist.
*
* @param \Illuminate\Support\ServiceProvider|string $provider
* @return array
*/
public function getProviders($provider)
{
$name = is_string($provider) ? $provider : get_class($provider);
return Arr::where($this->serviceProviders, function ($value) use ($name) {
return $value instanceof $name;
});
}
/**
* Resolve a service provider instance from the class name.
*
* @param string $provider
* @return \Illuminate\Support\ServiceProvider
*/
public function resolveProvider($provider)
{
return new $provider($this);
}
/**
* Mark the given provider as registered.
*
* @param \Illuminate\Support\ServiceProvider $provider
* @return void
*/
protected function markAsRegistered($provider)
{
$this->serviceProviders[] = $provider;
$this->loadedProviders[get_class($provider)] = true;
}
/**
* Load and boot all of the remaining deferred providers.
*
* @return void
*/
public function loadDeferredProviders()
{
// We will simply spin through each of the deferred providers and register each
// one and boot them if the application has booted. This should make each of
// the remaining services available to this application for immediate use.
foreach ($this->deferredServices as $service => $provider) {
$this->loadDeferredProvider($service);
}
$this->deferredServices = [];
}
/**
* Load the provider for a deferred service.
*
* @param string $service
* @return void
*/
public function loadDeferredProvider($service)
{
if (! $this->isDeferredService($service)) {
return;
}
$provider = $this->deferredServices[$service];
// If the service provider has not already been loaded and registered we can
// register it with the application and remove the service from this list
// of deferred services, since it will already be loaded on subsequent.
if (! isset($this->loadedProviders[$provider])) {
$this->registerDeferredProvider($provider, $service);
}
}
/**
* Register a deferred provider and service.
*
* @param string $provider
* @param string|null $service
* @return void
*/
public function registerDeferredProvider($provider, $service = null)
{
// Once the provider that provides the deferred service has been registered we
// will remove it from our local list of the deferred services with related
// providers so that this container does not try to resolve it out again.
if ($service) {
unset($this->deferredServices[$service]);
}
$this->register($instance = new $provider($this));
if (! $this->isBooted()) {
$this->booting(function () use ($instance) {
$this->bootProvider($instance);
});
}
}
/**
* Resolve the given type from the container.
*
* @param string $abstract
* @param array $parameters
* @return mixed
*/
public function make($abstract, array $parameters = [])
{
$this->loadDeferredProviderIfNeeded($abstract = $this->getAlias($abstract));
return parent::make($abstract, $parameters);
}
/**
* Resolve the given type from the container.
*
* @param string $abstract
* @param array $parameters
* @param bool $raiseEvents
* @return mixed
*/
protected function resolve($abstract, $parameters = [], $raiseEvents = true)
{
$this->loadDeferredProviderIfNeeded($abstract = $this->getAlias($abstract));
return parent::resolve($abstract, $parameters, $raiseEvents);
}
/**
* Load the deferred provider if the given type is a deferred service and the instance has not been loaded.
*
* @param string $abstract
* @return void
*/
protected function loadDeferredProviderIfNeeded($abstract)
{
if ($this->isDeferredService($abstract) && ! isset($this->instances[$abstract])) {
$this->loadDeferredProvider($abstract);
}
}
/**
* Determine if the given abstract type has been bound.
*
* @param string $abstract
* @return bool
*/
public function bound($abstract)
{
return $this->isDeferredService($abstract) || parent::bound($abstract);
}
/**
* Determine if the application has booted.
*
* @return bool
*/
public function isBooted()
{
return $this->booted;
}
/**
* Boot the application's service providers.
*
* @return void
*/
public function boot()
{
if ($this->isBooted()) {
return;
}
// Once the application has booted we will also fire some "booted" callbacks
// for any listeners that need to do work after this initial booting gets
// finished. This is useful when ordering the boot-up processes we run.
$this->fireAppCallbacks($this->bootingCallbacks);
array_walk($this->serviceProviders, function ($p) {
$this->bootProvider($p);
});
$this->booted = true;
$this->fireAppCallbacks($this->bootedCallbacks);
}
/**
* Boot the given service provider.
*
* @param \Illuminate\Support\ServiceProvider $provider
* @return mixed
*/
protected function bootProvider(ServiceProvider $provider)
{
if (method_exists($provider, 'boot')) {
return $this->call([$provider, 'boot']);
}
}
/**
* Register a new boot listener.
*
* @param callable $callback
* @return void
*/
public function booting($callback)
{
$this->bootingCallbacks[] = $callback;
}
/**
* Register a new "booted" listener.
*
* @param callable $callback
* @return void
*/
public function booted($callback)
{
$this->bootedCallbacks[] = $callback;
if ($this->isBooted()) {
$this->fireAppCallbacks([$callback]);
}
}
/**
* Call the booting callbacks for the application.
*
* @param callable[] $callbacks
* @return void
*/
protected function fireAppCallbacks(array $callbacks)
{
foreach ($callbacks as $callback) {
$callback($this);
}
}
/**
* {@inheritdoc}
*/
public function handle(SymfonyRequest $request, $type = self::MASTER_REQUEST, $catch = true)
{
return $this[HttpKernelContract::class]->handle(Request::createFromBase($request));
}
/**
* Determine if middleware has been disabled for the application.
*
* @return bool
*/
public function shouldSkipMiddleware()
{
return $this->bound('middleware.disable') &&
$this->make('middleware.disable') === true;
}
/**
* Get the path to the cached services.php file.
*
* @return string
*/
public function getCachedServicesPath()
{
return $this->normalizeCachePath('APP_SERVICES_CACHE', 'cache/services.php');
}
/**
* Get the path to the cached packages.php file.
*
* @return string
*/
public function getCachedPackagesPath()
{
return $this->normalizeCachePath('APP_PACKAGES_CACHE', 'cache/packages.php');
}
/**
* Determine if the application configuration is cached.
*
* @return bool
*/
public function configurationIsCached()
{
return file_exists($this->getCachedConfigPath());
}
/**
* Get the path to the configuration cache file.
*
* @return string
*/
public function getCachedConfigPath()
{
return $this->normalizeCachePath('APP_CONFIG_CACHE', 'cache/config.php');
}
/**
* Determine if the application routes are cached.
*
* @return bool
*/
public function routesAreCached()
{
return $this['files']->exists($this->getCachedRoutesPath());
}
/**
* Get the path to the routes cache file.
*
* @return string
*/
public function getCachedRoutesPath()
{
return $this->normalizeCachePath('APP_ROUTES_CACHE', 'cache/routes.php');
}
/**
* Determine if the application events are cached.
*
* @return bool
*/
public function eventsAreCached()
{
return $this['files']->exists($this->getCachedEventsPath());
}
/**
* Get the path to the events cache file.
*
* @return string
*/
public function getCachedEventsPath()
{
return $this->normalizeCachePath('APP_EVENTS_CACHE', 'cache/events.php');
}
/**
* Normalize a relative or absolute path to a cache file.
*
* @param string $key
* @param string $default
* @return string
*/
protected function normalizeCachePath($key, $default)
{
if (is_null($env = Env::get($key))) {
return $this->bootstrapPath($default);
}
return Str::startsWith($env, '/')
? $env
: $this->basePath($env);
}
/**
* Determine if the application is currently down for maintenance.
*
* @return bool
*/
public function isDownForMaintenance()
{
return file_exists($this->storagePath().'/framework/down');
}
/**
* Throw an HttpException with the given data.
*
* @param int $code
* @param string $message
* @param array $headers
* @return void
*
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
public function abort($code, $message = '', array $headers = [])
{
if ($code == 404) {
throw new NotFoundHttpException($message);
}
throw new HttpException($code, $message, null, $headers);
}
/**
* Register a terminating callback with the application.
*
* @param callable|string $callback
* @return $this
*/
public function terminating($callback)
{
$this->terminatingCallbacks[] = $callback;
return $this;
}
/**
* Terminate the application.
*
* @return void
*/
public function terminate()
{
foreach ($this->terminatingCallbacks as $terminating) {
$this->call($terminating);
}
}
/**
* Get the service providers that have been loaded.
*
* @return array
*/
public function getLoadedProviders()
{
return $this->loadedProviders;
}
/**
* Get the application's deferred services.
*
* @return array
*/
public function getDeferredServices()
{
return $this->deferredServices;
}
/**
* Set the application's deferred services.
*
* @param array $services
* @return void
*/
public function setDeferredServices(array $services)
{
$this->deferredServices = $services;
}
/**
* Add an array of services to the application's deferred services.
*
* @param array $services
* @return void
*/
public function addDeferredServices(array $services)
{
$this->deferredServices = array_merge($this->deferredServices, $services);
}
/**
* Determine if the given service is a deferred service.
*
* @param string $service
* @return bool
*/
public function isDeferredService($service)
{
return isset($this->deferredServices[$service]);
}
/**
* Configure the real-time facade namespace.
*
* @param string $namespace
* @return void
*/
public function provideFacades($namespace)
{
AliasLoader::setFacadeNamespace($namespace);
}
/**
* Get the current application locale.
*
* @return string
*/
public function getLocale()
{
return $this['config']->get('app.locale');
}
/**
* Set the current application locale.
*
* @param string $locale
* @return void
*/
public function setLocale($locale)
{
$this['config']->set('app.locale', $locale);
$this['translator']->setLocale($locale);
$this['events']->dispatch(new LocaleUpdated($locale));
}
/**
* Determine if application locale is the given locale.
*
* @param string $locale
* @return bool
*/
public function isLocale($locale)
{
return $this->getLocale() == $locale;
}
/**
* Register the core class aliases in the container.
*
* @return void
*/
public function registerCoreContainerAliases()
{
foreach ([
'app' => [self::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class],
'auth' => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
'auth.driver' => [\Illuminate\Contracts\Auth\Guard::class],
'blade.compiler' => [\Illuminate\View\Compilers\BladeCompiler::class],
'cache' => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
'cache.store' => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class, \Psr\SimpleCache\CacheInterface::class],
'cache.psr6' => [\Symfony\Component\Cache\Adapter\Psr16Adapter::class, \Symfony\Component\Cache\Adapter\AdapterInterface::class, \Psr\Cache\CacheItemPoolInterface::class],
'config' => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\Repository::class],
'cookie' => [\Illuminate\Cookie\CookieJar::class, \Illuminate\Contracts\Cookie\Factory::class, \Illuminate\Contracts\Cookie\QueueingFactory::class],
'encrypter' => [\Illuminate\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\Encrypter::class],
'db' => [\Illuminate\Database\DatabaseManager::class, \Illuminate\Database\ConnectionResolverInterface::class],
'db.connection' => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
'events' => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class],
'files' => [\Illuminate\Filesystem\Filesystem::class],
'filesystem' => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class],
'filesystem.disk' => [\Illuminate\Contracts\Filesystem\Filesystem::class],
'filesystem.cloud' => [\Illuminate\Contracts\Filesystem\Cloud::class],
'hash' => [\Illuminate\Hashing\HashManager::class],
'hash.driver' => [\Illuminate\Contracts\Hashing\Hasher::class],
'translator' => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class],
'log' => [\Illuminate\Log\LogManager::class, \Psr\Log\LoggerInterface::class],
'mailer' => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class],
'auth.password' => [\Illuminate\Auth\Passwords\PasswordBrokerManager::class, \Illuminate\Contracts\Auth\PasswordBrokerFactory::class],
'auth.password.broker' => [\Illuminate\Auth\Passwords\PasswordBroker::class, \Illuminate\Contracts\Auth\PasswordBroker::class],
'queue' => [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class],
'queue.connection' => [\Illuminate\Contracts\Queue\Queue::class],
'queue.failer' => [\Illuminate\Queue\Failed\FailedJobProviderInterface::class],
'redirect' => [\Illuminate\Routing\Redirector::class],
'redis' => [\Illuminate\Redis\RedisManager::class, \Illuminate\Contracts\Redis\Factory::class],
'redis.connection' => [\Illuminate\Redis\Connections\Connection::class, \Illuminate\Contracts\Redis\Connection::class],
'request' => [\Illuminate\Http\Request::class, \Symfony\Component\HttpFoundation\Request::class],
'router' => [\Illuminate\Routing\Router::class, \Illuminate\Contracts\Routing\Registrar::class, \Illuminate\Contracts\Routing\BindingRegistrar::class],
'session' => [\Illuminate\Session\SessionManager::class],
'session.store' => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class],
'url' => [\Illuminate\Routing\UrlGenerator::class, \Illuminate\Contracts\Routing\UrlGenerator::class],
'validator' => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class],
'view' => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
] as $key => $aliases) {
foreach ($aliases as $alias) {
$this->alias($key, $alias);
}
}
}
/**
* Flush the container of all bindings and resolved instances.
*
* @return void
*/
public function flush()
{
parent::flush();
$this->buildStack = [];
$this->loadedProviders = [];
$this->bootedCallbacks = [];
$this->bootingCallbacks = [];
$this->deferredServices = [];
$this->reboundCallbacks = [];
$this->serviceProviders = [];
$this->resolvingCallbacks = [];
$this->terminatingCallbacks = [];
$this->afterResolvingCallbacks = [];
$this->globalResolvingCallbacks = [];
}
/**
* Get the application namespace.
*
* @return string
*
* @throws \RuntimeException
*/
public function getNamespace()
{
if (! is_null($this->namespace)) {
return $this->namespace;
}
$composer = json_decode(file_get_contents($this->basePath('composer.json')), true);
foreach ((array) data_get($composer, 'autoload.psr-4') as $namespace => $path) {
foreach ((array) $path as $pathChoice) {
if (realpath($this->path()) === realpath($this->basePath($pathChoice))) {
return $this->namespace = $namespace;
}
}
}
throw new RuntimeException('Unable to detect application namespace.');
}
}
父类 Illuminate\Container\Container 源码:
<?php
namespace Illuminate\Container;
use ArrayAccess;
use Closure;
use Exception;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Contracts\Container\Container as ContainerContract;
use LogicException;
use ReflectionClass;
use ReflectionException;
use ReflectionParameter;
class Container implements ArrayAccess, ContainerContract
{
/**
* The current globally available container (if any).
*
* @var static
*/
protected static $instance;
/**
* An array of the types that have been resolved.
*
* @var bool[]
*/
protected $resolved = [];
/**
* The container's bindings.
*
* @var array[]
*/
protected $bindings = [];
/**
* The container's method bindings.
*
* @var \Closure[]
*/
protected $methodBindings = [];
/**
* The container's shared instances.
*
* @var object[]
*/
protected $instances = [];
/**
* The registered type aliases.
*
* @var string[]
*/
protected $aliases = [];
/**
* The registered aliases keyed by the abstract name.
*
* @var array[]
*/
protected $abstractAliases = [];
/**
* The extension closures for services.
*
* @var array[]
*/
protected $extenders = [];
/**
* All of the registered tags.
*
* @var array[]
*/
protected $tags = [];
/**
* The stack of concretions currently being built.
*
* @var array[]
*/
protected $buildStack = [];
/**
* The parameter override stack.
*
* @var array[]
*/
protected $with = [];
/**
* The contextual binding map.
*
* @var array[]
*/
public $contextual = [];
/**
* All of the registered rebound callbacks.
*
* @var array[]
*/
protected $reboundCallbacks = [];
/**
* All of the global resolving callbacks.
*
* @var \Closure[]
*/
protected $globalResolvingCallbacks = [];
/**
* All of the global after resolving callbacks.
*
* @var \Closure[]
*/
protected $globalAfterResolvingCallbacks = [];
/**
* All of the resolving callbacks by class type.
*
* @var array[]
*/
protected $resolvingCallbacks = [];
/**
* All of the after resolving callbacks by class type.
*
* @var array[]
*/
protected $afterResolvingCallbacks = [];
/**
* Define a contextual binding.
*
* @param array|string $concrete
* @return \Illuminate\Contracts\Container\ContextualBindingBuilder
*/
public function when($concrete)
{
$aliases = [];
foreach (Util::arrayWrap($concrete) as $c) {
$aliases[] = $this->getAlias($c);
}
return new ContextualBindingBuilder($this, $aliases);
}
/**
* Determine if the given abstract type has been bound.
*
* @param string $abstract
* @return bool
*/
public function bound($abstract)
{
return isset($this->bindings[$abstract]) ||
isset($this->instances[$abstract]) ||
$this->isAlias($abstract);
}
/**
* {@inheritdoc}
*/
public function has($id)
{
return $this->bound($id);
}
/**
* Determine if the given abstract type has been resolved.
*
* @param string $abstract
* @return bool
*/
public function resolved($abstract)
{
if ($this->isAlias($abstract)) {
$abstract = $this->getAlias($abstract);
}
return isset($this->resolved[$abstract]) ||
isset($this->instances[$abstract]);
}
/**
* Determine if a given type is shared.
*
* @param string $abstract
* @return bool
*/
public function isShared($abstract)
{
return isset($this->instances[$abstract]) ||
(isset($this->bindings[$abstract]['shared']) &&
$this->bindings[$abstract]['shared'] === true);
}
/**
* Determine if a given string is an alias.
*
* @param string $name
* @return bool
*/
public function isAlias($name)
{
return isset($this->aliases[$name]);
}
/**
* Register a binding with the container.
*
* @param string $abstract
* @param \Closure|string|null $concrete
* @param bool $shared
* @return void
*/
public function bind($abstract, $concrete = null, $shared = false)
{
$this->dropStaleInstances($abstract);
// If no concrete type was given, we will simply set the concrete type to the
// abstract type. After that, the concrete type to be registered as shared
// without being forced to state their classes in both of the parameters.
if (is_null($concrete)) {
$concrete = $abstract;
}
// If the factory is not a Closure, it means it is just a class name which is
// bound into this container to the abstract type and we will just wrap it
// up inside its own Closure to give us more convenience when extending.
if (! $concrete instanceof Closure) {
$concrete = $this->getClosure($abstract, $concrete);
}
$this->bindings[$abstract] = compact('concrete', 'shared');
// If the abstract type was already resolved in this container we'll fire the
// rebound listener so that any objects which have already gotten resolved
// can have their copy of the object updated via the listener callbacks.
if ($this->resolved($abstract)) {
$this->rebound($abstract);
}
}
/**
* Get the Closure to be used when building a type.
*
* @param string $abstract
* @param string $concrete
* @return \Closure
*/
protected function getClosure($abstract, $concrete)
{
return function ($container, $parameters = []) use ($abstract, $concrete) {
if ($abstract == $concrete) {
return $container->build($concrete);
}
return $container->resolve(
$concrete, $parameters, $raiseEvents = false
);
};
}
/**
* Determine if the container has a method binding.
*
* @param string $method
* @return bool
*/
public function hasMethodBinding($method)
{
return isset($this->methodBindings[$method]);
}
/**
* Bind a callback to resolve with Container::call.
*
* @param array|string $method
* @param \Closure $callback
* @return void
*/
public function bindMethod($method, $callback)
{
$this->methodBindings[$this->parseBindMethod($method)] = $callback;
}
/**
* Get the method to be bound in class@method format.
*
* @param array|string $method
* @return string
*/
protected function parseBindMethod($method)
{
if (is_array($method)) {
return $method[0].'@'.$method[1];
}
return $method;
}
/**
* Get the method binding for the given method.
*
* @param string $method
* @param mixed $instance
* @return mixed
*/
public function callMethodBinding($method, $instance)
{
return call_user_func($this->methodBindings[$method], $instance, $this);
}
/**
* Add a contextual binding to the container.
*
* @param string $concrete
* @param string $abstract
* @param \Closure|string $implementation
* @return void
*/
public function addContextualBinding($concrete, $abstract, $implementation)
{
$this->contextual[$concrete][$this->getAlias($abstract)] = $implementation;
}
/**
* Register a binding if it hasn't already been registered.
*
* @param string $abstract
* @param \Closure|string|null $concrete
* @param bool $shared
* @return void
*/
public function bindIf($abstract, $concrete = null, $shared = false)
{
if (! $this->bound($abstract)) {
$this->bind($abstract, $concrete, $shared);
}
}
/**
* Register a shared binding in the container.
*
* @param string $abstract
* @param \Closure|string|null $concrete
* @return void
*/
public function singleton($abstract, $concrete = null)
{
$this->bind($abstract, $concrete, true);
}
/**
* Register a shared binding if it hasn't already been registered.
*
* @param string $abstract
* @param \Closure|string|null $concrete
* @return void
*/
public function singletonIf($abstract, $concrete = null)
{
if (! $this->bound($abstract)) {
$this->singleton($abstract, $concrete);
}
}
/**
* "Extend" an abstract type in the container.
*
* @param string $abstract
* @param \Closure $closure
* @return void
*
* @throws \InvalidArgumentException
*/
public function extend($abstract, Closure $closure)
{
$abstract = $this->getAlias($abstract);
if (isset($this->instances[$abstract])) {
$this->instances[$abstract] = $closure($this->instances[$abstract], $this);
$this->rebound($abstract);
} else {
$this->extenders[$abstract][] = $closure;
if ($this->resolved($abstract)) {
$this->rebound($abstract);
}
}
}
/**
* Register an existing instance as shared in the container.
*
* @param string $abstract
* @param mixed $instance
* @return mixed
*/
public function instance($abstract, $instance)
{
$this->removeAbstractAlias($abstract);
$isBound = $this->bound($abstract);
unset($this->aliases[$abstract]);
// We'll check to determine if this type has been bound before, and if it has
// we will fire the rebound callbacks registered with the container and it
// can be updated with consuming classes that have gotten resolved here.
$this->instances[$abstract] = $instance;
if ($isBound) {
$this->rebound($abstract);
}
return $instance;
}
/**
* Remove an alias from the contextual binding alias cache.
*
* @param string $searched
* @return void
*/
protected function removeAbstractAlias($searched)
{
if (! isset($this->aliases[$searched])) {
return;
}
foreach ($this->abstractAliases as $abstract => $aliases) {
foreach ($aliases as $index => $alias) {
if ($alias == $searched) {
unset($this->abstractAliases[$abstract][$index]);
}
}
}
}
/**
* Assign a set of tags to a given binding.
*
* @param array|string $abstracts
* @param array|mixed ...$tags
* @return void
*/
public function tag($abstracts, $tags)
{
$tags = is_array($tags) ? $tags : array_slice(func_get_args(), 1);
foreach ($tags as $tag) {
if (! isset($this->tags[$tag])) {
$this->tags[$tag] = [];
}
foreach ((array) $abstracts as $abstract) {
$this->tags[$tag][] = $abstract;
}
}
}
/**
* Resolve all of the bindings for a given tag.
*
* @param string $tag
* @return iterable
*/
public function tagged($tag)
{
if (! isset($this->tags[$tag])) {
return [];
}
return new RewindableGenerator(function () use ($tag) {
foreach ($this->tags[$tag] as $abstract) {
yield $this->make($abstract);
}
}, count($this->tags[$tag]));
}
/**
* Alias a type to a different name.
*
* @param string $abstract
* @param string $alias
* @return void
*
* @throws \LogicException
*/
public function alias($abstract, $alias)
{
if ($alias === $abstract) {
throw new LogicException("[{$abstract}] is aliased to itself.");
}
$this->aliases[$alias] = $abstract;
$this->abstractAliases[$abstract][] = $alias;
}
/**
* Bind a new callback to an abstract's rebind event.
*
* @param string $abstract
* @param \Closure $callback
* @return mixed
*/
public function rebinding($abstract, Closure $callback)
{
$this->reboundCallbacks[$abstract = $this->getAlias($abstract)][] = $callback;
if ($this->bound($abstract)) {
return $this->make($abstract);
}
}
/**
* Refresh an instance on the given target and method.
*
* @param string $abstract
* @param mixed $target
* @param string $method
* @return mixed
*/
public function refresh($abstract, $target, $method)
{
return $this->rebinding($abstract, function ($app, $instance) use ($target, $method) {
$target->{$method}($instance);
});
}
/**
* Fire the "rebound" callbacks for the given abstract type.
*
* @param string $abstract
* @return void
*/
protected function rebound($abstract)
{
$instance = $this->make($abstract);
foreach ($this->getReboundCallbacks($abstract) as $callback) {
call_user_func($callback, $this, $instance);
}
}
/**
* Get the rebound callbacks for a given type.
*
* @param string $abstract
* @return array
*/
protected function getReboundCallbacks($abstract)
{
return $this->reboundCallbacks[$abstract] ?? [];
}
/**
* Wrap the given closure such that its dependencies will be injected when executed.
*
* @param \Closure $callback
* @param array $parameters
* @return \Closure
*/
public function wrap(Closure $callback, array $parameters = [])
{
return function () use ($callback, $parameters) {
return $this->call($callback, $parameters);
};
}
/**
* Call the given Closure / class@method and inject its dependencies.
*
* @param callable|string $callback
* @param array $parameters
* @param string|null $defaultMethod
* @return mixed
*/
public function call($callback, array $parameters = [], $defaultMethod = null)
{
return BoundMethod::call($this, $callback, $parameters, $defaultMethod);
}
/**
* Get a closure to resolve the given type from the container.
*
* @param string $abstract
* @return \Closure
*/
public function factory($abstract)
{
return function () use ($abstract) {
return $this->make($abstract);
};
}
/**
* An alias function name for make().
*
* @param string $abstract
* @param array $parameters
* @return mixed
*/
public function makeWith($abstract, array $parameters = [])
{
return $this->make($abstract, $parameters);
}
/**
* Resolve the given type from the container.
*
* @param string $abstract
* @param array $parameters
* @return mixed
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function make($abstract, array $parameters = [])
{
return $this->resolve($abstract, $parameters);
}
/**
* {@inheritdoc}
*/
public function get($id)
{
try {
return $this->resolve($id);
} catch (Exception $e) {
if ($this->has($id)) {
throw $e;
}
throw new EntryNotFoundException($id, $e->getCode(), $e);
}
}
/**
* Resolve the given type from the container.
*
* @param string $abstract
* @param array $parameters
* @param bool $raiseEvents
* @return mixed
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
protected function resolve($abstract, $parameters = [], $raiseEvents = true)
{
$abstract = $this->getAlias($abstract);
$needsContextualBuild = ! empty($parameters) || ! is_null(
$this->getContextualConcrete($abstract)
);
// If an instance of the type is currently being managed as a singleton we'll
// just return an existing instance instead of instantiating new instances
// so the developer can keep using the same objects instance every time.
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
return $this->instances[$abstract];
}
$this->with[] = $parameters;
$concrete = $this->getConcrete($abstract);
// We're ready to instantiate an instance of the concrete type registered for
// the binding. This will instantiate the types, as well as resolve any of
// its "nested" dependencies recursively until all have gotten resolved.
if ($this->isBuildable($concrete, $abstract)) {
$object = $this->build($concrete);
} else {
$object = $this->make($concrete);
}
// If we defined any extenders for this type, we'll need to spin through them
// and apply them to the object being built. This allows for the extension
// of services, such as changing configuration or decorating the object.
foreach ($this->getExtenders($abstract) as $extender) {
$object = $extender($object, $this);
}
// If the requested type is registered as a singleton we'll want to cache off
// the instances in "memory" so we can return it later without creating an
// entirely new instance of an object on each subsequent request for it.
if ($this->isShared($abstract) && ! $needsContextualBuild) {
$this->instances[$abstract] = $object;
}
if ($raiseEvents) {
$this->fireResolvingCallbacks($abstract, $object);
}
// Before returning, we will also set the resolved flag to "true" and pop off
// the parameter overrides for this build. After those two things are done
// we will be ready to return back the fully constructed class instance.
$this->resolved[$abstract] = true;
array_pop($this->with);
return $object;
}
/**
* Get the concrete type for a given abstract.
*
* @param string $abstract
* @return mixed
*/
protected function getConcrete($abstract)
{
if (! is_null($concrete = $this->getContextualConcrete($abstract))) {
return $concrete;
}
// If we don't have a registered resolver or concrete for the type, we'll just
// assume each type is a concrete name and will attempt to resolve it as is
// since the container should be able to resolve concretes automatically.
if (isset($this->bindings[$abstract])) {
return $this->bindings[$abstract]['concrete'];
}
return $abstract;
}
/**
* Get the contextual concrete binding for the given abstract.
*
* @param string $abstract
* @return \Closure|string|null
*/
protected function getContextualConcrete($abstract)
{
if (! is_null($binding = $this->findInContextualBindings($abstract))) {
return $binding;
}
// Next we need to see if a contextual binding might be bound under an alias of the
// given abstract type. So, we will need to check if any aliases exist with this
// type and then spin through them and check for contextual bindings on these.
if (empty($this->abstractAliases[$abstract])) {
return;
}
foreach ($this->abstractAliases[$abstract] as $alias) {
if (! is_null($binding = $this->findInContextualBindings($alias))) {
return $binding;
}
}
}
/**
* Find the concrete binding for the given abstract in the contextual binding array.
*
* @param string $abstract
* @return \Closure|string|null
*/
protected function findInContextualBindings($abstract)
{
return $this->contextual[end($this->buildStack)][$abstract] ?? null;
}
/**
* Determine if the given concrete is buildable.
*
* @param mixed $concrete
* @param string $abstract
* @return bool
*/
protected function isBuildable($concrete, $abstract)
{
return $concrete === $abstract || $concrete instanceof Closure;
}
/**
* Instantiate a concrete instance of the given type.
*
* @param string $concrete
* @return mixed
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function build($concrete)
{
// If the concrete type is actually a Closure, we will just execute it and
// hand back the results of the functions, which allows functions to be
// used as resolvers for more fine-tuned resolution of these objects.
if ($concrete instanceof Closure) {
return $concrete($this, $this->getLastParameterOverride());
}
try {
$reflector = new ReflectionClass($concrete);
} catch (ReflectionException $e) {
throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e);
}
// If the type is not instantiable, the developer is attempting to resolve
// an abstract type such as an Interface or Abstract Class and there is
// no binding registered for the abstractions so we need to bail out.
if (! $reflector->isInstantiable()) {
return $this->notInstantiable($concrete);
}
$this->buildStack[] = $concrete;
$constructor = $reflector->getConstructor();
// If there are no constructors, that means there are no dependencies then
// we can just resolve the instances of the objects right away, without
// resolving any other types or dependencies out of these containers.
if (is_null($constructor)) {
array_pop($this->buildStack);
return new $concrete;
}
$dependencies = $constructor->getParameters();
// Once we have all the constructor's parameters we can create each of the
// dependency instances and then use the reflection instances to make a
// new instance of this class, injecting the created dependencies in.
try {
$instances = $this->resolveDependencies($dependencies);
} catch (BindingResolutionException $e) {
array_pop($this->buildStack);
throw $e;
}
array_pop($this->buildStack);
return $reflector->newInstanceArgs($instances);
}
/**
* Resolve all of the dependencies from the ReflectionParameters.
*
* @param \ReflectionParameter[] $dependencies
* @return array
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
protected function resolveDependencies(array $dependencies)
{
$results = [];
foreach ($dependencies as $dependency) {
// If this dependency has a override for this particular build we will use
// that instead as the value. Otherwise, we will continue with this run
// of resolutions and let reflection attempt to determine the result.
if ($this->hasParameterOverride($dependency)) {
$results[] = $this->getParameterOverride($dependency);
continue;
}
// If the class is null, it means the dependency is a string or some other
// primitive type which we can not resolve since it is not a class and
// we will just bomb out with an error since we have no-where to go.
$results[] = is_null(Util::getParameterClassName($dependency))
? $this->resolvePrimitive($dependency)
: $this->resolveClass($dependency);
}
return $results;
}
/**
* Determine if the given dependency has a parameter override.
*
* @param \ReflectionParameter $dependency
* @return bool
*/
protected function hasParameterOverride($dependency)
{
return array_key_exists(
$dependency->name, $this->getLastParameterOverride()
);
}
/**
* Get a parameter override for a dependency.
*
* @param \ReflectionParameter $dependency
* @return mixed
*/
protected function getParameterOverride($dependency)
{
return $this->getLastParameterOverride()[$dependency->name];
}
/**
* Get the last parameter override.
*
* @return array
*/
protected function getLastParameterOverride()
{
return count($this->with) ? end($this->with) : [];
}
/**
* Resolve a non-class hinted primitive dependency.
*
* @param \ReflectionParameter $parameter
* @return mixed
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
protected function resolvePrimitive(ReflectionParameter $parameter)
{
if (! is_null($concrete = $this->getContextualConcrete('$'.$parameter->getName()))) {
return $concrete instanceof Closure ? $concrete($this) : $concrete;
}
if ($parameter->isDefaultValueAvailable()) {
return $parameter->getDefaultValue();
}
$this->unresolvablePrimitive($parameter);
}
/**
* Resolve a class based dependency from the container.
*
* @param \ReflectionParameter $parameter
* @return mixed
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
protected function resolveClass(ReflectionParameter $parameter)
{
try {
return $this->make(Util::getParameterClassName($parameter));
}
// If we can not resolve the class instance, we will check to see if the value
// is optional, and if it is we will return the optional parameter value as
// the value of the dependency, similarly to how we do this with scalars.
catch (BindingResolutionException $e) {
if ($parameter->isOptional()) {
return $parameter->getDefaultValue();
}
throw $e;
}
}
/**
* Throw an exception that the concrete is not instantiable.
*
* @param string $concrete
* @return void
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
protected function notInstantiable($concrete)
{
if (! empty($this->buildStack)) {
$previous = implode(', ', $this->buildStack);
$message = "Target [$concrete] is not instantiable while building [$previous].";
} else {
$message = "Target [$concrete] is not instantiable.";
}
throw new BindingResolutionException($message);
}
/**
* Throw an exception for an unresolvable primitive.
*
* @param \ReflectionParameter $parameter
* @return void
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
protected function unresolvablePrimitive(ReflectionParameter $parameter)
{
$message = "Unresolvable dependency resolving [$parameter] in class {$parameter->getDeclaringClass()->getName()}";
throw new BindingResolutionException($message);
}
/**
* Register a new resolving callback.
*
* @param \Closure|string $abstract
* @param \Closure|null $callback
* @return void
*/
public function resolving($abstract, Closure $callback = null)
{
if (is_string($abstract)) {
$abstract = $this->getAlias($abstract);
}
if (is_null($callback) && $abstract instanceof Closure) {
$this->globalResolvingCallbacks[] = $abstract;
} else {
$this->resolvingCallbacks[$abstract][] = $callback;
}
}
/**
* Register a new after resolving callback for all types.
*
* @param \Closure|string $abstract
* @param \Closure|null $callback
* @return void
*/
public function afterResolving($abstract, Closure $callback = null)
{
if (is_string($abstract)) {
$abstract = $this->getAlias($abstract);
}
if ($abstract instanceof Closure && is_null($callback)) {
$this->globalAfterResolvingCallbacks[] = $abstract;
} else {
$this->afterResolvingCallbacks[$abstract][] = $callback;
}
}
/**
* Fire all of the resolving callbacks.
*
* @param string $abstract
* @param mixed $object
* @return void
*/
protected function fireResolvingCallbacks($abstract, $object)
{
$this->fireCallbackArray($object, $this->globalResolvingCallbacks);
$this->fireCallbackArray(
$object, $this->getCallbacksForType($abstract, $object, $this->resolvingCallbacks)
);
$this->fireAfterResolvingCallbacks($abstract, $object);
}
/**
* Fire all of the after resolving callbacks.
*
* @param string $abstract
* @param mixed $object
* @return void
*/
protected function fireAfterResolvingCallbacks($abstract, $object)
{
$this->fireCallbackArray($object, $this->globalAfterResolvingCallbacks);
$this->fireCallbackArray(
$object, $this->getCallbacksForType($abstract, $object, $this->afterResolvingCallbacks)
);
}
/**
* Get all callbacks for a given type.
*
* @param string $abstract
* @param object $object
* @param array $callbacksPerType
* @return array
*/
protected function getCallbacksForType($abstract, $object, array $callbacksPerType)
{
$results = [];
foreach ($callbacksPerType as $type => $callbacks) {
if ($type === $abstract || $object instanceof $type) {
$results = array_merge($results, $callbacks);
}
}
return $results;
}
/**
* Fire an array of callbacks with an object.
*
* @param mixed $object
* @param array $callbacks
* @return void
*/
protected function fireCallbackArray($object, array $callbacks)
{
foreach ($callbacks as $callback) {
$callback($object, $this);
}
}
/**
* Get the container's bindings.
*
* @return array
*/
public function getBindings()
{
return $this->bindings;
}
/**
* Get the alias for an abstract if available.
*
* @param string $abstract
* @return string
*/
public function getAlias($abstract)
{
if (! isset($this->aliases[$abstract])) {
return $abstract;
}
return $this->getAlias($this->aliases[$abstract]);
}
/**
* Get the extender callbacks for a given type.
*
* @param string $abstract
* @return array
*/
protected function getExtenders($abstract)
{
$abstract = $this->getAlias($abstract);
return $this->extenders[$abstract] ?? [];
}
/**
* Remove all of the extender callbacks for a given type.
*
* @param string $abstract
* @return void
*/
public function forgetExtenders($abstract)
{
unset($this->extenders[$this->getAlias($abstract)]);
}
/**
* Drop all of the stale instances and aliases.
*
* @param string $abstract
* @return void
*/
protected function dropStaleInstances($abstract)
{
unset($this->instances[$abstract], $this->aliases[$abstract]);
}
/**
* Remove a resolved instance from the instance cache.
*
* @param string $abstract
* @return void
*/
public function forgetInstance($abstract)
{
unset($this->instances[$abstract]);
}
/**
* Clear all of the instances from the container.
*
* @return void
*/
public function forgetInstances()
{
$this->instances = [];
}
/**
* Flush the container of all bindings and resolved instances.
*
* @return void
*/
public function flush()
{
$this->aliases = [];
$this->resolved = [];
$this->bindings = [];
$this->instances = [];
$this->abstractAliases = [];
}
/**
* Get the globally available instance of the container.
*
* @return static
*/
public static function getInstance()
{
if (is_null(static::$instance)) {
static::$instance = new static;
}
return static::$instance;
}
/**
* Set the shared instance of the container.
*
* @param \Illuminate\Contracts\Container\Container|null $container
* @return \Illuminate\Contracts\Container\Container|static
*/
public static function setInstance(ContainerContract $container = null)
{
return static::$instance = $container;
}
/**
* Determine if a given offset exists.
*
* @param string $key
* @return bool
*/
public function offsetExists($key)
{
return $this->bound($key);
}
/**
* Get the value at a given offset.
*
* @param string $key
* @return mixed
*/
public function offsetGet($key)
{
return $this->make($key);
}
/**
* Set the value at a given offset.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function offsetSet($key, $value)
{
$this->bind($key, $value instanceof Closure ? $value : function () use ($value) {
return $value;
});
}
/**
* Unset the value at a given offset.
*
* @param string $key
* @return void
*/
public function offsetUnset($key)
{
unset($this->bindings[$key], $this->instances[$key], $this->resolved[$key]);
}
/**
* Dynamically access container services.
*
* @param string $key
* @return mixed
*/
public function __get($key)
{
return $this[$key];
}
/**
* Dynamically set container services.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function __set($key, $value)
{
$this[$key] = $value;
}
}
Illuminate\Support\ServiceProvider 源码:
<?php
namespace Illuminate\Support;
use Illuminate\Console\Application as Artisan;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Database\Eloquent\Factory as ModelFactory;
abstract class ServiceProvider
{
/**
* The application instance.
*
* @var \Illuminate\Contracts\Foundation\Application
*/
protected $app;
/**
* The paths that should be published.
*
* @var array
*/
public static $publishes = [];
/**
* The paths that should be published by group.
*
* @var array
*/
public static $publishGroups = [];
/**
* Create a new service provider instance.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function __construct($app)
{
$this->app = $app;
}
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}
/**
* Merge the given configuration with the existing configuration.
*
* @param string $path
* @param string $key
* @return void
*/
protected function mergeConfigFrom($path, $key)
{
if (! $this->app->configurationIsCached()) {
$this->app['config']->set($key, array_merge(
require $path, $this->app['config']->get($key, [])
));
}
}
/**
* Load the given routes file if routes are not already cached.
*
* @param string $path
* @return void
*/
protected function loadRoutesFrom($path)
{
if (! $this->app->routesAreCached()) {
require $path;
}
}
/**
* Register a view file namespace.
*
* @param string|array $path
* @param string $namespace
* @return void
*/
protected function loadViewsFrom($path, $namespace)
{
$this->callAfterResolving('view', function ($view) use ($path, $namespace) {
if (isset($this->app->config['view']['paths']) &&
is_array($this->app->config['view']['paths'])) {
foreach ($this->app->config['view']['paths'] as $viewPath) {
if (is_dir($appPath = $viewPath.'/vendor/'.$namespace)) {
$view->addNamespace($namespace, $appPath);
}
}
}
$view->addNamespace($namespace, $path);
});
}
/**
* Register a translation file namespace.
*
* @param string $path
* @param string $namespace
* @return void
*/
protected function loadTranslationsFrom($path, $namespace)
{
$this->callAfterResolving('translator', function ($translator) use ($path, $namespace) {
$translator->addNamespace($namespace, $path);
});
}
/**
* Register a JSON translation file path.
*
* @param string $path
* @return void
*/
protected function loadJsonTranslationsFrom($path)
{
$this->callAfterResolving('translator', function ($translator) use ($path) {
$translator->addJsonPath($path);
});
}
/**
* Register database migration paths.
*
* @param array|string $paths
* @return void
*/
protected function loadMigrationsFrom($paths)
{
$this->callAfterResolving('migrator', function ($migrator) use ($paths) {
foreach ((array) $paths as $path) {
$migrator->path($path);
}
});
}
/**
* Register Eloquent model factory paths.
*
* @param array|string $paths
* @return void
*/
protected function loadFactoriesFrom($paths)
{
$this->callAfterResolving(ModelFactory::class, function ($factory) use ($paths) {
foreach ((array) $paths as $path) {
$factory->load($path);
}
});
}
/**
* Setup an after resolving listener, or fire immediately if already resolved.
*
* @param string $name
* @param callable $callback
* @return void
*/
protected function callAfterResolving($name, $callback)
{
$this->app->afterResolving($name, $callback);
if ($this->app->resolved($name)) {
$callback($this->app->make($name), $this->app);
}
}
/**
* Register paths to be published by the publish command.
*
* @param array $paths
* @param mixed $groups
* @return void
*/
protected function publishes(array $paths, $groups = null)
{
$this->ensurePublishArrayInitialized($class = static::class);
static::$publishes[$class] = array_merge(static::$publishes[$class], $paths);
foreach ((array) $groups as $group) {
$this->addPublishGroup($group, $paths);
}
}
/**
* Ensure the publish array for the service provider is initialized.
*
* @param string $class
* @return void
*/
protected function ensurePublishArrayInitialized($class)
{
if (! array_key_exists($class, static::$publishes)) {
static::$publishes[$class] = [];
}
}
/**
* Add a publish group / tag to the service provider.
*
* @param string $group
* @param array $paths
* @return void
*/
protected function addPublishGroup($group, $paths)
{
if (! array_key_exists($group, static::$publishGroups)) {
static::$publishGroups[$group] = [];
}
static::$publishGroups[$group] = array_merge(
static::$publishGroups[$group], $paths
);
}
/**
* Get the paths to publish.
*
* @param string|null $provider
* @param string|null $group
* @return array
*/
public static function pathsToPublish($provider = null, $group = null)
{
if (! is_null($paths = static::pathsForProviderOrGroup($provider, $group))) {
return $paths;
}
return collect(static::$publishes)->reduce(function ($paths, $p) {
return array_merge($paths, $p);
}, []);
}
/**
* Get the paths for the provider or group (or both).
*
* @param string|null $provider
* @param string|null $group
* @return array
*/
protected static function pathsForProviderOrGroup($provider, $group)
{
if ($provider && $group) {
return static::pathsForProviderAndGroup($provider, $group);
} elseif ($group && array_key_exists($group, static::$publishGroups)) {
return static::$publishGroups[$group];
} elseif ($provider && array_key_exists($provider, static::$publishes)) {
return static::$publishes[$provider];
} elseif ($group || $provider) {
return [];
}
}
/**
* Get the paths for the provider and group.
*
* @param string $provider
* @param string $group
* @return array
*/
protected static function pathsForProviderAndGroup($provider, $group)
{
if (! empty(static::$publishes[$provider]) && ! empty(static::$publishGroups[$group])) {
return array_intersect_key(static::$publishes[$provider], static::$publishGroups[$group]);
}
return [];
}
/**
* Get the service providers available for publishing.
*
* @return array
*/
public static function publishableProviders()
{
return array_keys(static::$publishes);
}
/**
* Get the groups available for publishing.
*
* @return array
*/
public static function publishableGroups()
{
return array_keys(static::$publishGroups);
}
/**
* Register the package's custom Artisan commands.
*
* @param array|mixed $commands
* @return void
*/
public function commands($commands)
{
$commands = is_array($commands) ? $commands : func_get_args();
Artisan::starting(function ($artisan) use ($commands) {
$artisan->resolveCommands($commands);
});
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return [];
}
/**
* Get the events that trigger this service provider to register.
*
* @return array
*/
public function when()
{
return [];
}
/**
* Determine if the provider is deferred.
*
* @return bool
*/
public function isDeferred()
{
return $this instanceof DeferrableProvider;
}
}
参考资料
Laravel 6 中文文档 https://learnku.com/docs/laravel/6.x/container/5131
深入剖析 Laravel 服务容器 https://learnku.com/articles/12575/deep-analysis-of-the-laravel-service-container
Inversion of Control Containers and the Dependency Injection pattern https://www.martinfowler.com/articles/injection.html
依赖注入系列教程 https://learnku.com/articles/11151/dependency-injection-series-tutorials
什么是依赖注入 https://learnku.com/articles/10738/what-is-dependency-injection
深入浅出依赖注入 https://learnku.com/articles/10928/deep-inflow-of-dependency-injection
是否需要使用依赖注入容器 https://learnku.com/articles/10773/do-you-need-to-use-a-dependency-injection-container