详解Laravel服务容器的绑定与解析


Posted in PHP onNovember 05, 2019

前言

  老实说,第一次老大让我看laravel框架手册的那天早上,我是很绝望的,因为真的没接触过,对我这种渣渣来说,laravel的入门门槛确实有点高了,但还是得硬着头皮看下去(虽然到现在我还有很多没看懂,也没用过)。
  后面慢慢根据公司项目的代码对laravel也慢慢熟悉起来了,但还是停留在一些表面的功能,例如依赖注入,ORM操作,用户认证这些和我项目业务逻辑相关的操作,然后对于一些架构基础的,例如服务提供器,服务容器,中间件,Redis等这些一开始就要设置好的东西,我倒是没实际操作过(因为老大一开始就做好了),所以看手册还是有点懵。
  所以有空的时候逛逛论坛,搜下Google就发现许多关于laravel核心架构的介绍,以及如何使用的网站(确实看完后再去看手册就好理解多了),下面就根据一个我觉得不错的网站上面的教学来记录一下laravel核心架构的学习
网站地址:https://laraweb.net/ 这是一个日本的网站,我觉得挺适合新手的,内容用浏览器翻译过来就ok了,毕竟日文直翻过来很好理解的

关于服务容器

  手册上是这样介绍的:Laravel 服务容器是用于管理类的依赖和执行依赖注入的工具。依赖注入这个花俏名词实质上是指:类的依赖项通过构造函数,或者某些情况下通过「setter」方法「注入」到类中。。。。。。(真的看不懂啥意思)
  服务容器是用于管理类(服务)的实例化的机制。直接看看服务容器怎么用

  1.在服务容器中注册类(bind)

$this->app->bind('sender','MailSender');
//$this->app成为服务容器。

  2.从服务容器生成类(make)

$sender = $this->app->make('sender');
//从服务容器($this->app)创建一个sender类。

在这种情况下,将返回MailSender的实例。

  这是服务容器最简单的使用,下面是对服务容器的详细介绍

laravel容器基本认识

  一开始,index.php 文件加载 Composer 生成定义的自动加载器,然后从 bootstrap/app.php 脚本中检索 Laravel 应用程序的实例。Laravel 本身采取的第一个动作是创建一个 application/ service container 的实例。

$app = new Illuminate\Foundation\Application(
  dirname(__DIR__)
);

  这个文件在每一次请求到达laravel框架都会执行,所创建的$app即是laravel框架的应用程序实例,它在整个请求生命周期都是唯一的。laravel提供了很多服务,包括认证,数据库,缓存,消息队列等等,$app作为一个容器管理工具,负责几乎所有服务组件的实例化以及实例的生命周期管理。当需要一个服务类来完成某个功能的时候,仅需要通过容器解析出该类型的一个实例即可。从最终的使用方式来看,laravel容器对服务实例的管理主要包括以下几个方面:

  • 服务的绑定与解析
  • 服务提供者的管理
  • 别名的作用
  • 依赖注入

先了解如何在代码中获取到容器实例,再学习上面四个关键

如何在代码中获取到容器实例

第一种是

$app = app();
//app这个辅助函数定义在\vendor\laravel\framework\src\Illuminate\Foundation\helper.php

里面,,这个文件定义了很多help函数,并且会通过composer自动加载到项目中。

所以,在参与http请求处理的任何代码位置都能够访问其中的函数,比如app()。

第二种是

Route::get('/', function () {
  dd(App::basePath());
  return '';
});
//这个其实是用到Facade,中文直译貌似叫门面,在config/app.php中,

有一节数组aliases专门用来配置一些类型的别名,第一个就是'App' => Illuminate\Support\Facades\App::class,

具体的Google一下laravel有关门面的具体实现方式

第三种是

  在服务提供者里面直接使用$this->app。服务提供者后面还会介绍,现在只是引入。因为服务提供者类都是由laravel容器实例化的,这些类都继承自Illuminate\Support\ServiceProvider,它定义了一个实例属性$app:

abstract class ServiceProvider
{
  protected $app;

  laravel在实例化服务提供者的时候,会把laravel容器实例注入到这个$app上面。所以我们在服务提供者里面,始终能通过$this->$app访问到laravel容器实例,而不需要再使用app()函数或者App Facade了。

如何理解服务绑定与解析

