本文首发于 深入剖析 Laravel 服务容器,转载请注明出处。喜欢的朋友不要吝啬你们的赞同,谢谢。
之前在 深度挖掘 Laravel 生命周期 一文中,我们有去探究 Laravel 究竟是如何接收 HTTP 请求,又是如何生成响应并最终呈现给用户的工作原理。
本章将带领大家研究另一个 Laravel 框架的核心内容:「服务容器」。有阅读过 Laravel 文档 的朋友应该有注意到在「核心架构」篇章中包含了几个主题:生命周期、服务容器、服务提供者、Facades 和 Concracts.
今天就让我们一起来揭开「Laravel 服务容器」的神秘面纱。
提示:本文内容较长可能需要耗费较多的阅读时间,另外文中包含 Laravel 内核代码建议选择合适的 IDE 或文本编辑器进行源码阅读。
依赖注入基本概念
Laravel 服务容器是什么
Laravel 服务容器的使用方法
常用绑定方法
Laravel 服务容器实现原理
注册基础服务
管理所需创建的类及其依赖
如果您有阅读我的前作 深度挖掘 Laravel 生命周期 一文,你应该已经注意到「APP 容器」、「服务容器」、「绑定」和「解析」这些字眼。没错这些技术都和「Laravel 服务容器」有着紧密的联系。
在学习什么是「Laravel 服务容器」之前,如果您对「IoC(控制反转)」、「DI(依赖注入)」和「依赖注入容器」等相关知识还不够了解的话,建议先学习一下这些资料:
虽然,这些学习资料都有细致的讲解容器相关的概念。但介绍一下与「Laravel 服务容器」有关的基本概念仍然有必要。
这个小节会捎带讲解下「IoC(控制反转)」、「DI(依赖注入)」和「依赖注入容器」这些概念。
应用程序对需要使用的依赖「插件」在编译(编码)阶段仅依赖于接口的定义,到运行阶段由一个独立的组装模块(容器)完成对实现类的实例化工作,并将其「注射」到应用程序中称之为「依赖注入」。
一言以蔽之:面向接口编程。
至于如何实现面向接口编程,在 依赖注入系列教程 的前两篇中有实例演示,感兴趣的朋友可以去阅读这个教程。更多细节可以阅读 Inversion of Control Containers and the Dependency Injection pattern 和 深入浅出依赖注入。
在依赖注入过程中,由一个独立的组装模块(容器)完成对实现类的实例化工作,那么这个组装模块就是「依赖注入容器」。
通俗一点讲,使用「依赖注入容器」时无需人肉使用 new 关键字去实例化所依赖的「插件」,转而由「依赖注入容器」自动的完成一个模块的组装、配置、实例化等工作。
IoC 是 Inversion of Control 的简写,通常被称为控制反转,控制反转从字面上来说比较不容易被理解。
要掌握什么是「控制反转」需要整明白项目中「控制反转」究竟「反转」了哪方面的「控制」,它需要解决如何去定位(获取)服务所需要的依赖的实现。
实现控制反转时,通过将原先在模块内部完成具体实现类的实例化,移至模块的外部,然后再通过「依赖注入」的方式将具体实例「注入」到模块内即完成了对控制的反转操作。
「依赖注入」的结果就是「控制反转」的目的,也就说 控制反转 的最终目标是为了 实现项目的高内聚低耦合,而 实现这种目标 的方式则是通过 依赖注入 这种设计模式。
以上就是一些有关服务容器的一些基本概念。和我前面说的一样,本文不是一篇讲解依赖注入的文章,所以更多的细节需要大家自行去学习我之前列出的参考资料。
接下来才是今天的正餐,我将从以下几个角度讲解 Laravel 服务容器的相关内容:
在 Laravel 文档 中,有一段关于 Laravel 服务容器的介绍:
Laravel 服务容器是用于管理类的依赖和执行依赖注入的工具。依赖注入这个花俏名词实质上是指:类的依赖项通过构造函数,或者某些情况下通过「setter」方法「注入」到类中。
划下重点,「Laravel 服务容器」是用于 管理类的依赖 和 执行依赖注入 的 工具。
通过前一节「依赖注入基本概念」相关阐述,我们不难得出这样一个简单的结论「Laravel 服务容器」就是「依赖注入容器」。
其实,服务容器作为「依赖注入容器」去完成 Laravel 所需依赖的注册、绑定和解析工作只是 「Laravel 服务容器」核心功能之一;另外,「Laravel 服务容器」还担纲 Laravel 应用的注册程序的功能。
节选一段「深度挖掘 Laravel 生命周期」一文中有关服务容器的内容:
创建应用实例即实例化 Illuminate\Foundation\Application 这个服务容器,后续我们称其为 APP 容器。在创建 APP 容器主要会完成:注册应用的基础路径并将路径绑定到 APP 容器 、注册基础服务提供者至 APP 容器 、注册核心容器别名至 APP 容器 等基础服务的注册工作。
所以要了解 Larvel 服务容器必然需要研究 Illuminate\Foundation\Application 的构造函数:
/**
* Create a new Illuminate application instance.
*
* @see https://github.com/laravel/framework/blob/5.6/src/Illuminate/Foundation/Application.php#L162:27
* @param string|null $basePath
* @return void
*/
public function __construct($basePath = null)
{
if ($basePath) {
$this->setBasePath($basePath);
}
$this->registerBaseBindings();
$this->registerBaseServiceProviders();
$this->registerCoreContainerAliases();
}
没错在 Application 类的构造函数一共完成 3 个操作的处理功能:
这里所说的「注册」归根到底还是在执行「Laravel 服务容器」的「绑定(bind)」操作,完成绑定接口到实现。
为了表名我所言非虚,让我们看看 registerBaseBindings() 方法:
/**
* Register the basic bindings into the container. 注册 App 实例本身到 App 容器
*
* @return void
*/
protected function registerBaseBindings()
{
static::setInstance($this);
$this->instance('app', $this);
$this->instance(Container::class, $this);
$this->instance(PackageManifest::class, new PackageManifest(
new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
));
}
我们知道 instance() 方法会将对象实例 $this** 绑定到容器的 **app** 和 **Container::class** 接口。后续无论是通过 **app()->make('app')** 还是 **app()->make(ontainer::class)** 获取到的实现类都是 **$this(即 Laravel 服务容器实例) 对象。有关 instance 的使用方法可以查阅 Laravel 服务容器解析文档,不过我也会在下文中给出相关使用说明。
到这里相信大家对「Laravel 服务容器」有了一个比较清晰的理解了。
我们所说的「Laravel 服务容器」除了担纲「依赖注入容器」职能外;同时,还会作为 Laravel 项目的注册中心去完成基础服务的注册工作。直白一点讲在它的内部会将诸多服务的实现类「绑定」到「Laravel 服务容器」。总结起来它的作用主要可以归为以下 2 方面:
Laravel 服务容器在使用时一般分为两个阶段:使用之前进行绑定(bind)完成将实现绑定到接口;使用时对通过接口解析(make)出服务。
Laravel 内置多种不同的绑定方法以用于不同的使用场景。但无论哪种绑定方式,它们的最终目标是一致的:绑定接口到实现。
这样的好处是在项目的编码阶段建立起接口和实现的映射关系,到使用阶段通过抽象类(接口)解析出它的具体实现,这样就实现了项目中的解耦。
在讲解这些绑定方法前,先讲一个 Laravel 服务容器的使用场景。
通过向服务容器中绑定需要创建的类及其依赖,当需要使用这个类时直接从服务容器中解析出这个类的实例。类的实例化及其依赖的注入,完全由服务容器自动的去完成。
举个示例,相比于通过 new 关键词创建类实例:
<?php
$dependency = new ConfigDependency(config('cache.config.setting'));
$cache = new MemcachedCache($dependency);
每次实例化时我们都需要手动的将依赖 $dependency 传入到构造函数内。
而如果我们通过「Laravel 服务容器」绑定来管理依赖的话:
<?php
App::bind(Cache::class, function () {
$dependency = new ConfigDependency(config('cache.config.setting'));
return $cache = new MemcachedCache($dependency);
});
仅需在匿名函数内一次创建所需依赖 $dependency,再将依赖传入到服务进行实例化,并返回服务实例。
此时,使用 Cache 服务时只要从「Laravel 服务容器」中解析(make)出来即可,而无需每次手动传入 ConfigDependency 依赖再实例化服务。因为,所有的依赖注入工作此时都由 Laravel 服务容器 自动的给我们做好了,这样就简化了服务处理。
下面演示了如何解析出 Cache 服务:
<?php
$cache = App::make(Cache::class);
先了解 Laravel 服务容器的一个使用场景,会对学习服务容器的 绑定方式 大有裨益。
从 Laravel 服务容器解析 - 绑定 这部分的文档我们知道常用的绑定方式有:
接下来我们将学习这些绑定方法。
bind 方法的功能是将服务的实现绑定到抽象类,然后在每次执行服务解析操作时,Laravel 容器都会重新创建实例对象。
bind 的使用方法已经在「管理待创建类的依赖」一节中有过简单的演示,它会在每次使用 App::make(Cache::class) 去解析 Cache 服务时,重新执行「绑定」操作中定义的闭包而重新创建 MemcachedCache 缓存实例。
bind 方法除了能够接收闭包作为实现外,还可以:
采用单例绑定时,仅在首次解析时创建实例,后续使用 make 进行解析服务操作都将直接获取这个已解析的对象,实现了 共享 操作。
绑定处理类似 bind 绑定,只需将 bind 方法替换成 singleton 方法即可:
App::singleton(Cache::class, function () {
$dependency = new ConfigDependency(config('cache.config.setting'));
return $cache = new MemcachedCache($dependency);
});
实例绑定的功能是将已经创建的实例对象绑定到接口以供后续使用,这种使用场景类似于 注册表。
比如用于存储用户模型:
<?php
// 创建一个用户实例
$artisan = new User('柳公子');
// 将实例绑定到服务容器
App::instance('login-user', $artisan);
// 获取用户实例
$artisan = App::make('login-user');
在了解上下文绑定之前,先解释下什么是上下文,引用「轮子哥」的一段解释:
每一段程序都有很多外部变量。只有像Add这种简单的函数才是没有外部变量的。一旦你的一段程序有了外部变量,这段程序就不完整,不能独立运行。你为了使他们运行,就要给所有的外部变量一个一个写一些值进去。这些值的集合就叫上下文。 「编程中什么是「Context(上下文)」?」 - vczh的回答。
上下文绑定在 Laravel 服务容器解析 - 上下文绑定 文档中给出了相关示例:
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');
});
在项目中常会用到存储功能,得益于 Laravel 内置集成了 FlySystem 的 Filesystem 接口,我们很容易实现多种存储服务的项目。
示例中将用户头像存储到本地,将用户上传的小视频存储到云服务。那么这个时就需要区分这样不同的使用场景(即上下文或者说环境)。
当用户存储头像(PhotoController::class)需要使用存储服务(Filesystem::class)时,我们将本地存储驱动,作为实现给到 PhotoController::class:
function () {
return Storage::disk('local');
}
而当用户上传视频 VideoController::class,需要使用存储服务(Filesystem::class)时,我们则将云服务驱动,作为实现给到 VideoController::class:
function () {
return Storage::disk('s3');
}
这样就实现了基于不同的环境获取不同的服务实现。
「Laravel 服务容器」功能强大的原因在于除了提供手动的绑定接口到实现的方法,还支持自动注入和解析的功能。
我们在编写控制器时,经常会使用类型提示功能将某个类作为依赖传入构造函数;但在执行这个类时却无需我们去实例化这个类所需的依赖,这一切归功于自动解析的能力。
比如,我们的用户控制器需要获取用户信息,然后在构造函数中定义 User 模型作为依赖:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\User;
class UserController
{
private $user = null;
public function __construct(User $user)
{
$this->user = $user;
}
}
然后,当访问用户模块时 Laravel 会自动解析出 User 模型,而无需手动的常见模型示例。
除了以上几种数据绑定方法外还有 tag(标签绑定) 和 extend(扩展绑定) 等,毫无疑问这些内容在 Laravel 文档 也有介绍,所以这里就不再过多介绍了。
下一节,我们将深入到源码中去窥探下 Laravel 服务容器是如何进行绑定和解析处理的。
要了解一项技术的实现原理,免不了去探索源码,源码学习是个有意思的事情。这个过程不但让我们理解它是如何工作的,或许还会带给我们一些意外惊喜。
我们知道 Laravel 服务容器其实会处理以下两方面的工作:
关于注册基础服务,在「深度挖掘 Laravel 生命周期」一文中其实已经有所涉及,但并并不深入。
本文将进一步的研究注册基础服务的细节。除了研究这些服务究竟如何被注册到服务容器,还将学习它们是如何被使用的。所有的这些都需要我们深入到 Illuminate\Foundation\Application 类的内部:
/**
* Create a new Illuminate application instance.
*
* @see https://github.com/laravel/framework/blob/5.6/src/Illuminate/Foundation/Application.php#L162:27
* @param string|null $basePath
* @return void
*/
public function __construct($basePath = null)
{
if ($basePath) {
$this->setBasePath($basePath);
}
$this->registerBaseBindings();
$this->registerBaseServiceProviders();
$this->registerCoreContainerAliases();
}
前面我们已经研究过 registerBaseBindings() 方法,了解到该方法主要是将自身绑定到了服务容器,如此我们便可以在项目中使用 $this->app->make('something') 去解析一项服务。
现在让我们将焦点集中到 registerBaseServiceProviders 和 registerCoreContainerAliases 这两个方法。
打开 registerBaseServiceProviders 方法将发现在方法体中仅有 3 行代码,分别是注册 EventServiceProvider、LogServiceProvider 和 RoutingServiceProvider 这 3 个服务提供者:
/**
* 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 array $options
* @param bool $force
* @return \Illuminate\Support\ServiceProvider
*/
public function register($provider, $options = [], $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 方法时,执行 register 方法,完成绑定处理
if (method_exists($provider, 'register')) {
$provider->register();
}
$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->booted) {
$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 方法中,最好仅执行「绑定」操作。
为了更好的说明服务提供者仅完成绑定操作,还是让我们来瞧瞧 EventServiceProvider 服务,看看它究竟做了什么:
<?php
namespace Illuminate\Events;
use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Queue\Factory as QueueFactoryContract;
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);
});
});
}
}
没错 EventServiceProvider 所做的全部事情,仅仅通过 register 方法将闭包绑定到了服务容器,除此之外就什么都没有了。
用过 Laravel 框架的朋友应该知道在 Laravel 中有个别名系统。最常见的使用场景就是设置路由时,可以通过 Route 类完成一个新路由的注册,如:
Route::get('/', function() {
return 'Hello World';
});
得益于 Laravel Facades 和别名系统我们可以很方便的通过别名来使用 Laravel 内置提供的各种服务。
注册别名和对应服务的映射关系,便是在 registerCoreContainerAliases 方法内来完成的。由于篇幅所限本文就不做具体细节的展开,后续会单独出一篇讲解别名系统的文章。
不过现在还是有必要浏览下 Laravel 提供了哪些别名服务:
/**
* Register the core class aliases in the container. 在容器中注册核心服务的别名
*
* @return void
*/
public function registerCoreContainerAliases()
{
foreach ([
'app' => [\Illuminate\Foundation\Application::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],
'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],
'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\Contracts\Hashing\Hasher::class],
'translator' => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class],
'log' => [\Illuminate\Log\Writer::class, \Illuminate\Contracts\Logging\Log::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],
'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 绑定作为最基本的绑定方法,可以很好的说明 Laravel 是如何实现绑定服务处理的。
下面摘出 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)
{
// 如果未提供实现类 $concrete,我们直接将抽象类作为实现 $abstract。
// 这之后,我们无需明确指定 $abstract 和 $concrete 是否为单例模式,
// 而是通过 $shared 标识来决定它们是单例还是每次都需要实例化处理。
$this->dropStaleInstances($abstract);
if (is_null($concrete)) {
$concrete = $abstract;
}
// 如果绑定时传入的实现类非闭包,即绑定时是直接给定了实现类的类名,
// 这时要稍微处理下将类名封装成一个闭包,保证解析时处理手法的统一。
if (! $concrete instanceof Closure) {
$concrete = $this->getClosure($abstract, $concrete);
}
$this->bindings[$abstract] = compact('concrete', 'shared');
// 最后如果抽象类已经被容器解析过,我们将触发 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->make($concrete, $parameters);
};
}
/**
* 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)
{
if (isset($this->reboundCallbacks[$abstract])) {
return $this->reboundCallbacks[$abstract];
}
return [];
}
在 bind 方法中,主要完成以下几个方面的处理:
在绑定过程中,服务容器并不会执行服务的解析操作,这样有利于提升服务的性能。直到在项目运行期间,被使用时才会真正解析出需要使用的对应服务,实现「按需加载」。
解析处理和绑定一样定义在 Illuminate\Container\Container 类中,无论是手动解析还是通过自动注入的方式,实现原理都是基于 PHP 的反射机制。
所有我们还是直接从 make 方法开始去挖出相关细节:
/**
* Resolve the given type from the container. 从容器中解析出给定服务具体实现
*
* @param string $abstract
* @param array $parameters
* @return mixed
*/
public function make($abstract, array $parameters = [])
{
return $this->resolve($abstract, $parameters);
}
/**
* Resolve the given type from the container. 从容器中解析出给定服务具体实现
*
* @param string $abstract
* @param array $parameters
* @return mixed
*/
protected function resolve($abstract, $parameters = [])
{
$abstract = $this->getAlias($abstract);
// 如果绑定时基于上下文绑定,此时需要解析出上下文实现类
$needsContextualBuild = ! empty($parameters) || ! is_null(
$this->getContextualConcrete($abstract)
);
// 如果给定的类型已单例模式绑定,直接从服务容器中返回这个实例而无需重新实例化
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
return $this->instances[$abstract];
}
$this->with[] = $parameters;
$concrete = $this->getConcrete($abstract);
// 已准备就绪创建这个绑定的实例。下面将实例化给定实例及内嵌的所有依赖实例。
// 到这里我们已经做好创建实例的准备工作。只有可以构建的服务才可以执行 build 方法去实例化服务;
// 否则也就是说我们的服务还存在依赖,然后不断的去解析嵌套的依赖,知道它们可以去构建(isBuildable)。
if ($this->isBuildable($concrete, $abstract)) {
$object = $this->build($concrete);
} else {
$object = $this->make($concrete);
}
// 如果我们的服务存在扩展(extend)绑定,此时就需要去执行扩展。
// 扩展绑定适用于修改服务的配置或者修饰(decorating)服务实现。
foreach ($this->getExtenders($abstract) as $extender) {
$object = $extender($object, $this);
}
// 如果我们的服务已单例模式绑定,此时无要将已解析的服务缓存到单例对象池中(instances),
// 后续便可以直接获取单例服务对象了。
if ($this->isShared($abstract) && ! $needsContextualBuild) {
$this->instances[$abstract] = $object;
}
$this->fireResolvingCallbacks($abstract, $object);
$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 ($concrete instanceof Closure) {
return $concrete($this, $this->getLastParameterOverride());
}
$reflector = new ReflectionClass($concrete);
// 如果需要解析的类无法实例化,即试图解析一个抽象类类型如: 接口或抽象类而非实现类,直接抛出异常。
if (! $reflector->isInstantiable()) {
return $this->notInstantiable($concrete);
}
$this->buildStack[] = $concrete;
// 通过反射获取实现类构造函数
$constructor = $reflector->getConstructor();
// 如果实现类并没有定义构造函数,说明这个实现类没有相关依赖。
// 我们可以直接实例化这个实现类,而无需自动解析依赖(自动注入)。
if (is_null($constructor)) {
array_pop($this->buildStack);
return new $concrete;
}
// 获取到实现类构造函数依赖参数
$dependencies = $constructor->getParameters();
// 解析出所有依赖
$instances = $this->resolveDependencies(
$dependencies
);
array_pop($this->buildStack);
// 这是我们就可以创建服务实例并返回。
return $reflector->newInstanceArgs($instances);
}
/**
* Resolve all of the dependencies from the ReflectionParameters. 从 ReflectionParameters 解析出所有构造函数所需依赖
*
* @param array $dependencies
* @return array
*/
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;
}
// 构造函数参数为非类时,即参数为 string、int 等标量类型或闭包时,按照标量和闭包解析;
// 否则需要解析类。
$results[] = is_null($dependency->getClass())
? $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->name))) {
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($parameter->getClass()->name);
}
catch (BindingResolutionException $e) {
if ($parameter->isOptional()) {
return $parameter->getDefaultValue();
}
throw $e;
}
}
以上,便是 Laravel 服务容器解析的核心,得益于 PHP 的反射机制,实现了自动依赖注入和服务解析处理,概括起来包含以下步骤:
更多细节处理还是需要我们进一步深入的内核中才能发掘出来,但到这其实已经差不太多了。有兴趣的朋友可以亲自了解下其它绑定方法的源码解析处理。
以上便是今天 Laravel 服务容器的全部内容,希望对大家有所启发。
感谢一下优秀的学习资料:
https://www.insp.top/learn-laravel-container
https://laravel-china.org/articles/10421/depth-mining-of-laravel-life-cycle
https://laravel-china.org/articles/4698/laravel-core-ioc-service-container
https://hk.saowen.com/a/6c880512a3a01a10b07fb53364394b81eff931065ce33ec7ad36caac44e07852
http://rrylee.github.io/2015/09/23/laravel-container/
https://blog.tanteng.me/2016/01/laravel-construct-ioc/
https://juejin.im/entry/5916a557a0bb9f005fe07b3a
Original url: Access
Created at: 2018-10-10 14:34:07
Category: default
Tags: none
未标明原创文章均为采集,版权归作者所有,转载无需和我联系,请注明原出处,南摩阿彌陀佛,知识,不只知道,要得到
最新评论