laravel5.8生命周期与源码分析

谢俊英
2023-12-01

laravel的http请求入口是public目录下的index.php。

首先从这个文件进行分析,代码流程走完以后,会出一个逻辑流程图:

<?php


//定义LARAVEL_START常量
define('LARAVEL_START', microtime(true));

//使用composer的自动加载机制(后续分析自动加载是如何实现的)
require __DIR__.'/../vendor/autoload.php';

//创建laravel应用
$app = require_once __DIR__.'/../bootstrap/app.php';

//获取Http内核
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
//处理请求
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);
//将请求结果返回给客户端
$response->send();
//调用http内核的terminate方法,销毁资源
$kernel->terminate($request, $response);

进入当前目录下的bootstrap/app.php文件中,代码段如下:

<?php

//实例化Application,以base_path作为参入传递给构造函数
$app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);


//绑定三种接口的实现 ,在http服务中,只会用到第一种接口的实现
//第二种和第三种是命令行模式和调试模式的接口实现。
//为什么要绑定,因为laravel使用的应用容器模式实现类的依赖注入,为了降低代码的耦合,
//依赖类常常使用接口类或者抽象类作为参数,通过绑定接口类(抽象类)和具体接口的实现,当从容器中
//获取该接口的实现时,通过类型提示即可获取该接口的具体实现,降低了代码的耦合。

$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);
//
$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

return $app;

找到 Illuminate\Foundation\Application,该类继承自Container(laravel最重要的容器类)

也就是说,一个laravel应用就是一个laravel容器

其构造函数代码如下:

    public function __construct($basePath = null)
    {
        //设置应用基础目录
        if ($basePath) {
            $this->setBasePath($basePath);
        }
        //绑定基础类
        $this->registerBaseBindings();
        //注册基础服务提供者。
        $this->registerBaseServiceProviders();
        //设置框架核心类的短名。
        $this->registerCoreContainerAliases();
    }
    
    public function setBasePath($basePath)
    {
        $this->basePath = rtrim($basePath, '\/');

        //绑定路径到容器中。
        $this->bindPathsInContainer();

        return $this;
    }

    protected function bindPathsInContainer()
    {
        //$this->path(),$this->basePath()等都是返回框架设定的目录,以全路径形式返回。
        $this->instance('path', $this->path());
        $this->instance('path.base', $this->basePath());
        $this->instance('path.lang', $this->langPath());
        $this->instance('path.config', $this->configPath());
        $this->instance('path.public', $this->publicPath());
        $this->instance('path.storage', $this->storagePath());
        $this->instance('path.database', $this->databasePath());
        $this->instance('path.resources', $this->resourcePath());
        $this->instance('path.bootstrap', $this->bootstrapPath());
    }

当前实例的instance方法,代码段如下

    public function instance($abstract, $instance)
    {

        /*removeAbstractAlias  方法
        if (! isset($this->aliases[$searched])) {
            return;
        }

        foreach ($this->abstractAliases as $abstract => $aliases) {
            foreach ($aliases as $index => $alias) {
                if ($alias == $searched) {
                    unset($this->abstractAliases[$abstract][$index]);
                }
            }
        }

        */
        //removeAbstractAlias从方法名命名来解释:移除抽象别名
        //上面的实现为:删除当前容器的abstractAliases数组中保存的所有以$abstract为
        //键名的所有成员,保证注册的唯一性。
        $this->removeAbstractAlias($abstract);
         /*bound 方法
               return isset($this->bindings[$abstract]) ||
               isset($this->instances[$abstract]) ||
               $this->isAlias($abstract);
        */
        //在bindings/instances/aliases中,如果有一个存在,则该返回值为真。
        $isBound = $this->bound($abstract);

        //删除aliases中保存的当前abstract对应的值。
        unset($this->aliases[$abstract]);

        //保存进instances数组,(instances:实例,实体)。
        $this->instances[$abstract] = $instance;

        if ($isBound) { 
            /*rebound
           $instance = $this->make($abstract);
           foreach ($this->getReboundCallbacks($abstract) as $callback) {
              call_user_func($callback, $this, $instance);
            }
            */
            //若$isBound为真,也就是曾经向容器中保存过该$abstract
            //则需要重新make(实际调用的是resolve方法)一个实体出来,
            //然后调用rebound注册的回调函数
            //重新触发或者通知相关依赖类
            $this->rebound($abstract);
        }
        
        return $instance;
    }
