本文首发于 深刻剖析 Laravel 服务容器,转载请注明出处。喜欢的朋友不要吝啬大家的赞同,谢谢。
以前在 深度挖掘 Laravel 生命周期 一文中,咱们有去探究 Laravel 到底是如何接收 HTTP 请求,又是如何生成响应并最终呈现给用户的工做原理。php
本章将带领你们研究另外一个 Laravel 框架的核心内容:「服务容器」。有阅读过 Laravel 文档 的朋友应该有注意到在「核心架构」篇章中包含了几个主题:生命周期、服务容器、服务提供者、Facades 和 Concracts.html
今天就让咱们一块儿来揭开「Laravel 服务容器」的神秘面纱。laravel
提示:本文内容较长可能须要耗费较多的阅读时间,另外文中包含 Laravel 内核代码建议选择合适的 IDE 或文本编辑器进行源码阅读。
依赖注入基本概念git
Laravel 服务容器是什么github
Laravel 服务容器的使用方法redis
经常使用绑定方法编程
Laravel 服务容器实现原理设计模式
注册基础服务缓存
管理所需建立的类及其依赖cookie
若是您有阅读个人前做 深度挖掘 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-la...
https://laravel-china.org/art...
https://laravel-china.org/art...
https://hk.saowen.com/a/6c880...
http://rrylee.github.io/2015/...
https://blog.tanteng.me/2016/...