2.laravel启动流程分析(2)-服务容器

232 阅读4分钟

有了上一节的依赖注入的基础之后,现在我们来看Laravel的服务容器。服务容器就是管理类依赖和执行类依赖的容器。
绑定就是将类的依赖注入到容器之中,Laravel的绑定方法

  1. $this->app->bind()
  2. $this->app->singleton() 绑定一个单例

Laravel容器更详细的介绍,请参考这边非常详细的文章 www.insp.top/learn-larav…

绑定的方式主要有三种方式

//使用回调函数方法绑定
$this->app->bind("Hello",function (){
    return new HelloService();
});
//绑定一个类
$this->app->bind("Hello2",HelloService::class);
//绑定一个已存在的类
$this->app->instance("App\Service\HelloService",new HelloService());
//绑定一个单例
$this->app->singleton("Hello3",HelloService::class);
//绑定一个接口约定的
$config = "database";
if($config == "file") {
    $this->app->bind('AppService\Imp\AppendData','App\Service\AppendWrite\DataBaseWrite');
}else{
    $this->app->bind('AppService\Imp\AppendData','App\Service\AppendWrite\FileWrite');
}

下面分别介绍这几种绑定方式的实际运用

先创建一个Service类,用来构造实例类

//实例类
class HelloService {
    public $name;
    public function __construct()
    {

    }


    public function sayHello(){
        return "Hello";
    }
}

Laravel启动会加AppServiceProvider,我们在此方法里面进行注入

//Providers/AppServiceProvider.php

//使用构造方法绑定
$this->app->bind("Hello",function (){
    return new HelloService();
});
//绑定一个类
$this->app->bind("Hello2",HelloService::class);
//绑定一个已存在的类
$this->app->instance("App\Service\HelloService",new HelloService());
//绑定一个单例
$this->app->singleton("Hello3",HelloService::class);

进行上面多种方式的测试


public function index(Request $request) {
        //获取到绑定的类
      $helloService = app("App\Service\HelloService");
      echo $helloService->sayHello();

      $helloService2 = app("Hello2");
      echo $helloService2->sayHello();

      $helloService3 = app("Hello3");
      $helloService3Copy = app("Hello3");
      //使用单例返回的两个类是同一个类的实力
      var_dump($helloService3 instanceof  $helloService3Copy);
      echo $helloService->sayHello();
}

接下来我们看看bind绑定到接口的运用

//接口类
interface AppendData {
    public function write();
}

//database的实现
class DataBaseWrite implements AppendData {
    public function write()
    {
        echo "我是数据库写入的";
    }
}

//file的实现
class FileWrite implements AppendData {

    public function write()
    {
        echo "我是文件写入的";
    }
}

//使用工厂模式构建类
class WriteFactory {
    public static $instance=[];
    private function __construct()
    {
    }
    private function __clone()
    {
        // TODO: Implement __clone() method.
    }
    //实力的获取
    //这里其实没不要使用单例模式,因为app()->singleton 本身就已经是单例模式了
    public static function instance(string $name) {
        if(empty(self::$instance[$name])) {
            $file = [
                "database"=>"App\Service\AppendWrite\DataBaseWrite",
                "file"=>"App\Service\AppendWrite\FileWrite"
            ];
            //实现接口和类的绑定
            if(isset($file[$name])) {
                app()->singleton("AppService\Imp\AppendData",$file[$name]);
            }else{
                new \Exception("不存在这个驱动");
            }
            self::$instance[$name] = app("AppService\Imp\AppendData");
        }
        return self::$instance[$name];
    }
}

//测试
public function index(Request $request) {
    //获取到绑定的类
    
    $config = "file";
    $write = WriteFactory::instance($config);
    $write->write();
}

通过不同的$config,让工厂方法去获取到类,调用者并不用去关心具体绑定的是哪个类,只用去调用接口中已经存在的方法即可,将调用者和实现者进行解耦,以上用到了单例模式,工厂模式,策略模式

通过以上简单的了解bind绑定,我们已经了解到Laravl处理依赖的方式,接下来我们看看Laravel的源码,是如何实现服务的注册和服务的获取的

//Illuminate/Container/Container.php
public function bind($abstract, $concrete = null, $shared = false)
{
    // If no concrete type was given, we will simply set the concrete type to the
    // abstract type. After that, the concrete type to be registered as shared
    // without being forced to state their classes in both of the parameters.
    //删除instances和aliases的当前数据
    $this->dropStaleInstances($abstract);

    if (is_null($concrete)) {
        $concrete = $abstract;
    }

    // If the factory is not a Closure, it means it is just a class name which is
    // bound into this container to the abstract type and we will just wrap it
    // up inside its own Closure to give us more convenience when extending.
    //如果不是回调函数的话,包装成回调函数
    if (!$concrete instanceof Closure) {
        $concrete = $this->getClosure($abstract, $concrete);
    }
    //赋值变量,并且绑定到bindings中
    $this->bindings[$abstract] = compact('concrete', 'shared');

    // If the abstract type was already resolved in this container we'll fire the
    // rebound listener so that any objects which have already gotten resolved
    // can have their copy of the object updated via the listener callbacks.
    //如果存在resolved和instances中再处理
    if ($this->resolved($abstract)) {
        $this->rebound($abstract);
    }
        
}

//Illuminate/Container/Container.php
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);
        }
    }
    //使用上一节所说的反射构造对象
    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)) {
            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);
    }

上面我们在源码中粗略的分析了bind构造依赖类的方法,下面我们看看Laravel是如何进行Make获取依赖类的

//Illuminate/Container/Container.php
public function make($abstract, array $parameters = [])
{
    return $this->resolve($abstract, $parameters);
}
//Illuminate/Container/Container.php
protected function resolve($abstract, $parameters = [])
    {
        //获取到$abstract的别名
        $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 {
            //如果是借口实现的绑定,在执行一次make,满足上面的判断
            $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.
        //如果服务是以单例方式注册进来的则,把构建好的服务对象放到$instances里
        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;
    }

通过上面的build构建和make获取,我们应该对Laravel服务容器有个大致的了解了, 接下来我们就需要对服务容器进行管理,比如进行统一的注册,统一的初始化,下一章我们将来讲解服务容器的管理--服务提供者