resolve方法代码段:
 protected function resolve($abstract, $parameters = [], $raiseEvents = true)
    {
        //getAlias方法是一个递归调用的方法,用于获取已经保存在
        //aliases数组中该别名的最终对应abstract。
        $abstract = $this->getAlias($abstract);
        
        //从上下文关系中可以推断出,$needsContextualBuild的值为空
        $needsContextualBuild = (! empty($parameters) || ! is_null(
            $this->getContextualConcrete($abstract)
        ));
        //如果在单例管理数组中查找到该abstract的对应绑定,且不需要重新构建上下文,则
        //直接返回该instance(实例)
        if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
            return $this->instances[$abstract];
        }
        //当前迭代层参数入栈。
        $this->with[] = $parameters;
        //获取该abstract的具体实现
        //从bindings/contextual中获取该abstract的具体实现,如果未获取到,则返回其自身
        $concrete = $this->getConcrete($abstract);
        //判断该concrete 是否可以build,
        //$concrete与$abstract相等,$concrete 是一个闭包(匿名函数),二者满足其一即可。
        if ($this->isBuildable($concrete, $abstract)) {
            $object = $this->build($concrete);
        } else {
            //否则调用make(递归调用resolve),非闭包,且是一个接口的实现。
            $object = $this->make($concrete);
        }
        //对已经生成的object调用extend中已经注册的函数,扩展作用于该object之上
        //可以改变其object的属性
        foreach ($this->getExtenders($abstract) as $extender) {
            $object = $extender($object, $this);
        }
        //该abstract的shared属性是否为真,且不需要重新构建上下文,如果都为真,
        //则将该object保存进
        //容器管理的单例集中去。
        if ($this->isShared($abstract) && ! $needsContextualBuild) {
            $this->instances[$abstract] = $object;
        }
        //该参数默认为true,调用resolve所注册的callback
        if ($raiseEvents) {
            $this->fireResolvingCallbacks($abstract, $object);
        }
        //将当前abstract的resolved属性设置为true
        $this->resolved[$abstract] = true;
        //当前迭代层的参数出栈
        array_pop($this->with);
        //返回该object
        return $object;
    }

getContextualConcrete方法代码段: 

    protected function getContextualConcrete($abstract)
    {
        //若有直接找到该abstract的binding,则直接诶返回
        if (! is_null($binding = $this->findInContextualBindings($abstract))) {
            return $binding;
        }
        //检查在abstractAliases中是否有该abstract的相关绑定,若无,直接返回
        if (empty($this->abstractAliases[$abstract])) {
            return;
        }
        //遍历abstractAliases中该abstract所有的alias,调用findInContextualBindings
        foreach ($this->abstractAliases[$abstract] as $alias) {
            if (! is_null($binding = $this->findInContextualBindings($alias))) {
                return $binding;
            }
        }
    }

    /**
     * Find the concrete binding for the given abstract in the contextual binding array.
     * 在上下文绑定数组中找到给定抽象的具体绑定。
     * @param  string  $abstract
     * @return \Closure|string|null
     */
    protected function findInContextualBindings($abstract)
    {
        //end:输出数组中的最后一个元素的值:
        //buildStack是当前容器的虚拟栈,用于在递归调用make或者build
        //时的函数参数信息上下文。
        if (isset($this->contextual[end($this->buildStack)][$abstract])) {
            return $this->contextual[end($this->buildStack)][$abstract];
        }
    }

从上面代码实现可以看出,resolve最终都是要调用build生成object的,先看build方法,后面在分析在什么情况下会递归调用make(resolve)进行实例生成。

build方法代码段:

    public function build($concrete)
    {
        //如果传参试一个闭包(匿名函数),则直接调用该闭包,并返回
        if ($concrete instanceof Closure) {
            return $concrete($this, $this->getLastParameterOverride());
        }

        //新建反射类 ,反射类可以获取该类的一些基本参数。
        $reflector = new ReflectionClass($concrete);
        //如果类不可以实例化,则给上层抛出一个异常。
        if (! $reflector->isInstantiable()) { //检查类是否可实例化
            //如果不可实例化,则直接抛出一个异常并返回。
            return $this->notInstantiable($concrete);
        }
        //$concrete入栈。依赖类的创建会有递归调用,所以需要用
        //虚拟栈管理上下文。
        $this->buildStack[] = $concrete;
        //获取该类的构造函数。返回一个ReflectionMethod类
        $constructor = $reflector->getConstructor(); 
        //如果无构造函数,则证明无依赖关系。
        if (is_null($constructor)) {
            //当前$concrete出栈
            array_pop($this->buildStack);
            //直接使用new实例化该concrete.
            return new $concrete;
        }
        //有构造函数,则证明有依赖关系,需要获取构造函数的参数列表
        //参数列表是一个数组,数组成员的类型为ReflectionParameter
        $dependencies = $constructor->getParameters();
        try {
            //解析依赖参数,返回一个已经处理好依赖的参数列表
            //如果该构造函数依赖于某个实例(通过类型提示符申明)
            //则获取该实例后返回,依赖可能存在多级。每层都需要处理好。
            $instances = $this->resolveDependencies($dependencies);
        } catch (BindingResolutionException $e) {
            //try代码块执行失败,抛出异常,则执行catch捕获并抛出
            array_pop($this->buildStack);
            throw $e;
        }
        //当前操作的concrete出栈。
        array_pop($this->buildStack);
        //ReflectionClass::newInstanceArgs — 从给出的参数创建一个新的类实例。
        return $reflector->newInstanceArgs($instances);//按照参数实例化一个类
    }