  浅义层面理解,容器既然用来存储对象,那么就要有一个对象存入跟对象取出的过程。这个对象存入跟对象取出的过程在laravel里面称为服务的绑定与解析。

app()->bind('service', 'this is service1');
app()->bind('service2', [
  'hi' => function(){
    //say hi
  }
]);
class Service {
}
app()->bind('service3', function(){
  return new Service();
});

  还有一个单例绑定singleton,是bind的一种特殊情况(第三个参数为true),绑定到容器的对象只会被解析一次,之后的调用都返回相同的实例

public function singleton($abstract, $concrete = null)
{
$this->bind($abstract, $concrete, true);
}

  在绑定的时候,我们可以直接绑定已经初始化好的数据(基本类型、数组、对象实例),还可以用匿名函数来绑定。用匿名函数的好处在于,这个服务绑定到容器以后,并不会立即产生服务最终的对象,只有在这个服务解析的时候,匿名函数才会执行,此时才会产生这个服务对应的服务实例。

  实际上,当我们使用singleton,bind方法以及数组形式,(这三个方法是后面要介绍的绑定的方法),进行服务绑定的时候,如果绑定的服务形式,不是一个匿名函数,也会在laravel内部用一个匿名函数包装起来,这样的话, 不轮绑定什么内容,都能做到前面介绍的懒初始化的功能,这对于容器的性能是有好处的。这个可以从bind的源码中看到一些细节:

if (! $concrete instanceof Closure) {
  $concrete = $this->getClosure($abstract, $concrete);
}

看看bind的底层代码

public function bind($abstract, $concrete = null, $shared = false)

  第一个参数服务绑定名称,第二个参数服务绑定的结果(也就是闭包,得到实例),第三个参数就表示这个服务是否在多次解析的时候,始终返回第一次解析出的实例(也就是单例绑定singleton)。

  服务绑定还可以通过数组的方式:

app()['service'] = function(){
  return new Service();
};

绑定大概就这些,接下来看解析,也就是取出来用

$service= app()->make('service');

