likes
comments
collection
share

Laravel框架启动流程分析

作者站长头像
站长
· 阅读数 15

注:当笔者写本文时laravel的最新版本是9.0,因此本文基于laravel9.0版本编写Laravel框架启动流程分析

两个入口

分析启动,我们一般都从入口开始,laravel有两个入口文件,一个是基于php-fpm的public/index.php,一个是基于命令行的artisan文件

运行前准备

我们先讲/public/index.php,后面再补充命令行的

<?php

use Illuminate\Contracts\Http\Kernel;
use Illuminate\Http\Request;

define('LARAVEL_START', microtime(true));

/*
|--------------------------------------------------------------------------
| Check If The Application Is Under Maintenance
|--------------------------------------------------------------------------
|
| If the application is in maintenance / demo mode via the "down" command
| we will load this file so that any pre-rendered content can be shown
| instead of starting the framework, which could cause an exception.
|
*/

if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
    require $maintenance;
}

/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader for
| this application. We just need to utilize it! We'll simply require it
| into the script here so we don't need to manually load our classes.
|
*/

require __DIR__.'/../vendor/autoload.php';

/*
|--------------------------------------------------------------------------
| Run The Application
|--------------------------------------------------------------------------
|
| Once we have the application, we can handle the incoming request using
| the application's HTTP kernel. Then, we will send the response back
| to this client's browser, allowing them to enjoy our application.
|
*/

$app = require_once __DIR__.'/../bootstrap/app.php';

$kernel = $app->make(Kernel::class);

$response = $kernel->handle(
    $request = Request::capture()
)->send();

$kernel->terminate($request, $response);

当一个请求过来的时候,laravel先判断站点是否处于维护模式(maintenance),如果是,则直接显示维护内容。如果不是,则启动composer自加载,自动加载composer库,然后开始laravel机器开始转动了

$app = require_once __DIR__.'/../bootstrap/app.php';

这里实例化一个对象app,我们看一下app,我们看一下app,我们看一下app


$app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);

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

在这里,框架做了几件事。1、注册HTTP和Console内核,2、并将应用程序的实例返回到index.php。app.php在这里应该理解为一个工厂,这就是为什么它要返回app变量,尽管由于require,app变量,尽管由于require,app变量,尽管由于requireapp是在全局上下文中。现在有一个Illuminate\Foundation\Application类,这个就是Laravel应用实例。这个类包含了从环境,Laravel版本,容器,路径等所有信息。Application继承子容器Container。Laravel所有的内核精华都在这两个类里面。

看一下Application类

class Application extends Container implements ApplicationContract, CachesConfiguration, CachesRoutes, HttpKernelInterface
{
    use Macroable;
    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->instance('path', $this->path());
        $this->instance('path.base', $this->basePath());
        $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());

        $this->useLangPath(value(function () {
            if (is_dir($directory = $this->resourcePath('lang'))) {
                return $directory;
            }

            return $this->basePath('lang');
        }));
    }
      protected function registerBaseBindings()
    {
        static::setInstance($this);

        $this->instance('app', $this);

        $this->instance(Container::class, $this);
        $this->singleton(Mix::class);

        $this->singleton(PackageManifest::class, function () {
            return new PackageManifest(
                new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
            );
        });
    }
      protected function registerBaseServiceProviders()
    {
        $this->register(new EventServiceProvider($this));
        $this->register(new LogServiceProvider($this));
        $this->register(new RoutingServiceProvider($this));
    }
  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, \Psr\SimpleCache\CacheInterface::class],
            'cache.psr6' => [\Symfony\Component\Cache\Adapter\Psr16Adapter::class, \Symfony\Component\Cache\Adapter\AdapterInterface::class, \Psr\Cache\CacheItemPoolInterface::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],
            'db' => [\Illuminate\Database\DatabaseManager::class, \Illuminate\Database\ConnectionResolverInterface::class],
            'db.connection' => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
            'db.schema' => [\Illuminate\Database\Schema\Builder::class],
            'encrypter' => [\Illuminate\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\StringEncrypter::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],
            'mail.manager' => [\Illuminate\Mail\MailManager::class, \Illuminate\Contracts\Mail\Factory::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],
            'redis.connection' => [\Illuminate\Redis\Connections\Connection::class, \Illuminate\Contracts\Redis\Connection::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) {
                $this->alias($key, $alias);
            }
        }
    }
}