resolveDependencies方法代码块如下:

    protected function resolveDependencies(array $dependencies)
    {

        //用于保存参数列表的数组。
        $results = [];
        //循环参数列表(参数列表是一个数组)
        foreach ($dependencies as $dependency) {
            //在当前with栈中搜索是否存在当前参数,如果存在,则获取然后跳过该循环
            //参数是在resolve方法中入栈的(with栈)
            if ($this->hasParameterOverride($dependency)) {
                $results[] = $this->getParameterOverride($dependency);
                continue;
            }
            //$dependency的类型为ReflectionParameter,
            //ReflectionParameter::getClass — 获得类型提示类。
            $results[] = is_null($dependency->getClass())
                            ? $this->resolvePrimitive($dependency)
                            : $this->resolveClass($dependency);
        }
        //返回参数列表。
        return $results;
    }

    //如果依赖不是一个类。
    protected function resolvePrimitive(ReflectionParameter $parameter)
    {
        //在contextual中搜索该参数的名字 ,该符号是否已经注册,如果已经注册,则使用之 
        //如果获取的concrete 是闭包,则执行该闭包,并返回
        //否则返回其自身。
        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去解析类名。(类名通过getClass获取)
            //进入下一次递归。
            return $this->make($parameter->getClass()->name);
        } 
        //如果我们不能解析这个类实例,
        //我们将检查这个值是否是可选的,如果是,
        //我们将返回可选参数值作为依赖项的值,类似于我们处理标量的方法。
        catch (BindingResolutionException $e) {
            if ($parameter->isOptional()) {
                return $parameter->getDefaultValue();
            }
            //否则抛出异常
            throw $e;
        }
    }

到此处bindPathsInContainer(将当前的基础路径绑定到容器(应用)中)的工作已完成。

接着看构造函数的下一步操作registerBaseBindings

代码块:

    protected function registerBaseBindings()
    {
        
        //static和self的区别
        //static为继承父类的子类自身,而如果self出现在父类,不管子类如何继承,该self所
        //赋的zval一定是其父类本身。
        static::setInstance($this);
        
        // instance在上述流程中已经分析
        // 是容器管理的一个单列集。
        $this->instance('app', $this);
        //多个类名对应一个实例。
        $this->instance(Container::class, $this);
        //singleton方法调用bind,且设置shared默认为true。
        $this->singleton(Mix::class);
        $this->instance(PackageManifest::class, new PackageManifest(
            new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
        ));
    }

    public function bind($abstract, $concrete = null, $shared = false)
    {
        //删除原来注册的(包括instances和alies数组中)
        $this->dropStaleInstances($abstract);

        //如果未给出具体类型,则将abstract赋给concrete 
        if (is_null($concrete)) {
            $concrete = $abstract;
        }
        //若concrete 不是一个闭包,则证明他只是一个类名。
        //则调用getClosure生成一个闭包并将其赋给concrete 
        if (! $concrete instanceof Closure) {
            //此处手动创建一个闭包
            $concrete = $this->getClosure($abstract, $concrete);
        }

        //compact :创建一个包含变量名和它们的值的数组
        //key就是参数,value就是参数所对应的php字符串。
        //将该对应关系存储到容器的bindings数组中,使用compact方法构建一个数组
        $this->bindings[$abstract] = compact('concrete', 'shared');

        //resolved方法检查该abstract是否已经做解析
        //搜寻范围包括resolved数组(abstract若已解析,则该值为true)
        //和instances数组(保存着容器以单例模式管理的具体实现。)
        if ($this->resolved($abstract)) {
            // 调用rebound。
            //则需要重新make(实际调用的是resolve方法)一个实体出来,
            //然后调用rebound注册的回调函数
            //重新触发或者通知相关依赖类
            $this->rebound($abstract);
        }
    }