  这个方法接收两个参数,第一个是服务的绑定名称和服务绑定名称的别名,如果是别名,那么就会根据服务绑定名称的别名配置,找到最终的服务绑定名称,然后进行解析;第二个参数是一个数组,最终会传递给服务绑定产生的闭包。

看源码:

/**
 * 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 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;
  }
  $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;
}

第一步:

$needsContextualBuild = ! empty($parameters) || ! is_null(
  $this->getContextualConcrete($abstract)
);

  该方法主要是区分,解析的对象是否有参数,如果有参数,还需要对参数做进一步的分析,因为传入的参数,也可能是依赖注入的,所以还需要对传入的参数进行解析;这个后面再分析。

第二步:

if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
  return $this->instances[$abstract];
}

  如果是绑定的单例,并且不需要上面的参数依赖。我们就可以直接返回 $this->instances[$abstract]。

第三步:

$concrete = $this->getConcrete($abstract);
...
/**
 * Get the concrete type for a given abstract.
 *
 * @param string $abstract
 * @return mixed  $concrete
 */
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;
}

  这一步主要是先从绑定的上下文找,是不是可以找到绑定类;如果没有,则再从 $bindings[] 中找关联的实现类;最后还没有找到的话,就直接返回 $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);
}
...
/**
 * 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;
}

  如果之前找到的 $concrete 返回的是 $abstract 值,或者 $concrete 是个闭包,则执行 $this->build($concrete),否则,表示存在嵌套依赖的情况,则采用递归的方法执行 $this->make($concrete),直到所有的都解析完为止。

$this->build($concrete)


/**
 * 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());
  }
  // 利用反射机制,解析该类。
  $reflector = new ReflectionClass($concrete);
  // If the type is not instantiable, the developer is attempting to resolve
  // an abstract type such as an Interface of 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)) {
    // 将 build 过程的内容 pop,然后直接构造对象输出。
    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.
  // 解析出所有上下文依赖对象,带入函数,构造对象输出
  $instances = $this->resolveDependencies(
    $dependencies
  );
  array_pop($this->buildStack);
  return $reflector->newInstanceArgs($instances);
}

总结

以上所述是小编给大家介绍的Laravel服务容器的绑定与解析,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

PHP 相关文章推荐
详细介绍PHP应用提速面面观
Oct 09 PHP
默默小谈PHP&MYSQL分页原理及实现
Jan 02 PHP
PHP取二进制文件头快速判断文件类型的实现代码
Aug 05 PHP
PHP中设置一个严格30分钟过期Session面试题的4种答案
Jul 30 PHP
PHP彩蛋信息介绍和阻止泄漏的方法(隐藏功能)
Aug 06 PHP
php与flash as3 socket通信传送文件实现代码
Aug 16 PHP
Laravel框架路由配置总结、设置技巧大全
Sep 03 PHP
php列出mysql表所有行和列的方法
Mar 13 PHP
Yii使用技巧大汇总
Dec 29 PHP
PHP中list()函数用法实例简析
Jan 08 PHP
老生常谈PHP面向对象之标识映射
Jun 21 PHP
Windows服务器中PHP如何安装redis扩展
Sep 27 PHP
php+laravel依赖注入知识点总结
Nov 04 #PHP
PHP保存Base64图片base64_decode的问题整理
Nov 04 #PHP
详解laravel passport OAuth2.0的4种模式
Nov 04 #PHP
laravel返回统一格式错误码问题
Nov 04 #PHP
php 中self,this的区别和操作方法实例分析
Nov 04 #PHP
PHP 文件写入和读取操作实例详解【必看篇】
Nov 04 #PHP
PHP连接MySQL数据库的三种方式实例分析【mysql、mysqli、pdo】
Nov 04 #PHP
You might like
兼容性最强的PHP生成缩略图的函数代码(修改版)
2011/01/18 PHP
用 Composer构建自己的 PHP 框架之设计 MVC
2014/10/30 PHP
php开发最强大的IDE编辑的phpstorm 2020.2配置Xdebug调试的详细教程
2020/08/17 PHP
JS类定义原型方法的两种实现的区别评论很多
2007/09/12 Javascript
JavaScript日历实现代码
2010/09/12 Javascript
JS实现日期加减的方法
2013/11/29 Javascript
jQuery实现单击按钮遮罩弹出对话框(仿天猫的删除对话框)
2014/04/10 Javascript
js随机生成一个验证码
2017/06/01 Javascript
Javascript快速实现浏览器系统通知
2017/08/26 Javascript
node基于async/await对mysql进行封装
2019/06/20 Javascript
js实现随机点名器精简版
2020/06/29 Javascript
[01:00:54]TI4正赛第二日开场
2014/07/20 DOTA
[03:46]DAC趣味视频-中文考试.mp4
2017/04/02 DOTA
[00:12]DAC2018 Miracle-站上中单舞台,他能否再写奇迹?
2018/04/06 DOTA
[00:05]ChinaJoy现场 DOTA2玩家高呼“CN DOTA BEST DOTA”
2019/08/04 DOTA
[08:56]DOTA2-DPC中国联赛2月23日Recap集锦
2021/03/11 DOTA
Python回文字符串及回文数字判定功能示例
2018/03/20 Python
Python subprocess库的使用详解
2018/10/26 Python
Python并发:多线程与多进程的详解
2019/01/24 Python
python中实现控制小数点位数的方法
2019/01/24 Python
Python常用爬虫代码总结方便查询
2019/02/25 Python
python验证身份证信息实例代码
2019/05/06 Python
Python中一些深不见底的“坑”
2019/06/12 Python
docker-py 用Python调用Docker接口的方法
2019/08/30 Python
简单介绍django提供的加密算法
2019/12/18 Python
python 抓取知乎指定回答下视频的方法
2020/07/09 Python
python 可视化库PyG2Plot的使用
2021/01/21 Python
描述JSP和Servlet的区别、共同点、各自应用的范围
2012/10/02 面试题
农村结婚典礼司仪主持词
2014/03/14 职场文书
市场营销专业毕业生求职信
2014/03/26 职场文书
淘宝活动总结范文
2014/06/26 职场文书
大学生求职信例文
2014/06/29 职场文书
公务员学习中国梦心得体会
2016/01/05 职场文书
3招让你摆脱即兴讲话冷场尴尬
2019/08/08 职场文书
一篇文章弄懂MySQL查询语句的执行过程
2021/05/07 MySQL
漫改真人电影「萌系男友是燃燃的橘色」公开先导视觉图
2022/03/21 日漫