从构造方法开始,Application做了很多事情,总结如下1、通过use Macroable实现了门面方法调用的扩展2、设置项目根路径basePath,通过bindPathsInContainer绑定各种路径到容器里面<br/>3、通过static::setInstance(basePath,通过bindPathsInContainer绑定各种路径到容器里面<br />3、通过static::setInstance(basePath,通过bindPathsInContainer绑定各种路径到容器里面<br/>3、通过static::setInstance(this);this−>instance(′app′,this->instance('app', this>instance(app,this);this−>instance(Container::class,this->instance(Container::class, this>instance(Container::class,this);将自己绑定到laravel容器里面,并关联到‘app',Container::class,ContainerInterface.class等别名4、注册Mix实例用于前端mix函数5、实例化PackageManifest 类到容器中用于收集composer信息的相关操作6、注册了3个核心的服务提供者,

        $this->register(new EventServiceProvider($this));
        $this->register(new LogServiceProvider($this));
        $this->register(new RoutingServiceProvider($this));

通过EventServiceProvider和LogServiceProvider,将事件派发类Dispatcher和事件管理类LogManager实例化到容器中。RoutingServiceProvider则是提供了各种路由、url、psr、request、response等容器对象的绑定。7、通过registerCoreContainerAliases注册了众多框架运行必须的核心容器对象,并绑定到各自的别名上。,我们可以看到所有的服务,如认证管理器、邮件发送器、数据库,都已经加载。请注意,到这时我们还没有加载.env文件、配置,或者实例化数据库的连接。我们只是在容器中加载和存储类而已

回到app.php

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

这里又绑定了3个内核,App\Http\Kernel::class用户处理http请求,App\Console\Kernel::class用于处理控制台请求,分别对应了文章开头说的两个入口,App\Exceptions\Handler::class用于捕获异常。至此,application运行启动前的所有环境准备完成,接下去就是框架从容器中拿对应的东西做对应的事情。

运行启动

主要代码

$kernel = $app->make(Kernel::class);

$response = $kernel->handle(
    $request = Request::capture()
)->send();

如果是基于php-fpm启动,那么Kernel::class就是对应App\Http\Kernel::class,而这个类又继承于Illuminate\Foundation\Http\Kernel,我们看一下Illuminate\Foundation\Http\Kernel。

class Kernel implements KernelContract
{
  use InteractsWithTime;

  protected $app;

  protected $router;

  protected $bootstrappers = [
  \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
  \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
  \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
  \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
  \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
  \Illuminate\Foundation\Bootstrap\BootProviders::class,
  ];


  protected $middleware = [];


  protected $middlewareGroups = [];

  protected $routeMiddleware = [];


  protected $requestLifecycleDurationHandlers = [];

  protected $requestStartedAt;

  protected $middlewarePriority = [
  \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
  \Illuminate\Cookie\Middleware\EncryptCookies::class,
  \Illuminate\Session\Middleware\StartSession::class,
  \Illuminate\View\Middleware\ShareErrorsFromSession::class,
  \Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
  \Illuminate\Routing\Middleware\ThrottleRequests::class,
  \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
  \Illuminate\Contracts\Session\Middleware\AuthenticatesSessions::class,
  \Illuminate\Routing\Middleware\SubstituteBindings::class,
  \Illuminate\Auth\Middleware\Authorize::class,
  ];
  public function __construct(Application $app, Router $router)
  {
    $this->app = $app;
    $this->router = $router;

    $this->syncMiddlewareToRouter();
  }
    protected function syncMiddlewareToRouter()
    {
        $this->router->middlewarePriority = $this->middlewarePriority;

        foreach ($this->middlewareGroups as $key => $middleware) {
            $this->router->middlewareGroup($key, $middleware);
        }

        foreach ($this->routeMiddleware as $key => $middleware) {
            $this->router->aliasMiddleware($key, $middleware);
        }
    }  

可以看到,已经预定义了一组优先级中间件middlewarePriority,框架把优先级中间件注入到router,紧接着注入App\Http\Kernel::class中定义的分组中间件和路由中间件。∗∗我们在使用laravel开发应用时这主要定义middlewarePriority,框架把优先级中间件注入到router,紧接着注入App\Http\Kernel::class中定义的分组中间件和路由中间件。**我们在使用laravel开发应用时这主要定义middlewarePriority,框架把优先级中间件注入到router,紧接着注入App\Http\Kernel::class中定义的分组中间件和路由中间件。我们在使用laravel开发应用时这主要定义middlewareGroups和routeMiddleware∗∗,routeMiddleware**,routeMiddleware,middlewarePriority一般很少东。

处理请求开始

$response = $kernel->handle(
    $request = Request::capture()
)->send();

当执行到这里时,框架开始处理请求并响应请求我们看一下handle方法

    public function handle($request)
    {
        $this->requestStartedAt = Carbon::now();

        try {
            $request->enableHttpMethodParameterOverride();

            $response = $this->sendRequestThroughRouter($request);
        } catch (Throwable $e) {
            $this->reportException($e);

            $response = $this->renderException($request, $e);
        }

        $this->app['events']->dispatch(
            new RequestHandled($request, $response)
        );

        return $response;
    }

1、通过$request->enableHttpMethodParameterOverride();启动了对restful的_method方法的支持。2、紧接着,laravel将请求通过sendRequestThroughRouter来进行处理。

    protected function sendRequestThroughRouter($request)
    {
        $this->app->instance('request', $request);

        Facade::clearResolvedInstance('request');

        $this->bootstrap();

        return (new Pipeline($this->app))
                    ->send($request)
                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                    ->then($this->dispatchToRouter());
    }

$this->bootstrap();是重点,通过bootstrap,Laravel又做了很多事情

    protected $bootstrappers = [
        \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,// 加载env环境变量
        \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,// 加载配置文件
        \Illuminate\Foundation\Bootstrap\HandleExceptions::class,// 异常处理
        \Illuminate\Foundation\Bootstrap\RegisterFacades::class, // 注册 Facades
        \Illuminate\Foundation\Bootstrap\RegisterProviders::class,// 注册 Providers
        \Illuminate\Foundation\Bootstrap\BootProviders::class,// 启动 Providers
    ];
public function bootstrapWith(array $bootstrappers)
    {
        $this->hasBeenBootstrapped = true;

        foreach ($bootstrappers as $bootstrapper) {
            $this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]);

            $this->make($bootstrapper)->bootstrap($this);

            $this['events']->dispatch('bootstrapped: '.$bootstrapper, [$this]);
        }
    }

其中RegisterProviders和BootProviders工作量最大,我们自定义的服务提供者都在这个时候触发生效了。bootstrappers执行完毕之后。Laravel开始通过管道(pipeline)进行请求处理。管道是个难点,这里你可以理解为循环使用中间件来处理请求。当所有的中间件处理完之后,交由 dispatchToRouter 进行下一步处理。

管道原理

管道使用了一个很重要的php内置函数array_reducearray_reduce(array array,[callable](https://www.php.net/manual/zh/language.types.callable.php)array, [callable](https://www.php.net/manual/zh/language.types.callable.php) array,[callable](https://www.php.net/manual/zh/language.types.callable.php)callback, mixed $initial = null): mixedarray_reduce() 将回调函数 callback 迭代地作用到 array 数组中的每一个单元中,从而将数组简化为单一的值。我们先手写一段简单代码类理解

$middlewares = ['middle1','middle2','middle3'];
$callback = function($aftermiddleware,$item){
return $aftermiddleware.$item.'-->';
};
$endback = 'controller->method-->';
$res = array_reduce($middlewares,$callback,$endback);
var_dump($res);
//输出controller->method-->middle1-->middle2-->middle3-->

我们看到本该最后执行的$endback却最先执行了,我们在改进一下,换成如下

$middlewares = ['middle1', 'middle2', 'middle3'];

$endback = function () {
    echo 'controller->method';
};

$res = array_reduce($middlewares, function ($aftermiddleware, $item) {
        return function() use($aftermiddleware,$item){
            if($item instanceof \Closure){
                $theitem = $item();
            }
            else{
                $theitem = $item;
            }
            echo $theitem.'-->';
            $aftermiddleware($theitem);
        };
}, $endback);

$res();
//输出middle3-->middle2-->middle1-->controller->method

可以看到,通过闭包包装后,执行输出的顺便改变了,可是我们要的middle1到middle3的执行顺序也相反了,我们对middlewares做一下middlewares做一下middlewares做一下middlewares = array_reverse($middlewares);此时输出顺序就会变为middle1-->middle2-->middle3-->controller->method了,这就是管道原理

Laravel管道原理:使用array_reduce把所有要通过的中间件都通过 callback 方法并压缩为一个 Closure。最后在执行 Initial

我们回到laravel代码中Laravel中的主要管道算法如下

//Illuminate\Foundation\Http\Kernel.php
protected function sendRequestThroughRouter($request)
{
    return (new Pipeline($this->app))
        ->send($request)
        ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
        ->then($this->dispatchToRouter());
}
protected function dispatchToRouter()
{
    return function ($request) {
        $this->app->instance('request', $request);
        return $this->router->dispatch($request);
    };
}

   public function send($passable)
    {
        $this->passable = $passable;

        return $this;
    }


    public function through($pipes)
    {
        $this->pipes = is_array($pipes) ? $pipes : func_get_args();

        return $this;
    }


    public function pipe($pipes)
    {
        array_push($this->pipes, ...(is_array($pipes) ? $pipes : func_get_args()));

        return $this;
    }


    public function via($method)
    {
        $this->method = $method;

        return $this;
    }


    public function then(Closure $destination)
    {
        $pipeline = array_reduce(
            array_reverse($this->pipes()), $this->carry(), $this->prepareDestination($destination)
        );

        return $pipeline($this->passable);
    }


    public function thenReturn()
    {
        return $this->then(function ($passable) {
            return $passable;
        });
    }


    protected function prepareDestination(Closure $destination)
    {
        return function ($passable) use ($destination) {
            try {
                return $destination($passable);
            } catch (Throwable $e) {
                return $this->handleException($passable, $e);
            }
        };
    }


    protected function carry()
    {
        return function ($stack, $pipe) {
            return function ($passable) use ($stack, $pipe) {
                try {
                    if (is_callable($pipe)) {
                        // If the pipe is a callable, then we will call it directly, but otherwise we
                        // will resolve the pipes out of the dependency container and call it with
                        // the appropriate method and arguments, returning the results back out.
                        return $pipe($passable, $stack);
                    } elseif (! is_object($pipe)) {
                        [$name, $parameters] = $this->parsePipeString($pipe);

                        // If the pipe is a string we will parse the string and resolve the class out
                        // of the dependency injection container. We can then build a callable and
                        // execute the pipe function giving in the parameters that are required.
                        $pipe = $this->getContainer()->make($name);

                        $parameters = array_merge([$passable, $stack], $parameters);
                    } else {
                        // If the pipe is already an object we'll just make a callable and pass it to
                        // the pipe as-is. There is no need to do any extra parsing and formatting
                        // since the object we're given was already a fully instantiated object.
                        $parameters = [$passable, $stack];
                    }

                    $carry = method_exists($pipe, $this->method)
                                    ? $pipe->{$this->method}(...$parameters)
                                    : $pipe(...$parameters);

                    return $this->handleCarry($carry);
                } catch (Throwable $e) {
                    return $this->handleException($passable, $e);
                }
            };
        };
    }

可以看到,laravel就是发送一个 $request 对象,通过 middleware 中间件数组,最后在执行 dispatchToRouter 方法的。假设有两个全局中间件,我们来看看这两个中间件是如何通过管道压缩为一个 Closure 的

Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
App\Http\Middleware\AllowOrigin::class,//自定义中间件

在 Illuminate\Pipeline\Pipeline 的 then 方法中,destination为代码中的的∗∗dispatchToRouter∗∗闭包,即初始值,pipes为要通过的中间件数组,passable为Request对象。arrayreverse函数将中间件数组的每一项都通过destination 为代码中的的 **dispatchToRouter** 闭包,即初始值,pipes 为要通过的中间件数组,passable 为 Request 对象。array_reverse 函数将中间件数组的每一项都通过 destination为代码中的的dispatchToRouter闭包,即初始值,pipes为要通过的中间件数组,passableRequest对象。arrayreverse函数将中间件数组的每一项都通过this->carry()。

  • 第一次迭代时,返回一个闭包,use 了 stack和stack 和 stackpipe,stack的值为初始值闭包,stack 的值为初始值闭包,stack的值为初始值闭包,pipe 为中间件类名,此处是 App\Http\Middleware\AllowOrigin::class(注意 array_reverse 函数把传进来的中间件数组倒叙了)。假设我们直接运行该闭包,由于此时 pipe是一个String类型的中间件类名,只满足!isobject(pipe 是一个 String 类型的中间件类名,只满足 ! is_object(pipe是一个String类型的中间件类名,只满足!isobject(pipe) 这个条件,我们将直接从容器中 make 一个该中间件的实列出来,在执行该中间件实列的 handle 方法(**默认 this−>method为handle∗∗)。并且将request对象和初始值作为参数,传给这个中间件。在这个中间件的handle方法中,当我们直接执行returnthis->method 为 handle**)。并且将 request 对象和初始值作为参数,传给这个中间件。在这个中间件的 handle 方法中,当我们直接执行 return this>methodhandle)。并且将request对象和初始值作为参数,传给这个中间件。在这个中间件的handle方法中,当我们直接执行returnnext($request) 时,相当于我们开始执行 array_reduce 函数的初始值闭包了,即上述的 dispatchToRouter 方法返回的闭包。好,假设结束。

这里的handle方法调用的就行我们平时写laravel中间件时的handle方法。

  • 在第二次迭代时,也返回一个 use 了 stack和stack 和 stackpipe,stack的值为我们第一次迭代时返回的闭包,stack 的值为我们第一次迭代时返回的闭包,stack的值为我们第一次迭代时返回的闭包,pipe 为中间件类名,此处是 Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class。
  • 两次迭代结束,回到 then 方法中,我们手动执行了第二次迭代返回的闭包。当执行第二次迭代返回的闭包时,当前闭包 use 的 pipe为Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,同样只满足!isobject(pipe为 Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,同样只满足 ! is_object(pipeIlluminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,同样只满足!isobject(pipe) 这个条件,我们将会从容器中 make 出 CheckForMaintenanceMode 中间件的实列,在执行该实列的 handle 方法,并且把第一次迭代返回的闭包作为参数传到 handle 方法中。
  • 当我们在 CheckForMaintenanceMode 中间件的 handle 方法中执行 return next(next(next(request) 时,此时的 next为我们第一次迭代返回的闭包,将回到我们刚才假设的流程那样。从容器中make一个App\Http\Middleware\AllowOrigin实列,在执行该实列的handle方法,并把初始值闭包作为参数传到AllowOrigin中间件的handle方法中。当我们再在AllowOrigin中间件中执行returnnext 为我们第一次迭代返回的闭包,将回到我们刚才假设的流程那样。从容器中 make 一个 App\Http\Middleware\AllowOrigin 实列,在执行该实列的 handle 方法,并把初始值闭包作为参数传到 AllowOrigin 中间件的 handle方法中。当我们再在 AllowOrigin 中间件中执行 return next为我们第一次迭代返回的闭包,将回到我们刚才假设的流程那样。从容器中make一个App\Http\Middleware\AllowOrigin实列,在执行该实列的handle方法,并把初始值闭包作为参数传到AllowOrigin中间件的handle方法中。当我们再在AllowOrigin中间件中执行returnnext($request) 时,代表我们所有中间件都通过完成了,接下来开始执行 dispatchToRouter。

总结

1、中间件是区分先后顺序的,从这里你应该能明白为什么要把中间件用 array_reverse 倒叙了。 2、并不是所有中间件在运行前都已经实例化了的,用到的时候才去想容器取 3、中间件不执行 next(next (next(request) 后续所有中间件无法执行。

响应开始

1、路由匹配

Laravel执行完管道环节后,如果中间件没有退出的话,那么最终会执行dispatchToRouter,在这里laravel会寻找路由匹配到最终执行的方法

protected function dispatchToRouter()
{
    return function ($request) {
        $this->app->instance('request', $request);
        return $this->router->dispatch($request);
    };
}
/**
     * Dispatch the request to the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function dispatch(Request $request)
    {
        $this->currentRequest = $request;

        return $this->dispatchToRoute($request);
    }

    /**
     * Dispatch the request to a route and return the response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function dispatchToRoute(Request $request)
    {
        return $this->runRoute($request, $this->findRoute($request));
    }

    /**
     * Find the route matching a given request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Routing\Route
     */
    protected function findRoute($request)
    {
        $this->events->dispatch(new Routing($request));

        $this->current = $route = $this->routes->match($request);

        $route->setContainer($this->container);

        $this->container->instance(Route::class, $route);

        return $route;
    }


在我们执行路由动作之前,我们必须找到与我们的请求URI相匹配的路由。在我们的Request对象上有requestUri和方法属性,可以帮助我们做到这一点。在findRoute方法中,我们从RouteCollection对象中调用匹配方法,找到路由,将其映射到路由对象中,完成后,我们将该路由对象绑定到容器中。请注意,RouteCollection是我们在注册核心RoutingServiceProvide时在构造函数中实例化的类。匹配路由

    public function match(Request $request)
    {
        $routes = $this->get($request->getMethod());

        // First, we will see if we can find a matching route for this current request
        // method. If we can, great, we can just return it so that it can be called
        // by the consumer. Otherwise we will check for routes with another verb.
        $route = $this->matchAgainstRoutes($routes, $request);

        return $this->handleMatchedRoute($request, $route);
    }
    protected function matchAgainstRoutes(array $routes, $request, $includingMethod = true)
    {
        [$fallbacks, $routes] = collect($routes)->partition(function ($route) {
            return $route->isFallback;
        });

        return $routes->merge($fallbacks)->first(
            fn (Route $route) => $route->matches($request, $includingMethod)
        );
    }
    public function matches(Request $request, $includingMethod = true)
    {
        $this->compileRoute();

        foreach (self::getValidators() as $validator) {
            if (! $includingMethod && $validator instanceof MethodValidator) {
                continue;
            }

            if (! $validator->matches($this, $request)) {
                return false;
            }
        }

        return true;
    }

这里的匹配方法解有点复杂,先通过多种验证器:URIValidator, SchemeValidator, HostValidator & MethodValidator来匹配特定路由。这些验证器类试图为其特定目的验证路由。如果找到了任何匹配的路由,laravel就将请求与路由绑定,并将其返回。然而,如果在这个过程中没有匹配的路由,laravel将循环浏览路由集合,使用checkForAlternateVerbs($request)查看该URI是否存在不同的HTTP方法。如果不存在,则抛回一个404异常

2、执行路由

    protected function runRoute(Request $request, Route $route)
    {
        $request->setRouteResolver(fn () => $route);

        $this->events->dispatch(new RouteMatched($route, $request));

        return $this->prepareResponse($request,
            $this->runRouteWithinStack($route, $request)
        );
    }

注意,我们最初需要在Request类中设置路由解析器,所以当你在你的类中使用$request->route()方法时,该解析器会找到该路由并将其返回给你。之后,laravel发射了一个事件RouteMatched。然后通过runRouteWithinStack执行路由中间件

        $shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
                                $this->container->make('middleware.disable') === true;

        $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);

        return (new Pipeline($this->container))
                        ->send($request)
                        ->through($middleware)
                        ->then(fn ($request) => $this->prepareResponse(
                            $request, $route->run()
                        ));

这里又来的一个管道模型,如何拆解和前面讲的一样。最终laravel吧处理结果通过prepareResponse包装成Response对象返回给入口文件index.php。


    public function prepareResponse($request, $response)
    {
        return static::toResponse($request, $response);
    }


    public static function toResponse($request, $response)
    {
        if ($response instanceof Responsable) {
            $response = $response->toResponse($request);
        }

        if ($response instanceof PsrResponseInterface) {
            $response = (new HttpFoundationFactory)->createResponse($response);
        } elseif ($response instanceof Model && $response->wasRecentlyCreated) {
            $response = new JsonResponse($response, 201);
        } elseif ($response instanceof Stringable) {
            $response = new Response($response->__toString(), 200, ['Content-Type' => 'text/html']);
        } elseif (! $response instanceof SymfonyResponse &&
                   ($response instanceof Arrayable ||
                    $response instanceof Jsonable ||
                    $response instanceof ArrayObject ||
                    $response instanceof JsonSerializable ||
                    $response instanceof stdClass ||
                    is_array($response))) {
            $response = new JsonResponse($response);
        } elseif (! $response instanceof SymfonyResponse) {
            $response = new Response($response, 200, ['Content-Type' => 'text/html']);
        }

        if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) {
            $response->setNotModified();
        }

        return $response->prepare($request);
    }

接着。通过$response->send();将信息返回给浏览器。

    /**
     * Sends HTTP headers and content.
     *
     * @return $this
     */
    public function send(): static
    {
        $this->sendHeaders();
        $this->sendContent();

        if (\function_exists('fastcgi_finish_request')) {
            fastcgi_finish_request();
        } elseif (\function_exists('litespeed_finish_request')) {
            litespeed_finish_request();
        } elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
            static::closeOutputBuffers(0, true);
        }

        return $this;
    }

最后,通过kernel−>terminate(kernel->terminate(kernel>terminate(request, $response);laravel执行了中间件的terminate方法,至此,整个请求流程结束。