从上面代码实现中可以看出,instance方法是abstract和实体(对象)的对应,而使用singleton注册的abstract是还未实现的abstract,在使用make(或者其他调用make的方法)后方可获取该abstract的具体实现,这里可以理解为懒加载,在需要使用的地方才会去实例化一个对象出来。

接下来继续看构造函数中的registerBaseServiceProviders具体代码段:

    protected function registerBaseServiceProviders()
    {
        //event,log,route:事件,日志,路由的服务提供者。
        //以new出的对象作为参数,且设置其app属性为当前容器
        //所有的服务都是继承abstract class ServiceProvider(抽象类)
        //在抽象类中定义的抽象方法是子类必须实现的。
        //new出来的三个ServiceProvider子类,都有register方法
        //在分析容器的register方法再看该方法的具体作用。
        $this->register(new EventServiceProvider($this));
        $this->register(new LogServiceProvider($this));
        $this->register(new RoutingServiceProvider($this));
    }
    public function register($provider, $force = false)
    {
        //$force为覆盖标志,如果该值为true,则已注册的服务是需要被覆盖掉的。 
        //判断是否已注册,如已经注册该服务。且覆盖标志为假,则直接返回
        //一个标志可以注册多个服务提供者。
        if (($registered = $this->getProvider($provider)) && ! $force) {
            return $registered;
        }

          //如果注册时是一个字符串,则解析该服务(注册基础服务参数传递进来的是实例化的类。)
        if (is_string($provider)) {
            //resolveProvider方法直接new一个该对象,参数为$this
            $provider = $this->resolveProvider($provider);
        }
        //到此处provider 已经确定是一个实例了。
        
        //调用该服务提供着自己的register方法。
        $provider->register(); 

         
        //property_exists — 检查对象或类是否具有该属性
        //下面两个方法是将所注册服务的bindings和singletons添加进容器中。
        //直接调用bind和singleton的区别
        //bind是无shared标志的,而singleton需要设置shared标志为true,在后续
        //调用的时候,会将其实现保存至instances数组中,以单例模式来管理
        if (property_exists($provider, 'bindings')) {
            //具体实例的bindings是一个数组,用于向容器中绑定对应关系
            foreach ($provider->bindings as $key => $value) {
                $this->bind($key, $value);
            }
        }
           
        if (property_exists($provider, 'singletons')) {
            //具体实例的singletons是一个数组,用于向容器中singleton
            foreach ($provider->singletons as $key => $value) {
                $this->singleton($key, $value);
            }
        }


        //保存该服务至serviceProviders
        //并在loadedProviders(已加载服务)中将该provider(解析出类名)设置为true
        $this->markAsRegistered($provider);
        

        //$this->isBooted():返回容器的booted标志,该属性默认为false。
        //只有调用了容器的boot方法(启动服务提供者),该标志才会置为true
        //这里可以理解,在所有服务提供着启动过后,应用是处于就绪状态的
        //如果此时再向容器中注册新的服务,就需要触发容器的bootProvider方法
        //启动该服务。
        //如果该标志为true,则调用bootProvider,并将$provider作为参数传递进去
        if ($this->isBooted()) {
            //调用实例中存在的boot方法(以该方法存在为前提。)
            $this->bootProvider($provider);
        }
        //返回服务实例。
        return $provider;
    }
    //获取该服务提供者。只需要返回第一个即可。
   public function getProvider($provider)
    {
        //array_values :将数组变换为索引数组(保留原来的键值)
        return array_values($this->getProviders($provider))[0] ?? null;
    }
    //在所有服务中搜寻以provider为名字(或者是实例)注册的服务提供者
    public function getProviders($provider)
    {
        $name = is_string($provider) ? $provider : get_class($provider);
        
        //只有在容器中的serviceProviders数组中,
        //且是该注册服务(一个字符串的类名)的实现,才会返回。
        return Arr::where($this->serviceProviders, function ($value) use ($name) {
            return $value instanceof $name;
        });
    }
    public static function where($array, callable $callback)
    {
        //array_filter — 用回调函数过滤数组中的单元
        //ARRAY_FILTER_USE_KEY - callback接受键名作为的唯一参数
        //ARRAY_FILTER_USE_BOTH - callback同时接受键名和键值
        //只有callback调用返回true(或者大于0),才会将其成员保存至返回的数组。
        return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH);
    }

