laravel容器和依赖注入


  • 啥是Ioc容器,方便我们实现依赖注入的一种实现,也就是说依赖注入不一定需要控制反转容器,只不过使用容器可能会方便些。

  • laravel通过向容器中绑定接口的具体实现,可实现不同实现的快速切换,接口在laravel中有个好听的名字叫契约。

  • 面向接口编程和容器结合使用,可以轻松实现代码解耦,实现了关注分离。

  • 面向接口开发的好处:除了可以快速切换实现了相同契约的实现,对开发测试同步进行,以及对单元测试都是非常有好的。

  • 下面是一个简单的使用示例,为了相对好理解没有加入service层。

    1 创建文章接口
    <?php namespace App\Repository; interface ArticleRepository
    {
    // 返回文章列表
    public function getList(): array;
    } 2 创建一个文章具体实现类
    <?php namespace App; use App\Repository\ArticleRepository;
    use App\Models\Article; class DbArticle implements ArticleRepository
    {
    public function getList(): array
    {
    return Article::all()->toArray();
    }
    } 3 在容器中进行绑定
    <?php namespace App\Providers; use App\DbArticle;
    use Illuminate\Support\ServiceProvider;
    use App\Repository\ArticleRepository;
    use App\DbArticle; class AppServiceProvider extends ServiceProvider
    {
    /**
    * Register any application services.
    *
    * @return void
    */
    public function register()
    {
    $this->app->bind(ArticleRepository::class, function () {
    return new DbArticle();
    });
    } 4 在控制器中使用
    <?php namespace App\Http\Controllers\Test; use App\Http\Controllers\Controller;
    use Illuminate\Http\Request;
    use App\Repository\ArticleRepository; class ArticleController extends Controller
    {
    protected $articles; public function __construct(ArticleRepository $articles)
    {
    $this->articles = $articles;
    } public function index()
    {
    $articles = $this->articles->getList();
    return view('article.index', compact('articles'));
    }
    } 以上便是一个比较标准的依赖注入的使用方式,其实还有很多使用方式,比如通过app()函数或者App门面直接获取依赖等等。看到这里应该能感觉到依赖注入为开发者带来的方便了,上述例子中文章是从ORM中取出,当需求改变要求文章从mongodb或者redis中取出的时候,我们只需要编写单独的实现类,然后在服务提供者中绑定新的实现,业务代码完全不需要改变,就能快速实现切换。
  • 下面讲解laravel如何实现的依赖注入 laravel版本6.12

    从bootstrap/app.php开始
    
    // 实例化app容器类 并传递项目根目录给构造函数
    $app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
    ); Illuminate\Foundation\Application中
    public function __construct($basePath = null)
    {
    if ($basePath) {
    // 注册基本路径 调用container的instance方法 添加路径实例到容器
    // 结果就是app实例的instances属性下多了几条路径映射 可直接打印$this查看
    $this->setBasePath($basePath);
    }
    // 注册基本绑定 跳转到下面的registerBaseBindings方法
    $this->registerBaseBindings();
    $this->registerBaseServiceProviders();
    $this->registerCoreContainerAliases();
    } protected function registerBaseBindings()
    {
    static::$instance = $this;
    // 绑定自身到容器
    $this->instance('app', $this);
    // 依然还是绑定
    $this->instance(Container::class, $this);
    // 重点来了!!! 跳转到下面的singleton方法
    $this->singleton(Mix::class);
    $this->instance(PackageManifest::class, new PackageManifest(
    new Filesystem,
    $this->basePath(),
    $this->getCachedPackagesPath()
    ));
    } Illuminate\Container\Container中
    // 绑定共享实例 就是单例绑定 可以看到singleton方法就是调用了bind方法
    public function singleton($abstract, $concrete = null)
    {
    $this->bind($abstract, $concrete, true);
    } /**
    * 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)
    {
    // 此处的$abstract = Illuminate\Foundation\Mix
    // 删除老的实例绑定
    $this->dropStaleInstances($abstract); // laravel默认的如果没传递对应抽象的实例,那么就认为实例应该是传递的抽象的一个实例
    if (is_null($concrete)) {
    // 如果concrete为null
    $concrete = $abstract;
    } // 如果传递的不是一个闭包
    if (!$concrete instanceof Closure) {
    // laravel会试图根据传递进来的抽象得到一个闭包
    // 跳转到getClosure方法
    //
    $concrete = $this->getClosure($abstract, $concrete);
    } // 将返回的闭包和是否shared标志保存到bindings数组下
    $this->bindings[$abstract] = compact('concrete', 'shared'); 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)
    {
    // 此时的$abstract $concrete都是Illuminate\Foundation\Mix
    return function ($container, $parameters = []) use ($abstract, $concrete) {
    // 如果调用bind方法只传入了abstract
    if ($abstract == $concrete) {
    // 返回容器的build方法返回的实例
    return $container->build($concrete);
    }
    // 否则直接调用contaner的resolve方法进行解析
    return $container->resolve(
    $concrete,
    $parameters,
    $raiseEvents = false
    );
    };
    } // 什么时候使用到这些绑定呢(如何从容器中解析需要的实例呢) 来看make方法 调用方式app()->make($yourAbstract)
    /**
    * 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 = [])
    {
    // 跳转到resolve方法
    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)
    ); // 如果要解析的类名instances中已经有了 并且不需要临时build 那么就返回已经存在的实例
    if (isset($this->instances[$abstract]) && !$needsContextualBuild) {
    // dd(123, $abstract);
    // dd($this->instances);
    return $this->instances[$abstract];
    } $this->with[] = $parameters; // getConcrete方法 拿到之前可能绑定过的binding 绑定过就是一个闭包
    // 没绑定过返回自身,即通过容器make可一个自定义的类
    $concrete = $this->getConcrete($abstract); if ($this->isBuildable($concrete, $abstract)) {
    // 当concrete === abstract 或者 concrete是一个闭包的时候 直接调用build方法
    // 跳转到build方法
    $object = $this->build($concrete);
    } else {
    // 当concrete是一个类名的时候 调用make方法
    $object = $this->make($concrete);
    } foreach ($this->getExtenders($abstract) as $extender) {
    $object = $extender($object, $this);
    } // 如果是共享的实例 或者 不需要临时build 那么就将实例存放到容器的instances数组中
    if ($this->isShared($abstract) && !$needsContextualBuild) {
    $this->instances[$abstract] = $object;
    } if ($raiseEvents) {
    $this->fireResolvingCallbacks($abstract, $object);
    } $this->resolved[$abstract] = true; array_pop($this->with); // 返回实例
    return $object;
    } public function build($concrete)
    {
    // 如果传递进来的是一个闭包 (通过之前bind方法绑定得到的闭包)
    // 直接调用闭包返回实例
    if ($concrete instanceof Closure) {
    return $concrete($this, $this->getLastParameterOverride());
    } // 如果传递进来的是一个可能的类名 那么调用反射api解析依赖
    try {
    $reflector = new ReflectionClass($concrete);
    } catch (ReflectionException $e) {
    throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e);
    } // 不能实例化就抛出异常
    if (!$reflector->isInstantiable()) {
    return $this->notInstantiable($concrete);
    } $this->buildStack[] = $concrete; // 拿到构造方法
    $constructor = $reflector->getConstructor(); // 没有构造方法直接new实例
    if (is_null($constructor)) {
    array_pop($this->buildStack);
    return new $concrete;
    } // 拿到构造方法中的依赖
    $dependencies = $constructor->getParameters(); try {
    // 解析依赖
    // 跳转到resolveDependencies方法
    $instances = $this->resolveDependencies($dependencies);
    } catch (BindingResolutionException $e) {
    array_pop($this->buildStack); throw $e;
    } array_pop($this->buildStack); // 返回实例
    return $reflector->newInstanceArgs($instances);
    } protected function resolveDependencies(array $dependencies)
    {
    $results = []; foreach ($dependencies as $dependency) {
    if ($this->hasParameterOverride($dependency)) {
    // 如果存在临时重写 则获取
    $results[] = $this->getParameterOverride($dependency); continue;
    } // 如果构造方法中不存在类型提示 那么会返回null
    // 跳转到resolvePrimitive方法
    $results[] = is_null($dependency->getClass())
    ? $this->resolvePrimitive($dependency)
    // 如果存在类型提示 根据类型尝试解析出依赖实例 getClass能拿到一个确切的类名
    // 跳转到resolveClass方法
    : $this->resolveClass($dependency);
    } return $results;
    } 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);
    } protected function resolveClass(ReflectionParameter $parameter)
    {
    try {
    // 又回到了make方法 进行了递归调用
    // 至此一切形成了闭环 完美!!!
    return $this->make($parameter->getClass()->name);
    } catch (BindingResolutionException $e) {
    if ($parameter->isOptional()) {
    return $parameter->getDefaultValue();
    } throw $e;
    }
    } 可以看到:
    1 当从laravel中解析对象的时候,实际调用的是build方法,而build方法首先会尝试从之前的绑定中获取,如果未找到绑定那么就通过反射机制尝试解析,当然解析依赖的过程依然存在尝试获得之前绑定的过程。
    2 laravel的容器解析依赖需要使用其固定的规则,直接调用app()函数或者App门面从容器中解析。
    3 建议使用容器进行解析自定义类的时候,一定要加上依赖的类型
    4 绑定的时候尽量使用闭包形式

其中的临时绑定等需要配合laravel容器的when with等api使用,还有解析时的事件等,本文都没进行讲解。

下期预告:Application类实例化时剩下的两个方法

laravel核心Ioc容器的更多相关文章

  1. Laravel开发:Laravel核心——Ioc服务容器源码解析(服务器绑定)

    服务容器的绑定 bind 绑定 bind 绑定是服务容器最常用的绑定方式,在 上一篇文章中我们讨论过,bind 的绑定有三种: 绑定自身 绑定闭包 绑定接口 今天,我们这篇文章主要从源码上讲解 Ioc ...

  2. 转 Spring源码剖析——核心IOC容器原理

    Spring源码剖析——核心IOC容器原理 2016年08月05日 15:06:16 阅读数:8312 标签: spring源码ioc编程bean 更多 个人分类: Java https://blog ...

  3. Laravel开发:Laravel核心——Ioc服务容器

    服务容器 在说 Ioc 容器之前,我们需要了解什么是 Ioc 容器. Laravel 服务容器是一个用于管理类依赖和执行依赖注入的强大工具. 在理解这句话之前,我们需要先了解一下服务容器的来龙去脉:  ...

  4. 重新学习Spring之核心IOC容器的底层原理

    一:IOC容器的定义 控制反转(Inversion of Control,英文缩写为IoC)是一个重要的面向对象编程的法则来削减计算机程序的耦合问题,也是轻量级的Spring框架的核心. 控制反转一般 ...

  5. Laravel开发:Laravel核心——服务容器的细节特性

    前言 在前面几个博客中,我详细讲了 Ioc 容器各个功能的使用.绑定的源码.解析的源码,今天这篇博客会详细介绍 Ioc 容器的一些细节,一些特性,以便更好地掌握容器的功能. 注:本文使用的测试类与测试 ...

  6. Laravel开发:Laravel核心——Ioc服务容器源码解析(服务器解析)

    make解析 服务容器对对象的自动解析是服务容器的核心功能,make 函数.build 函数是实例化对象重要的核心,先大致看一下代码: public function make($abstract) ...

  7. 简单解析Spring核心IOC容器原理

    将大体流程解析了一边,具体可以看源代码一个方法一个方法的跟下 XmlBeanFactory的功能是建立在DefaultListableBeanFactory这个基本容器的基础上的,并在这个基本容器的基 ...

  8. 理解PHP 依赖注入|Laravel IoC容器

    看Laravel的IoC容器文档只是介绍实例,但是没有说原理,之前用MVC框架都没有在意这个概念,无意中在phalcon的文档中看到这个详细的介绍,感觉豁然开朗,复制粘贴过来,主要是好久没有写东西了, ...

  9. Spring的核心之IoC容器创建对象

    Spring的Ioc容器,是Spring的核心内容: 作用:对象的创建和处理对象的依赖关系. Spring容器创建对象有以下几种方式: 1:调用无参数的构造器 <!-- 默认无参的构造器 --& ...

随机推荐

  1. 【小白学AI】八种应对样本不均衡的策略

    文章来自:微信公众号[机器学习炼丹术] 目录 1 什么是非均衡 2 8种解决办法 2.1 重采样(四种方法) 2.2 调整损失函数 2.3 异常值检测框架 2.4 二分类变成多分类 2.5 EasyE ...

  2. 无版号的ios手游下架 TF签名才是正确选择?

    2020年8月1日开始,无版号的ios手游就要全部下架appstore了,在这样关键的时刻,TF签名成了ios手游的最后一根救命稻草.因为被下架或者根本无法通过appstore的上架审核,ios手游的 ...

  3. Apache Hudi + AWS S3 + Athena实战

    Apache Hudi在阿里巴巴集团.EMIS Health,LinkNovate,Tathastu.AI,腾讯,Uber内使用,并且由Amazon AWS EMR和Google云平台支持,最近Ama ...

  4. 如何用Keil MDK5创建新项目

    1.安装相应软件 2.创建与Build项目 创建项目 下载与调试— ST-Link

  5. 如何查看Docker容器环境变量,如何向容器传递环境变量

    1 前言 欢迎访问南瓜慢说 www.pkslow.com获取更多精彩文章! 了解Docker容器的运行环境非常重要,我们把应用放在容器里执行,环境变量会直接影响程序的执行效果.所以我们要知道容器内部的 ...

  6. Python内置OS模块用法详解

    大家好,从今天起早起Python将持续更新由小甜同学从初学者的角度学习Python的笔记,其特点就是全文大多由新手易理解的代码与注释及动态演示.刚入门的读者千万不要错过! 很多人学习python,不知 ...

  7. Ant Design Pro V5 从服务器请求菜单(typescript版)

    [前言] 找了很多Admin模板,最后还是看中了AntDesignPro(下文简写antd pro)这个阿里巴巴开源的Admin框架,长这样(还行吧,目前挺主流的): 官网地址:https://pro ...

  8. 9、Java 常用类 Math,Number子类,String,Character

    本小节主要介绍一些如何去使用Java提供的类如何去使用?如何在实战中使用?从来没有用过的如何去学习? 分享一下发哥的学习方法? 1.针对性的学习 在理解自己的需求或者要做某一块的内容后,有针对性,选择 ...

  9. Docker-Compose介绍,安装和使用

    Docker-Compose 介绍 有时候运行一个镜像需要大量的参数,可以通过Docker-Compose编写这些参数.而且Docker-Compose可以版主我们批量管理容器,这些信息值需要通过一个 ...

  10. Linux(Centos 7)下安装Git并配置连接GitHub

    1.安装git  Centos7 查看git --version 2.配置用户名密码 git config --global user.name "xxx" git config ...