精华内容
下载资源
问答
  • laravel 服务容器实现原理

    千次阅读 2019-05-08 18:29:22
    Laravel框架中就是使用服务容器来实现 ** 控制反转 ** 和 ** 依赖注入 **。 什么是控制反转(IoC)和依赖注入(DI) 控制反转(IoC) 就是说把创建对象的** 控制权 进行转移,以前创建对象的主动权和创建时机是由...

    前言

    通过实现laravel 框架功能,以便深入理解laravel框架的先进思想。

    什么是服务容器

    服务容器是用来管理类依赖与运行依赖注入的工具。Laravel框架中就是使用服务容器来实现 ** 控制反转 ** 和 ** 依赖注入 **。

    什么是控制反转(IoC)和依赖注入(DI)

    控制反转(IoC) 就是说把创建对象的** 控制权 进行转移,以前创建对象的主动权和创建时机是由自己把控的,而现在这种权力转移到第三方,也就是 Laravel ** 中的容器。

    依赖注入(DI)则是帮助容器实现在运行中动态的为对象提供提依赖的资源。

    概念容易不太容易让人理解,举个栗子:

    //我们构建一个人的类和一个狗的类
     class People
    {
        public $dog = null;
    
        public function __construct()
        {
            $this->dog = new Dog();
        }
    
        public function putDog(){
            return $this->dog->dogCall();
        }
    
    }
    
    class Dog{
        public function dogCall(){
            return '汪汪汪';
        }
    }

    这个人在遛狗,突然遇到了死对头,他于是放狗咬人

    $people = new People();
    $people->putDog();

    在这个操作中,people类要执行putDog()这个方法,需要依赖Dog类,一般我们像上面一样,在people中利用构造函数来添加这个Dog依赖。如果使用控制反转 依赖注入则是这个样子

    class People
    {
        public $dog = null;
    
        public function __construct(Dog $Dog)
        {
            $this->dog = $dog;
        }
    
        public function putDog(){
            return $this->dog->dogCall();
        }
    
    }

    People类通过构造参数声明自己需要的 依赖类,由容器自动注入。这样就实现了程序的有效解耦,好处在这就不多说了。

    Laravel容器依赖注入的实现

    实现原理需要了解的知识点:

    闭包(匿名函数):
    匿名函数(Anonymous functions),也叫闭包函数(closures),允许 临时创建一个没有指定名称的函数

    反射:PHP 5 以上版本具有完整的反射 API,添加了对类、接口、函数、方法和扩展进行反向工程的能力。 此外,反射 API 提供了方法来取出函数、类和方法中的文档注释

    理解了闭包和反射的基本用法我们来看Laravel中是怎么实现容器的,下面代码是我对laravel框架容器部分代码的简化核心版:

    lass Container
    {
        /**
         *  容器绑定,用来装提供的实例或者 提供实例的回调函数
         * @var array
         */
        public $building = [];
    
        /**
         * 注册一个绑定到容器
         */
        public function bind($abstract, $concrete = null, $shared = false)
        {
            if(is_null($concrete)){
                $concrete = $abstract;
            }
    
            if(!$concrete instanceOf Closure){
                $concrete = $this->getClosure($abstract, $concrete);
            }
    
            $this->building[$abstract] =  compact("concrete", "shared");
        }
    
        //注册一个共享的绑定 单例
        public function singleton($abstract, $concrete, $shared = true){
            $this->bind($abstract, $concrete, $shared);
        }
    
        /**
         * 默认生成实例的回调闭包
         *
         * @param $abstract
         * @param $concrete
         * @return Closure
         */
        public function getClosure($abstract, $concrete)
        {
            return function($c) use($abstract, $concrete){
                $method = ($abstract == $concrete)? 'build' : 'make';
    
                return $c->$method($concrete);
            };
        }
    
        /**
         * 生成实例 
         */
        public function make($abstract)
        {
            $concrete = $this->getConcrete($abstract);
    
            if($this->isBuildable($concrete, $abstract)){
                $object = $this->build($concrete);
            }else{
                $object = $this->make($concrete);
            }
    
            return $object;
        }
    
        /**
         * 获取绑定的回调函数
         */
        public function getConcrete($abstract)
        {
            if(! isset($this->building[$abstract])){
                return $abstract;
            }
    
            return $this->building[$abstract]['concrete'];
        }
    
        /**
         * 判断 是否 可以创建服务实体
         */
        public function isBuildable($concrete, $abstract)
        {
            return $concrete === $abstract || $concrete instanceof Closure;
        }
    
        /**
         * 根据实例具体名称实例具体对象
         */
        public function build($concrete)
        {
            if($concrete instanceof Closure){
                return $concrete($this);
            }
    
            //创建反射对象
            $reflector = new ReflectionClass($concrete);
    
            if( ! $reflector->isInstantiable()){
                //抛出异常
                throw new \Exception('无法实例化');
            }
    
            $constructor = $reflector->getConstructor();
            if(is_null($constructor)){
                return new $concrete;
            }
    
            $dependencies = $constructor->getParameters();
            $instance = $this->getDependencies($dependencies);
    
            return $reflector->newInstanceArgs($instance);
    
        }
    
        //通过反射解决参数依赖
        public function getDependencies(array $dependencies)
        {
            $results = [];
            foreach( $dependencies as $dependency ){
                $results[] = is_null($dependency->getClass())
                    ?$this->resolvedNonClass($dependency)
                    :$this->resolvedClass($dependency);
            }
    
            return $results;
        }
    
        //解决一个没有类型提示依赖
        public function resolvedNonClass(ReflectionParameter $parameter)
        {
            if($parameter->isDefaultValueAvailable()){
                return $parameter->getDefaultValue();
            }
            throw new \Exception('出错');
    
        }
    
        //通过容器解决依赖
        public function resolvedClass(ReflectionParameter $parameter)
        {
            return $this->make($parameter->getClass()->name);
    
        }
    
    }

    容器的工作流程

    接着上面遛狗的例子:

    //实例化容器类
    $app =  new Container();
    //向容器中填充Dog
    $app->bind('Dog','App\Dog');
    //填充People
    $app->bind('People', 'App\People');
    //通过容器实现依赖注入,完成类的实例化;
    $people = $app->make('People');
    //调用方法
    echo $people->putDog();

    上面示例中我们先实例化容器类,然后使用bind()方法 绑定接口和 生成相应的实例的闭包函数。然后使用make() 函数生成实例对象,在make()中会调用 isBuildable($concrete, $abstract) 来判断 给定的服务实体($concrete参数)是否可以创建,可以创建 就会调用 build($concrete) 函数 ,build($concrete) 函数会判断传的参数是 是** 闭包 还是 具体类名 **,如果是闭包则直接运行,如果是具体类名的话,则通过反射获取该类的构造函数所需的依赖,完成实例化。

    ** 重点理解 下面这几个函数中 反射的用法,应该就很好理解了 **
    build($concrete) getDependencies(array $dependencies) resolvedNonClass(ReflectionParameter $parameter) resolvedClass(ReflectionParameter $parameter)

    最后

    IoC 理解起来是有点难度,可能文中描述让你感觉不是很清楚,可以将文中代码 在php中用debug观察 运行状态。
    理解了容器的具体实现原理,再去看Laravel中的相关实现,就会感觉豁然开朗。

    原文地址:https://www.cnblogs.com/lilili/p/6953749.html 

    展开全文
  • Laravel 服务容器是用于管理类的依赖和执行依赖注入的工具。 依赖注入的本质是通过构造函数或者某些情况下通过 setter 方法,将类的依赖注入到类中。 来看一个简单的例子: <?php namespace App\...

    简介

    Laravel 服务容器是用于管理类的依赖和执行依赖注入的工具。

    依赖注入的本质是通过构造函数或者某些情况下通过 setter 方法,将类的依赖注入到类中。

    来看一个简单的例子:

    <?php
    
    namespace App\Http\Controllers;
    
    use App\User;
    use App\Repositories\UserRepository;
    use App\Http\Controllers\Controller;
    
    class UserController extends Controller
    {
        /**
         * 用户存储库的实现。
         *
         * @var UserRepository
         */
        protected $users;
    
        /**
         * 创建新的控制器实例。
         *
         * @param  UserRepository  $users
         * @return void
         */
        public function __construct(UserRepository $users)
        {
            $this->users = $users;
        }
    
        /**
         * 显示指定用户的 profile。
         *
         * @param  int  $id
         * @return Response
         */
        public function show($id)
        {
            $user = $this->users->find($id);
    
            return view('user.profile', ['user' => $user]);
        }
    }
    

    在本例中,UserController 需要从数据源获取用户,所以,我们注入了一个可以获取用户的服务 UserRepository,其扮演的角色类似使用 Eloquent 从数据库获取用户信息。注入 UserRepository 后,我们可以在其基础上封装其他实现,也可以模拟或者创建一个假的 UserRepository 实现用于测试。

    深入理解 Laravel 服务容器对于构建功能强大的大型 Laravel 应用而言至关重要,对于贡献代码到 Laravel 核心也很有帮助。

    绑定

    几乎所有的服务容器绑定都是在服务提供者中完成。因此本文档的演示例子用到的容器都是在服务提供者中绑定。

    注:如果一个类没有基于任何接口那么就没有必要将其绑定到容器。容器并不需要被告知如何构建对象,因为它会使用 PHP 的反射服务自动解析出具体的对象。

    简单绑定

    在服务提供者中,可以通过 $this->app 属性访问容器。我们可以通过 bind 方法进行绑定。

    bind 方法需要两个参数,第一个参数是我们想要注册的类名或接口名称,第二个参数是返回类的实例的闭包。

    $this->app->bind('HelpSpot\API', function ($app) {
        return new HelpSpot\API($app->make('HttpClient'));
    });
    

    注意到我们将容器本身作为解析器的一个参数,然后我们可以使用该容器来解析我们正在构建的对象的子依赖。

    绑定一个单例

    singleton 方法绑定一个只会解析一次的类或接口到容器,然后接下来对容器的调用将会返回同一个对象实例。

    $this->app->singleton('HelpSpot\API', function ($app) {
        return new HelpSpot\API($app->make('HttpClient'));
    });
    

    绑定实例

    你还可以使用 instance 方法绑定一个已存在的对象实例到容器,随后调用容器将总是返回给定的实例:

    $api = new HelpSpot\API(new HttpClient);
    
    $this->app->instance('HelpSpot\API', $api);
    

    绑定初始数据

    当你有一个类不仅需要接受一个注入类,还需要注入一个基本值(比如整数)。你可以使用上下文绑定来轻松注入你的类需要的任何值:

    $this->app->when('App\Http\Controllers\UserController')
              ->needs('$variableName')
              ->give($value);
    

    绑定接口到实现

    服务容器有一个强大的功能,就是将接口绑定到给定实现。

    假设有一个 EventPusher 接口及其实现类 RedisEventPusher ,编写完该接口的 RedisEventPusher 实现后,就可以将其注册到服务容器:

    $this->app->bind(
        'App\Contracts\EventPusher',
        'App\Services\RedisEventPusher'
    );
    

    这段代码告诉容器当一个类需要 EventPusher 的实现时将会注入 RedisEventPusher。

    现在,我们可以在构造函数或者任何其它通过服务容器注入依赖项的地方,使用类型提示,来依赖注入 EventPusher 接口。

    use App\Contracts\EventPusher;
    
    /**
     * 创建一个新的类实例
     *
     * @param  EventPusher  $pusher
     * @return void
     */
    public function __construct(EventPusher $pusher)
    {
        $this->pusher = $pusher;
    }
    

    上下文绑定

    有时候,你可能有两个类使用了相同的接口,但你希望每个类都能注入不同的实现。

    例如,两个控制器可能需要依赖不同的 Illuminate\Contracts\Filesystem\Filesystem 契约 实现。 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');
              });
    

    标签

    有时候,你可能需要解析某个「分类」下的所有绑定。

    例如,你正在构建一个报表的聚合器,它接收多个不同的 Report 接口实现。在注册完 Report 实现之后,可以通过 tag 方法给它们分配一个标签。

    $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);
    });
    

    解析

    有很多方式可以从容器中解析出我们想要的对象。

    make 方法

    可以使用 make 方法将容器中的类实例解析出来。

    make 方法接收要解析的类或接口的名称:

    $api = $this->app->make('HelpSpot\API');
    

    resolve 方法

    如果你的代码处于不能访问 $app 变量的位置,你可以使用全局的辅助函数 resolve 进行解析:

    $api = resolve('HelpSpot\API');
    

    makeWith 方法

    如果某些类的依赖项不能通过容器去解析,那你可以通过将它们作为关联数组传递到 makeWith 方法来注入它们。

    $api = $this->app->makeWith('HelpSpot\API', ['id' => 1]);
    

    自动注入

    自动注入是最常用的解析方式。

    在具体实践中,这是大多数对象从容器中解析的方式。

    可以简单地使用「类型提示」的方式,在由容器解析的类的构造函数中添加依赖项。

    控制器、事件监听器、队列任务、中间件等都是通过这种方式。

    <?php
    
    namespace App\Http\Controllers;
    
    use App\Users\Repository as UserRepository;
    
    class UserController extends Controller
    {
        /**
         * 用户存储库实例。
         */
        protected $users;
    
        /**
         * 创建一个新的控制器实例。
         *
         * @param  UserRepository  $users
         * @return void
         */
        public function __construct(UserRepository $users)
        {
            $this->users = $users;
        }
    
        /**
         * 显示指定 ID 的用户信息。
         *
         * @param  int  $id
         * @return Response
         */
        public function show($id)
        {
            //
        }
    }
    

    容器事件

    服务容器在每一次解析对象时都会触发一个事件,可以使用 resolving 方法监听该事件:

    $this->app->resolving(function ($object, $app) {
        // 当容器解析任何类型的对象时调用...
    });
    
    $this->app->resolving(HelpSpot\API::class, function ($api, $app) {
        // 当容器解析类型为「HelpSpot\API」的对象时调用...
    });
    

    如你所见,被解析的对象将会传递给回调函数,从而允许你在对象被传递给消费者之前为其设置额外属性。

    PSR-11

    Laravel 的服务容器实现了 PSR-11 接口。

    因此,你可以通过类型提示 PSR-11 容器接口来获取 Laravel 容器的实例:

    use Psr\Container\ContainerInterface;
    
    Route::get('/', function (ContainerInterface $container) {
        $service = $container->get('Service');
    
        //
    });
    

    注:如果绑定到容器的唯一标识有冲突,调用 get 方法会抛出异常。

    展开全文
  • 主要介绍了详解如何实现Laravel服务容器的方法示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • 这段代码来自《laravel 框架关键技术解析》书中的对框架容器框架的提炼,很好的还原了laravel服务容器的核心思想,有兴趣的童鞋可以自己执行并研究下代码的逻辑。 <?php interface Visit { public ...

    这段代码来自《laravel 框架关键技术解析》书中的对框架容器框架的提炼,很好的还原了laravel 的服务容器的核心思想,有兴趣的童鞋可以自己执行并研究下代码,研究下laravel框架容器的基本实现原理。

    <?php
    
    interface Visit
    {
        public function go();
    }
    
    class Train implements Visit
    {
        public function go()
        {
            echo "go to Tibet by train" . PHP_EOL;
        }
    }
    
    
    
    class Leg implements Visit
    {
        public function go()
        {
            echo 'go to Tibet by leg' . PHP_EOL;
        }
    }
    
    
    class Car implements Visit
    {
        public function go()
        {
            echo 'go to Tibet by car' . PHP_EOL;
        }
    }
    
    
    
    class Traveller
    {
        protected $trafficTool;
    
    
    
        public function __construct(Visit $trafficTool)
        {
            $this->trafficTool = $trafficTool;
        }
    
    
    
        public function visitTibet()
        {
            $this->trafficTool->go();
        }
    }
    
    
    
    /**
     * 设计容器类,容器类里装实例或者提供实例的回调函数
     */
    class Container
    {
        //用于装提供实例的回调函数,真正的容器还会装其它内存,从而实现单例等高级功能
        public $bindings = [];
    
    
    
        /**
         * 绑定接口和生成相应实例的回调函数
         * @param  string $abstract
         * @param  string $concrete
         * @param  mixed $shared
         */
        public function bind($abstract, $concrete = null, $shared = false)
        {
            if ( !$concrete instanceof Closure ) {
                //如果不是回调函数,则产生默认的回调
                $concrete = $this->getClosure($abstract, $concrete);
            }
    
            //将回调函数注册到数组中
            $this->bindings[$abstract] = compact('concrete', 'shared');
        }
    
    
    
        /**
         * 默认生成实例的回调函数
         * @param  string $abstract
         * @param  string $concrete
         * @return  mixed
         */
        public function getClosure($abstract, $concrete)
        {
            //生成实例的回调函数,$c一般为IoC容器对象,在调用回调生成实例时提供,即build函数中的$concrete($this)中实现调用
            return function ($c) use ($abstract, $concrete) {
    
                $method = ($abstract == $concrete) ? 'build' : 'make';
    
                return $c->$method($concrete);
            };
        }
    
    
    
        /**
         * 生成实例对象,首先要解决接口和要实例化类之间的依赖关系,通过make,指定生成注册树上的绑定的类
         * @param  string $abstract
         * @return  object $object
         */
        public function make($abstract)
        {
            $concrete = $this->getConcrete($abstract);//获取依赖
            if ( $this->isBuildable($concrete, $abstract) ) {
                $object = $this->build($concrete);
            } else {
                $object = $this->make($concrete);
            }
    
            return $object;
        }
    
    
    
        /**
         * 生成实例对象,首先要解决接口和要实例化类之间的依赖关系,通过make,指定生成注册树上的绑定的类
         */
        public function build($concrete)
        {
            if ( $concrete instanceof Closure ) {
                return $concrete($this);//这个用法不是很清楚
            }
    
            //生成反射类
            $reflector = new ReflectionClass($concrete);
            //判断该类是否能实例化
            if ( !$reflector->isInstantiable() ) {
                echo $message = '该类不能被实例化';
            }
            $constructor = $reflector->getConstructor();
            //如果没有依赖的类,那么就实例化自身
            if ( is_null($constructor) ) {
                return new $concrete;
            }
            $dependencies = $constructor->getParameters();
            $instance = $this->getDependencies($dependencies);//获取依赖的对象
    
            return $reflector->newInstanceArgs($instance);//这里实现依赖注入
        }
    
    
    
        /**
         * 获取依赖的对象
         * @param  array $parameters
         * @return array $dependencies
         */
        protected function getDependencies($parameters)
        {
            $dependencies = [];
            foreach ($parameters as $parameter) {
                $dependency = $parameter->getClass();//获取依赖的类名
                if ( is_null($dependency) ) {
                    $dependencies[] = null;
                } else {
                    $dependencies[] = $this->resolveClass($parameter);
                }
            }
    
            return (array)$dependencies;
        }
    
    
    
        protected function resolveClass(ReflectionParameter $parameter)
        {
            return $this->make($parameter->getClass()->name);
        }
    
    
    
        /**
         * 获取绑定的回调函数
         * @param  string $abstract
         * @return  mixed $abstract
         */
        public function getConcrete($abstract)
        {
            if ( !isset($this->bindings[$abstract]) ) {
                echo '$abstract=' . $abstract . PHP_EOL;
    
                return $abstract;
            }
    
            return $this->bindings[$abstract]['concrete'];
        }
    
    
    
        /**
         * 判断是否达到创建实例的条件
         * @param  string $concrete
         * @param  string $abstract
         * @return  mixed
         */
        public function isBuildable($concrete, $abstract)
        {
            return $concrete === $abstract || $concrete instanceof Closure;
        }
    }
    
    
    
    //实例化IoC容器
    $app = new Container();
    //完成容器的填充
    //$app->bind('Visit', 'Train');//给Visit接口绑定具体的类
    //$app->bind('Visit', 'Leg');//给Visit接口绑定具体的类
    $app->bind('Visit', 'Car');//给Visit接口绑定具体的类
    $app->bind('traveller', 'Traveller');
    //通过容器进行以来注入,完成类的实例化
    $traveller = $app->make('traveller');
    $traveller->visitTibet();
    

     

    展开全文
  • 文章目录前言容器的定义Laravel容器的解释服务容器几种绑定的方式容器注册容器注入后记 前言 容器的定义 容器,字面上理解就是装东西的东西。常见的变量、对象属性等都可以算是容器。一个容器能够装什么,全部取决于...

    没写完

    前言

    容器的定义

    容器,字面上理解就是装东西的东西。常见的变量、对象属性等都可以算是容器。一个容器能够装什么,全部取决于你对该容器的定义。当然,有这样一种容器,它存放的不是文本、数值,而是对象、对象的描述(类、接口)或者是提供对象的回调,通过这种容器,我们得以实现许多高级的功能,其中最常提到的,就是 “解耦” 、“依赖注入(DI)”。

    Laravel容器的解释

    Laravel 的核心就是一个 IoC 容器,根据文档,称其为“服务容器”,顾名思义,该容器提供了整个框架中需要的一系列服务。作为初学者,很多人会在这一个概念上犯难,因此,我打算从一些基础的内容开始讲解,通过理解面向对象开发中依赖的产生和解决方法,来逐渐揭开“依赖注入”的面纱,逐渐理解这一神奇的设计理念。

    Laravel 服务容器是一个用于管理类依赖和执行依赖注入的强大工具。

    依赖注入:只要不是由内部生产(比如初始化、构造函数 __construct 中通过工厂方法、自行手动 new 的),而是由外部以参数或其他形式注入的,都属于依赖注入(DI)(个人觉得最清晰的解释

    服务容器

    容器的获取

    • 使用函数获取

    app这个辅助函数定义在vendor/laravel/lumen-framework/src/helpers.php 里面,,这个文件定义了很多help函数,并且会通过composer自动加载到项目中(autoload_static.php文件中可以看到它,composer的具体实现可以自己百度去看看)。所以,在参与http请求处理的任何代码位置都能够访问其中的函数,比如app()

    $app = app();
    
    • 使用门面获取

    这个其实是用到Facade(门面),在laravel中会存在一个名称为aliases的数组,专门用来配置一些类型的别名,在lumenApp的别名是在vendor/barryvdh/laravel-ide-helper/src/Generator.php:244中定义了的,还有一些其他的门面定义,可以自行查看

    use App;
    
    App::basePath();
    
    • 依赖注入

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

    abstract class ServiceProvider
    {
        protected $app;
    	...
    }
    
    $tis->app->singleton(...);
    

    容器绑定

    如果类没有依赖任何接口,就没有必要将类绑定到容器中。容器不需要指定如何构建这些对象,因为它可以使用反射自动解析这些对象。

    在接口\Illuminate\Contracts\Container\Container中定义了容器绑定和解析的各种方法,接口Container存在一个实现类\Illuminate\Container\Container\Laravel\Lumen\Application继承了它的实现类,容器$app就是类Application的实现

    下面先看看各个方法做了些什么事,最后看Lumen从入口进来是如何调用服务提供器的

    简单绑定

    前面说了我们在服务提供器里面可以通过 $this->app 属性访问容器。我们可以通过 bind 方法注册绑定,传递我们想要注册的类或接口名称再返回类的实例的 Closure
    注意,我们接受容器本身作为解析器的参数。然后,我们可以使用容器来解析正在构建的对象的子依赖。

    $this->app->bind('HelpSpot\API', function ($app) {
        return new HelpSpot\API($app->make('HttpClient'));
    });
    

    我们看看bind方法具体的做了些什么事

    public function bind($abstract, $concrete = null, $shared = false)
    {
        $this->dropStaleInstances($abstract);
    
        if (is_null($concrete)) {
            $concrete = $abstract;
        }
    
        if (! $concrete instanceof Closure) {
            $concrete = $this->getClosure($abstract, $concrete);
        }
    
        $this->bindings[$abstract] = compact('concrete', 'shared');
    
        if ($this->resolved($abstract)) {
            $this->rebound($abstract);
        }
    }
    

    1、根据一个名称,删除已有的所有实例和别名,就是删除instance数组和aliases数组中已存在的值

    protected function dropStaleInstances($abstract)
    {
        unset($this->instances[$abstract], $this->aliases[$abstract]);
    }
    

    2、判断是否给了具体类型$concrete,没有的话把抽象类型$abstract赋值给具体类型$concrete
    3、如果具体类型$concrete不是一个闭包,不是的话,则根据抽象类型$abstract和具体类型$concrete转成一个闭包

    • 抽象类型和具体类型相同,返回一个build的实例,实例化给定的具体实例
    • 不相同则返回makeWith的实例,从容器中解析出一个实例
    protected function getClosure($abstract, $concrete)
    {
        return function ($container, $parameters = []) use ($abstract, $concrete) {
            if ($abstract == $concrete) {
                return $container->build($concrete);
            }
    
            return $container->makeWith($concrete, $parameters);
        };
    }
    

    4、以$abstractkey将具体类型$concrete以及$shared的值绑定到bindings的成员数组中
    5、判断$abstract是否被解析,已经被解析,存在实例,rebound一次?(不太懂这个操作,不知道对不对)

    绑定接口到实现

    服务容器有一个强大的功能,就是将接口绑定到给定实现。例如,如果我们有一个 EventPusher 接口和一个 RedisEventPusher 实现。编写完接口的 RedisEventPusher 实现后,我们就可以在服务容器中注册它,像这样:

    $this->app->bind(
        'App\Contracts\EventPusher',
        'App\Services\RedisEventPusher'
    );
    

    这么做相当于告诉容器:当一个类需要实现 EventPusher 时,应该注入 RedisEventPusher。现在我们就可以在构造函数或者任何其他通过服务容器注入依赖项的地方使用类型提示注入 EventPusher 接口:

    use App\Contracts\EventPusher;
    
    /**
     * 创建一个新的类实例
     *
     * @param  EventPusher  $pusher
     * @return void
     */
    public function __construct(EventPusher $pusher)
    {
        $this->pusher = $pusher;
    }
    

    这个用法感觉还可以,但是一个接口也只是能绑定一个实现,其他要同一个接口,不同实现的,这个就没啥用了(laravel给出了上下文绑定的方式),但是对修改方便,实现一个新类,修改一下绑定就可以了(个人想法)

    绑定一个单例

    singleton 方法将类或接口绑定到只能解析一次的容器中。绑定的单例被解析后,相同的对象实例会在随后的调用中返回到容器中:

    $this->app->singleton('HelpSpot\API', function ($app) {
        return new HelpSpot\API($app->make('HttpClient'));
    });
    

    看看singleton方法中做了些什么事情

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

    shared设置为true,然后调用build方法,参见上面

    绑定实例

    你也可以使用 instance 方法将现有对象实例绑定到容器中。给定的实例会始终在随后的调用中返回到容器中:

    $api = new HelpSpot\API(new HttpClient);
    
    $this->app->instance('HelpSpot\API', $api);
    

    直接创建一个类对象,然后调用instance,看看instance()方法做了些什么

    public function instance($abstract, $instance)
    {
        $this->removeAbstractAlias($abstract);
    
        $isBound = $this->bound($abstract);
    
        unset($this->aliases[$abstract]);
        
        $this->instances[$abstract] = $instance;
    
        if ($isBound) {
            $this->rebound($abstract);
        }
    }
    

    1、从上下文绑定的别名aliases中删除已存在的abstractAliases缓存
    2、在bindingsinstancesaliases中查找是否存在抽象类型$abstract的绑定
    3、删除aliases中的绑定
    4、在instances上绑定该实例
    5、根据第二步的判断,如果存在实例,rebound一次(又来了,不知道干嘛?)

    绑定初始数据

    当你有一个类不仅需要接受一个注入类,还需要注入一个基本值(比如整数)。你可以使用上下文绑定来轻松注入你的类需要的任何值

    $this->app->when('App\Http\Controllers\UserController')
              ->needs('$variableName')
              ->give($value);
    

    这种场景,我没有使用过,我们也大概看看他们是做些什么处理

    public function when($concrete)
    {
        return new ContextualBindingBuilder($this, $this->getAlias($concrete));
    }
    
    public function needs($abstract)
    {
        $this->needs = $abstract;
    
        return $this;
    }
    
    public function give($implementation)
    {
        $this->container->addContextualBinding(
            $this->concrete, $this->needs, $implementation
        );
    }
    

    1、when()函数里面实例化了一个ContextualBindingBuilder对象(上下文绑定对象),传入container对象以及在aliases数组最终的信息
    2、needs()函数,对当前对象的成员变量needs赋值抽象类型$abstract
    3、give()函数,调用container对象的addContextualBinding方法根据抽象类型和具体类型添加一个上下文绑定到contextual这个变量中,值为给定的变量(give里面的参数)

    上下文绑定

    有时候,你可能有两个类使用了相同的接口,但你希望每个类都能注入不同的实现。例如,两个控制器可能需要依赖不同的 Illuminate\Contracts\Filesystem\Filesystem 契约 实现。 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');
              });
    

    函数调用和绑定初始数据一样

    标记

    有时候,你可能需要解析某个「分类」下的所有绑定。例如,你正在构建一个报表的聚合器,它接收一个包含不同 Report 接口实现的数组。注册了 Report 实现后,你可以使用 tag 方法为其分配标签:

    $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'));
    });
    

    tag函数将第一个参数的信息,绑定container的成员变量tags中;
    tagged函数将这个标签中的所有抽象类型都实例化,返回。相当于给构造函数传了一个数组,数组内有很多不同的对象

    绑定大概就是这么多方式了

    服务解析

    我现阶段的代码都是在Controlleruse xxFacaed,然后使用调用`````

    make
    public function make($abstract)
    {
        $abstract = $this->getAlias($abstract);
    
        if (array_key_exists($abstract, $this->availableBindings) &&
            ! array_key_exists($this->availableBindings[$abstract], $this->ranServiceBinders)) {
            $this->{$method = $this->availableBindings[$abstract]}();
    
            $this->ranServiceBinders[$method] = true;
        }
    
        return parent::make($abstract);
    }
    

    1、获取抽象类型$abstract的最终别名
    2、判断$abstract是否默认可用的绑定availableBindings,而且未执行,则先执行availableBindings中对应的方法
    3、调用父类的make方法,该方法只有一句return $this->resolve($abstract);

    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);
    
        if ($this->isBuildable($concrete, $abstract)) {
            $object = $this->build($concrete);
        } else {
            $object = $this->make($concrete);
        }
    
        foreach ($this->getExtenders($abstract) as $extender) {
            $object = $extender($object, $this);
        }
    
        if ($this->isShared($abstract) && ! $needsContextualBuild) {
            $this->instances[$abstract] = $object;
        }
    
        $this->fireResolvingCallbacks($abstract, $object);
    
        $this->resolved[$abstract] = true;
    
        array_pop($this->with);
    
        return $object;
    }
    

    从容器中解析给定的类型:
    1、获取最终的别名信息
    2、判断是否需要绑定上下文,存在参数或者

    依赖注入的实现

    路由中的request就是在解析完所有的请求信息时,使用instance的方式绑定到容器的,后面不论在哪里调用request对象,都是同一个

    后记

    本文一些定义抄的地址
    https://learnku.com/articles/4698/laravel-core-ioc-service-container
    https://laravelacademy.org/post/769.html

    展开全文
  • 背景 最近在看项目中前辈的代码,发现使用队列发送消息的时候,从一开始整合发送消息需要数据的时候,就...所以laravel服务容器就像一个高度自动化的工厂,你需要的东西,定制好模型,使用特定接口来制造就可以了;而
  • 刚刚接触laravel,对于laravel服务容器不是很理解。看了《Laravel框架关键技术解析》和网上的一些资料后对于服务容器有了一些自己的理解,在这里分享给大家 1、依赖 IoC模式主要是用来解决系统组件之间相互依赖...
  • laravel IoC容器心得

    千次阅读 2018-01-24 13:33:52
    前言 laravel现在已经是一个非常成熟的PHP框架,在学习laravel的这一段时间内,laravel...在刚刚开始使用laravel的时候,说实话,阅读laravel文档有关服务容器服务提供者的章节的时候是很迷茫的。由于其中涉及了很多
  • 关于laravel服务(容器)和服务提供者及绑定 Laravel 服务容器是一个用于管理类依赖和执行依赖注入的强大工具。 参考:https://laravelacademy.org/post/9534.html <?php namespace App\Http\Controllers\UcServer...
  • interface Visit{ public function go();}class Train implements Visit{ public function go() { echo "go to Tibet by train"; }}class Traveller{ protected $trafficTool; public func...
  • 而这里laravel说的容器,抽象出来也就是一种放置各种服务容器,具体的服务则是指每一个类,比如说我们写入一个发送邮件的类 class SendMail{ public function send() { echo "发送邮件"; ...
  • 前言 通过实现laravel 框架功能,以便...Laravel框架中就是使用服务容器来实现 控制反转 和 依赖注入 。 什么是控制反转(IoC)和依赖注入(DI) 控制反转(IoC) 就是说把创建对象的 控制权 进行转移,以前创建对象...
  • 本文首发于 深入剖析 Laravel 服务容器,转载请注明出处。喜欢的朋友不要吝啬你们的赞同,谢谢。 之前在 深度挖掘 Laravel 生命周期 一文中,我们有去探究 Laravel 究竟是如何接收 HTTP 请求,又是如何生成响应并...
  • laravel的App::make()是如何解决实例化时所需的依赖的? 比如说:A类的构造函数参数依赖B类 class A {public function __...而laravel容器可以这样:App:make(A::class),自动的解决了B类的依赖,它是如何做到的?
  • 深入Laravel服务容器

    2016-12-09 18:16:22
    在开始之前要明确一个概念,不管是设计模式,还是依赖注入等等,都是为了实现模块化.所谓模块化就是希望一个软件是由很多子模块组成的,这些模块之间的依赖程度尽量的低,...你应该听过,Laravel中的服务容器其本质上是一...
  • 深入浅出 Laravel 路由执行原理

    千次阅读 2018-07-13 12:15:10
    本文首发于「深入浅出 Laravel 路由执行原理」,转载请注明出处。这篇文章我们将学习 Laravel 项目中一个很重要的主题 --「路由」。可以说几乎所有的框架都会涉及到「路由」的处理,简单一点讲就将用户请求的 url ...
  • 服务容器】 管理类的依赖和执行依赖注入。 原理: 1.服务容器绑定操作都是在 服务提供器 中注册。 2.类的依赖项通过构造函数,或者某些情况下通过「setter」方法「注入」到类中。 解析实例: 1.通过 make 方法...
  • 你的应用程序以及 Laravel 的所有核心服务都是通过服务提供者进行引导。 在这里,我们说的「引导」其实是指注册,通常,这意味着注册服务,包括注册服务容器绑定、事件监听器、中间件甚至路由。 服务提供者是应用...
  • Laravel 框架运行原理

    2020-12-28 09:20:19
    Laravel框架入口文件为:index.php 1、引入自动加载autoload.php文件 2、创建应用实例,并同时完成了 基本绑定($this、容器类Container等等)、 基本服务提供者的注册(Event、log、routing)、 核心类别名的...
  • Laravel—IOC容器详解

    2019-03-18 11:11:45
    laravel框架中, 服务容器是整个laravel的核心,它提供了整个系统功能及服务的配置, 调用. 容器按照字面上的理解就是装东西的东西,比如冰箱, 当我们需要冰箱里面的东西的时候直接从里面拿就行了. 服务容器也...
  • laravel查询底层原理

    2019-03-15 09:47:10
    说明:本文主要学习Laravel Database模块的Query Builder源码。实际上,Laravel通过Schema Builder来设计数据库,通过Query Builder来CURD数据库。Query Builder并不复杂或神秘,只是在PDO扩展的基础上又开放封闭的...
  • 其实我看到这些关键词的时候,脑子里有一个定论,但是呢,一知半解,也是深受痛苦,怎么才能不痛苦呢,那就是理解他们之间的关系、原理,各个击破,这才是最好的办法,不然,看到一次,你后悔一次,看到一次,你头痛...
  • 你应该听过,Laravel中的服务容器其本质上是一个IoC容器,但是好像队IoC又不是很了解,讲来讲去优点很多,功能很强劲.但是不懂原理怎么用都不踏实啊.所以,这里我们自己来实现一个IoC容器,洞察其本质. 在开始之前,先说明...
  • laravel依赖注入原理

    千次阅读 2017-06-07 11:17:04
    laravel依赖注入浅析laravel容器包含控制反转和依赖注入,使用起来就是,先把对象bind好,需要时可以直接使用make来取就好。 具体分析参照:http://laravelacademy.org/post/769.html通常我们的调用如下。$config =...
  • 当你理解了 Laravel 的工作原理,用起来才能游刃有余。 这篇文档的目标就是从更高层面向你阐述 Laravel 框架的工作原理。通过对框架更全面的了解,你将会更加自信地构建应用。 如果你不能马上理解所有的内容,不要...
  • http://huifeng.me/2015/12/23/laravel-%E6%9C%8D%E5%8A%A1%E6%8F%90%E4%BE%9B%E8%80%85-%E6%88%91%E6%98%AF%E8%BF%99%E4%B9%88%E7%90%86%E8%A7%A3%E7%9A%84/ http://blog.csdn.net/hel12he/article/det

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,104
精华内容 441
关键字:

laravel服务容器原理