容器的bootProvider方法。这里简单看下

   protected function bootProvider(ServiceProvider $provider)
    {    
        //检测该服务是否有boot方法,如果有就使用容器的call方法调用boot方法
        if (method_exists($provider, 'boot')) {
            return $this->call([$provider, 'boot']);
        }
    }

    public function call($callback, array $parameters = [], $defaultMethod = null)
    {    
        //将容器本身作为参数一传递给BoundMethod::call
        return BoundMethod::call($this, $callback, $parameters, $defaultMethod);
    }

    //该静态方法存在于BoundMethod类中
    public static function call($container, $callback, array $parameters = [], $defaultMethod = null)
    {        
        //class@method 格式的函数或者defaultMethod标志为真。此处该标志为null
        if (static::isCallableWithAtSign($callback) || $defaultMethod) {
            return static::callClass($container, $callback, $parameters, $defaultMethod);
        }
        //调用callBoundMethod。
        return static::callBoundMethod($container, $callback, function () use ($container, $callback, $parameters) {
           //call_user_func_array — 调用回调函数,并把一个数组参数作为回调函数的参数
           //把第一个参数作为回调函数(callback)调用,把参数2为回调函数的的参数传入。
            //如果执行该闭包,则首先调用call_user_func_array
            return call_user_func_array(
                $callback, static::getMethodDependencies($container, $callback, $parameters)
            );
        });
    }
    //是否是class@method 格式的函数。
    protected static function isCallableWithAtSign($callback)
    {
        return is_string($callback) && strpos($callback, '@') !== false;
    }

    //如果是class@method则调用该方法
    //class@method需要处理依赖,去容器中获取该class的具体实现。
        protected static function callClass($container, $target, array $parameters = [], $defaultMethod = null)
    {
        $segments = explode('@', $target);
        $method = count($segments) === 2
                        ? $segments[1] : $defaultMethod;
        //如果method不存在,需要抛出一个异常
        if (is_null($method)) {
            throw new InvalidArgumentException('Method not provided.');
        }
        //递归调用BoundMethod::call,参数1为容器实例
        //参数二是使用make从容器中获取的该类的一个具体实现和method组成的数组。
        return static::call(
            $container, [$container->make($segments[0]), $method], $parameters
        );
    }
     //按照方法名猜测用途,调用已经bound进container的方法
     //因为bound进methodBindings的方法是有统一格式的(class@method)
        protected static function callBoundMethod($container, $callback, $default)
    {
        //如果callback不是数组,则返回设置的默认函数
        //从call方法中得知,default是一个定义的匿名函数。
        //如果$default是一个闭包,则调用闭包,并返回
        //否则直接返回$default。
        if (! is_array($callback)) {
            return $default instanceof Closure ? $default() : $default;
        }
        //该方法返回一个由类名和method且以@分隔的字符串
        $method = static::normalizeMethod($callback);
        
        //在容器的methodBindings中查找该方法,如果查找到。则使用
        //容器的callMethodBinding方法调用该method
        if ($container->hasMethodBinding($method)) {
            return $container->callMethodBinding($method, $callback[0]);
        }
        //如果上述if都未进入,则按照传入的$default判断是返回
        //$default还是$default()。
        return $default instanceof Closure ? $default() : $default;
    }

    //下面两个方法存在于容器类中
    //该方法返回一个由类名和method且以@分隔的字符串
    protected static function normalizeMethod($callback)
    {
        $class = is_string($callback[0]) ? $callback[0] : get_class($callback[0]);

        return "{$class}@{$callback[1]}";
    }
    //调用容器中已经保存的method
        public function callMethodBinding($method, $instance)
    {
        //调用系统函数call_user_func,
        //该函数参数一为调用的方法,参数2和参数3是传进来的参数
        //从上层代码可以知道,instance是目标类的实现,而$this则为容器实例
        //参数2与参数3是该调用函数的参数。
        return call_user_func($this->methodBindings[$method], $instance, $this);
    }
    //BoundMethod类中的getMethodDependencies,获取方法的依赖项
    protected static function getMethodDependencies($container, $callback, array $parameters = [])
    {
        $dependencies = [];
        //通过调用getCallReflector返回一个反射类,然后调用该反射类的getParameters
        //获取参数列表
        //遍历参数列表,调用addDependencyForCallParameter,将参数依赖处理
        //并保存进$dependencies,
        foreach (static::getCallReflector($callback)->getParameters() as $parameter) {
             //addDependencyForCallParameter方法会返回该参数(如果有依赖,则会先解析依赖)
            static::addDependencyForCallParameter($container, $parameter, $parameters, $dependencies);
        }
        //返回合并的参数列表
        //如果键名相同,array_merge后面出现的元素会将前面的元素覆盖,但是如果健名是数字,
        //则不会进行覆盖。
        //数组合并还有种方式:数组相加,对于+操作
        //不管是数字还是字符串,数组相加会将最先出现的值作为结果,后面键名相同的会被抛弃
        //且如果两个数组中的键名全为数字时,会格式化键名并保留所有键值。
        return array_merge($dependencies, $parameters);
    }
    //为给定回调获取适当的反射实例。
    protected static function getCallReflector($callback)
    {    
        //类名和::分隔的方法名
        if (is_string($callback) && strpos($callback, '::') !== false) {
            $callback = explode('::', $callback);
        }

        //按照$callback的具体值返回一个反射类
        //ReflectionMethod 类报告了一个方法的有关信息。
        //ReflectionFunction 类报告了一个函数的有关信息。
        return is_array($callback)
                        ? new ReflectionMethod($callback[0], $callback[1])
                        : new ReflectionFunction($callback);
    }
    //
    protected static function addDependencyForCallParameter($container, $parameter,
                                                            array &$parameters, &$dependencies)
    {    
        //$parameter是一个ReflectionParameter类
        //ReflectionParameter::name返回参数名。
        //情况1:在$parameters中存在以该name为key的值,则使用该值,保存进$dependencies
        if (array_key_exists($parameter->name, $parameters)) {
            $dependencies[] = $parameters[$parameter->name];

            unset($parameters[$parameter->name]);
        //情况2:ReflectionParameter是一个类,且该以该类类名为key的值存在于parameters
        //则使用之,保存进$dependencies
        } elseif ($parameter->getClass() && array_key_exists($parameter->getClass()->name, $parameters)) {
            $dependencies[] = $parameters[$parameter->getClass()->name];

            unset($parameters[$parameter->getClass()->name]);
        //情况3:不在 $parameters中,但是是一个类,则从容器中make出来该实例
        } elseif ($parameter->getClass()) {
            
            $dependencies[] = $container->make($parameter->getClass()->name);

        //ReflectionParameter::isDefaultValueAvailable :检查默认值是否可用
        //情况4,当上述都不成立的时候,则检查是否设置有默认值。
        //如果有,则调用ReflectionParameter->getDefaultValue()获取并使用之。
        } elseif ($parameter->isDefaultValueAvailable()) {
            $dependencies[] = $parameter->getDefaultValue();
        }
    }

至此,bootProvider调用结束。在registerBaseServiceProviders中,还有一步操作:$provider->register(),接下来看下传递给容器自身register方法的三个服务提供者中,register方法都做了哪些动作。

1、EventServiceProvider:

  public function register()
    {
        //向容器中注册events的具体实现,具体实现是一个闭包。只有在make的时候才会调用闭包中
        //定义的返回
        $this->app->singleton('events', function ($app) {
            return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
                return $app->make(QueueFactoryContract::class);
            });
        });
    }

2、LogServiceProvider:

   public function register()
    {    //向容器中添加log的实现
        $this->app->singleton('log', function () {
            return new LogManager($this->app);
        });
    }

3、RoutingServiceProvider

    public function register()
    {   
         //添加router的具体实现
        $this->registerRouter();
        
        //添加url的具体实现
        $this->registerUrlGenerator();

        //添加redirect的具体实现
        $this->registerRedirector();

        //添加ServerRequestInterface::class的具体实现
        $this->registerPsrRequest();

        //添加ResponseInterface::class的具体实现
        $this->registerPsrResponse();

        //添加ResponseFactoryContract::class的具体实现
        $this->registerResponseFactory();

        //添加ControllerDispatcherContract::class的具体实现
        $this->registerControllerDispatcher();
    }

这些注册进容器的实现在后面具体使用的时候逐个进行分析

接下来看容器的构造方法中最后一个注册调用registerCoreContainerAliases

    public function registerCoreContainerAliases()
    {
        foreach ([
            'app'                  => [self::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\Hashing\HashManager::class],
            'hash.driver'          => [\Illuminate\Contracts\Hashing\Hasher::class],
            'translator'           => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class],
            'log'                  => [\Illuminate\Log\LogManager::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) {
                //保存进容器的aliases数组
                //这里注意,在容器的aliases中,参数2才是key,参数1是value
                //也就是多个接口对应的可能是一个字符别名(短名)
                //这使得当调用make的时候,会按照接口类提示符首先找到别名
                //因为getAlias是一个递归调用,所以会继续搜寻该短名
                //所对应的具体实现。
                //到应用运行到当前阶段时,只有部分短名的实现是已经
                //注册进容器中了的。
                $this->alias($key, $alias);
            }
        }
    }
    public function alias($abstract, $alias)
    {
        //排除自身,避免死循环
        if ($alias === $abstract) {
            throw new LogicException("[{$abstract}] is aliased to itself.");
        }
        //aliases则保存着某个类型提示所对应的一个别名(短名)。
        $this->aliases[$alias] = $abstract;
        //abstractAliases保存着某个短名所对应的所以类型提示(或者是一个接口类申明)
        $this->abstractAliases[$abstract][] = $alias;
    }

应用容器到此数便已初始化完毕。此时在返回index.php继续走下一步流程。

代码段:

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

返回的app(应用容器)调用make获取Illuminate\Contracts\Http\Kernel::class已经注册的具体实例,获取处理http请求的kernel(内核)。

可以在app.php中找到,该接口注册的实现类是App\Console\Kernel::class:

$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

按照make的执行逻辑:

    public function make($abstract, array $parameters = [])
    {
        $abstract = $this->getAlias($abstract);
        //如果设置了延迟加载的服务提供者,需要加载进来。
        //延迟加载可以理解为懒加载,需要的时候才去加载该资源
        if ($this->isDeferredService($abstract) && ! isset($this->instances[$abstract])) {
            $this->loadDeferredProvider($abstract);
        }
        
        return parent::make($abstract, $parameters);
    }

会调用父类的make(resolve)方法,此时符合

        //当前调用符合isBuildable
        //getConcrete调用会从bindings获取到该abstract的具体实现
        //concrete是一个闭包
        //在singleton调用中,因为具体的接口实现传参进去是一个类型提示符,在bind代码逻辑中
        //会调用getClosure将其包装成一个闭包类。然后保存bindings数组。
        if ($this->isBuildable($concrete, $abstract)) {
            //调用build生成object。
            $object = $this->build($concrete);
        } else {
            //调用make就递归的调用了resolve

            //否则就调用make(递归调用),可能会走到这个循环。
            //with属性就是一个参数堆栈。用于存放每次make的传递参数。
            $object = $this->make($concrete);
        }

在build中

       if ($concrete instanceof Closure) { 
            //getLastParameterOverride调用会从当前栈中获取压入的参数集。
            return $concrete($this, $this->getLastParameterOverride());
        }
        //该闭是由getClosure生成。
        protected function getClosure($abstract, $concrete)
        {
            return function ($container, $parameters = []) use ($abstract, $concrete) {
                if ($abstract == $concrete) {
                    return $container->build($concrete);
                }
                //当前调用符合该条件
                return $container->resolve(
                    $concrete, $parameters, $raiseEvents = false
                );
            };
        }
       

从上面的代码段可以得知,会继续调用容器的resolve方法,此时其参数concrete是一个类型提示,所以会直接调用:

$reflector = new ReflectionClass($concrete);

获取该类的相关信息,开始进行分析处理。从之前的分析得知,剩下的步骤会获取构造函数,解析依赖,然后构造构造函数的参数,最后将获取的参数传递给newInstanceArgs将httpkernel实例化出来。

先看一下App\Http\Kernel:

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{

    
    protected $middleware = [
        \App\Http\Middleware\CheckForMaintenanceMode::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
        \App\Http\Middleware\TrustProxies::class,
    ];


    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            'throttle:60,1',//throttle节流阀 配置
            'bindings', //捆绑,束缚。
        ],
    ];


    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
    ];


    protected $middlewarePriority = [
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\Authenticate::class,
        \Illuminate\Session\Middleware\AuthenticateSession::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
        \Illuminate\Auth\Middleware\Authorize::class,
    ];
}

除了定义了中间件、路由组件、路由中间件、以及中间件的执行顺序,四个属性之外,其余的属性和方法均继承自父类。

父类的构造函数如下:

    public function __construct(Application $app, Router $router)
    {
        
        $this->app = $app;
        $this->router = $router;

        //中间件的优先级属性赋值
        $router->middlewarePriority = $this->middlewarePriority;

        //路由组件添加
        foreach ($this->middlewareGroups as $key => $middleware) {
            $router->middlewareGroup($key, $middleware);
        }
        //路由中间件添加。
        foreach ($this->routeMiddleware as $key => $middleware) {
            $router->aliasMiddleware($key, $middleware);
        }
    }

构造函数有两个依赖;Application $app, Router $router

$app的类型提示符是Application,其全名为:Illuminate\Contracts\Foundation\Application,

在容器构造函数中的registerCoreContainerAliases调用,得知Illuminate\Contracts\Foundation\Application和app对应,

而app在registerBaseBindings中又被设置为容器自身,且以单例模式管理。所以此处依赖类最终会从容器中获取

容器自身实例,并传递给该构造函数。

$router的类型提示符为Illuminate\Routing\Router

在容器构造函数中的registerCoreContainerAliases调用,得知Illuminate\Routing\Router和router对应

在app在registerBaseServiceProviders调用了RoutingServiceProvider的register方法:

    public function register()
    {
        $this->registerRouter();
        $this->registerUrlGenerator();
        $this->registerRedirector();
        $this->registerPsrRequest();
        $this->registerPsrResponse();
        $this->registerResponseFactory();
        $this->registerControllerDispatcher();
    }

    protected function registerRouter()
    {
        $this->app->singleton('router', function ($app) {
            return new Router($app['events'], $app);
        });
    }

所以router最终注册的是Router类,其全类型提示路径为Illuminate\Routing\Router。

在闭包调用中容器自身实例会以参数传递进去,实例化Router有两个参数:$app['events']和 $app

在解析依赖类Router时使用如下代码段:

   if ($concrete instanceof Closure) {
            return $concrete($this, $this->getLastParameterOverride());
        }

将当前容器($this)作为参数传递给

        function ($app) {
            return new Router($app['events'], $app);
        }

可以注意到,该闭包只接受一个参数,在调用闭包的时候传递的是两个参数,参数二则自动抛弃,不被闭包接收。

此处还有个对象被当作数组的用法:

从php手册得知,只要某一个类实现了AccessArray接口,则可以使用对数组的操作去操作类。

class Container implements ArrayAccess

Application类的父类Container实现了ArrayAccess的下面四个接口

/**
     * Determine if a given offset exists.
     * 当出现isset($arr['key'])或者empty($arr['key'])会调用此方法
     * @param  string  $key
     * @return bool
     */
    public function offsetExists($key)
    {
        return $this->bound($key);
    }

    /**
     * Get the value at a given offset.
     * 获取$arr['key']时调用
     * @param  string  $key
     * @return mixed
     */

    public function offsetGet($key)
    {
        return $this->make($key);
    }

    /**
     * Set the value at a given offset.
     * 对$arr['key']赋值时调用
     * @param  string  $key
     * @param  mixed   $value
     * @return void
     */
    public function offsetSet($key, $value)
    {
        $this->bind($key, $value instanceof Closure ? $value : function () use ($value) {
            return $value;
        });
    }

    /**
     * Unset the value at a given offset.
     * unset($arr['key'])时调用
     * @param  string  $key
     * @return void
     */
    public function offsetUnset($key)
    {
        unset($this->bindings[$key], $this->instances[$key], $this->resolved[$key]);
    }

所以$app['events']调用会默认调用$this->make($key)方法,将event从容器中make出来。

在Application::__construct中registerBaseServiceProviders调用中event被注册为:

        $this->app->singleton('events', function ($app) {
            return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
                return $app->make(QueueFactoryContract::class);
            });
        });

所以这里的event最终是一个Illuminate\Events\Dispatcher类实例化出来的对象。在实例化之后调用setQueueResolver(队列解析器)将其成员queueResolver设置为一个闭包,该闭包实现了创建一个使用QueueFactoryContract::class所注册的实例。因为interface接口和抽象类是不能被实例化的只能实例化实现了该接口的类或者继承类该抽象类的子类。在初始框架中并未找到该接口所注册的实现。所以在使用Dispatcher的queueResolver之前是不会报错,否则框架应该会抛出一个异常。

致此,httpkernel创建成功。开始分析解析流程。

//处理数据 获取相应
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

创建一个request,并将其作为参数传递给kernel的handle,处理请求,生成response。

首先看参数的生成:$request = Illuminate\Http\Request::capture()(静态方法是类维度的,在php代码编译后即可调用,不用实例化该类生成对象)

   //捕捉请求参数
     public static function capture()
    {

        //设置静态属性$httpMethodParameterOverride为true
        static::enableHttpMethodParameterOverride();
        //使用SymfonyRequest::createFromGlobals()创建请求参数
        //调用自身静态方法static::createFromBase。
        return static::createFromBase(SymfonyRequest::createFromGlobals());
    }
    //使用php的全局变量创建一个请求参数
    public static function createFromGlobals()
    {
        $request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER);

        if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded')
            && \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), ['PUT', 'DELETE', 'PATCH'])
        ) {
            //parse_str — 将url字符串解析成多个变量
            parse_str($request->getContent(), $data);
            $request->request = new ParameterBag($data);
        }

        return $request;
    }
    
    private static function createRequestFromFactory(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null)
    {
     
        if (self::$requestFactory) {
            $request = (self::$requestFactory)($query, $request, $attributes, $cookies, $files, $server, $content);

            if (!$request instanceof self) {
                throw new \LogicException('The Request factory must return an instance of Symfony\Component\HttpFoundation\Request.');
            }

            return $request;
        }

        return new static($query, $request, $attributes, $cookies, $files, $server, $content);
    }

 

 类似资料: