Skip to content

处理请求

index.php文件中,处理请求的实现

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

调用Kernel类的handle方法

handle()方法

php
public function handle($request)
{
    try {
        // 开启_method参数
        $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;
}

查看enableHttpMethodParameterOverride()方法

enableHttpMethodParameterOverride()\Illuminate\Http\Request类的父类\Symfony\Component\HttpFoundation\Request的方法:

php
public static function enableHttpMethodParameterOverride()
{
    self::$httpMethodParameterOverride = true;
}

当设置$httpMethodParameterOverride参数值为true时,使用表单进行POST提交http请求时,可以通过设置表单_method参数值来向服务器发送PUT或者DELETE请求。即Laravel可以将POST请求转换为PUT或者DELETE请求,前提是必须设置$httpMethodParameterOverride参数值为true

sendRequestThroughRouter()方法

php
protected function sendRequestThroughRouter($request)
{
    // 调用instance方法,绑定键值对
    $this->app->instance('request', $request);
    // 清除绑定在容器成员变量$resolvedInstance中的request键值对
    Facade::clearResolvedInstance('request');
    // 运行引导程序
    $this->bootstrap();
    // 借助app容器使用管道类处理HTTP请求,并将结果返回。
    return (new Pipeline($this->app))
                ->send($request)
                ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                ->then($this->dispatchToRouter());
}

bootstrap()方法

查看bootstrap方法

php
public function bootstrap()
{
    // 判断容器是否已经初始化,首次进入为未初始化
    if (! $this->app->hasBeenBootstrapped()) {
        /**
         * bootstrappers()方法,返回的是$bootstrappers数组
         * 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,
         *  1. 环境检测 DetectEnvironment
         *  2. 配置加载 LoadConfiguration
         *  3. 日志配置 ConfigureLogging
         *  4. 异常处理 HandleException
         *  5. 注册Facades RegisterFacades
         *  6. 注册Providers RegisterProviders
         *  7. 启动Providers BootProviders
         * ];
         */
        $this->app->bootstrapWith($this->bootstrappers());
    }
}
bootstrapWith()方法

查看bootstrapWith方法

php
public function bootstrapWith(array $bootstrappers)
{
    // 设置成员变量$hasBeenBootstrapped为true,表示已经初始化
    $this->hasBeenBootstrapped = true;
    // 循环每个value
    foreach ($bootstrappers as $bootstrapper) {
        /**
         * events是在注册服务提供者是注册的
         * $this->register(new EventServiceProvider($this));
         * $this['events']写法调用是因为Container类,实现了ArrayAccess接口方法offsetGet()
         * public function offsetGet($key)
         * {
         *     return $this->make($key);
         * }
         * 解析出来的是Illuminate\Events\Dispatcher类
         * dispatch方法是框架预留的事件分发方法,可以将事件分发到多个监听器中,并返回最后一个监听器的返回值
         */ 
        $this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]);

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

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

传入的$eventbootstrapping:Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables字符串, $payload为数组[0 => Application类实例]

php

public function dispatch($event, $payload = [], $halt = false)
{
    // 
    [$event, $payload] = $this->parseEventAndPayload(
        $event, $payload
    );

    if ($this->shouldBroadcast($payload)) {
        $this->broadcastEvent($payload[0]);
    }

    $responses = [];

    foreach ($this->getListeners($event) as $listener) {
        $response = $listener($event, $payload);

        // If a response is returned from the listener and event halting is enabled
        // we will just return this response, and not call the rest of the event
        // listeners. Otherwise we will add the response on the response list.
        if ($halt && ! is_null($response)) {
            return $response;
        }

        // If a boolean false is returned from a listener, we will stop propagating
        // the event to any further listeners down in the chain, else we keep on
        // looping through the listeners and firing every one in our sequence.
        if ($response === false) {
            break;
        }

        $responses[] = $response;
    }

    return $halt ? null : $responses;
}
parseEventAndPayload()方法

$event$payload值同上

php
protected function parseEventAndPayload($event, $payload)
{
    // 判断$event是否为对象
    if (is_object($event)) {
        [$payload, $event] = [[$event], get_class($event)];
    }

    return [$event, Arr::wrap($payload)];
}

启动阶段的服务加载

php
//   \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,
// 对应代码,解析出对应类实例,并调用bootstrap方法
$this->make($bootstrapper)->bootstrap($this);
LoadEnvironmentVariables::class类

LoadEnvironmentVariables类的bootstrap方法,可以理解为加载配置文件并解析为全局常量。

php
public function bootstrap(Application $app)
{
    // 如果缓存了配置文件,直接返回
    if ($app->configurationIsCached()) {
        return;
    }

    $this->checkForSpecificEnvironmentFile($app);

    try {
        // 使用Dotenv类加载配置文件
        $this->createDotenv($app)->safeLoad();
    } catch (InvalidFileException $e) {
        $this->writeErrorAndDie($e);
    }
}
LoadConfiguration::class类

该类是加载config目录下的所有配置文件

php
 public function bootstrap(Application $app)
{
    $items = [];

    // 同理,如果缓存了配置文件,则require该文件
    if (file_exists($cached = $app->getCachedConfigPath())) {
        $items = require $cached;

        $loadedFromCache = true;
    }

    // 绑定一个配置文件的实例,初始化时为空
    $app->instance('config', $config = new Repository($items));
    // 如果未从缓存中加载,则从config目录加载配置文件
    if (! isset($loadedFromCache)) {
        $this->loadConfigurationFiles($app, $config);
    }

    // Finally, we will set the application's environment based on the configuration
    // values that were loaded. We will pass a callback which will be used to get
    // the environment in a web context where an "--env" switch is not present.
    $app->detectEnvironment(function () use ($config) {
        return $config->get('app.env', 'production');
    });

    date_default_timezone_set($config->get('app.timezone', 'UTC'));

    mb_internal_encoding('UTF-8');
}
HandleExceptions::class类

该类处理异常

php
public function bootstrap(Application $app)
{
    self::$reservedMemory = str_repeat('x', 32768);

    $this->app = $app;

    error_reporting(-1);

    set_error_handler([$this, 'handleError']);

    set_exception_handler([$this, 'handleException']);

    register_shutdown_function([$this, 'handleShutdown']);

    if (! $app->environment('testing')) {
        ini_set('display_errors', 'Off');
    }
}
RegisterFacades::class类

这部分代码是实现laravel的Facades功能

php
public function bootstrap(Application $app)
{
    // 清除绑定
    Facade::clearResolvedInstances();

    Facade::setFacadeApplication($app);

    AliasLoader::getInstance(array_merge(
        // 从config实例中,获取aliases配置
        $app->make('config')->get('app.aliases', []),
        // 从PackageManifest实例中,获取aliases配置
        $app->make(PackageManifest::class)->aliases()
    ))->register();
}
RegisterProviders::class类

该类是注册服务提供者

php
public function bootstrap(Application $app)
{
    $app->registerConfiguredProviders();
}
// registerConfiguredProviders
public function registerConfiguredProviders()
{
    // 取出配置文件中的providers配置
    $providers = Collection::make($this->make('config')->get('app.providers'))
                    ->partition(function ($provider) {
                        return strpos($provider, 'Illuminate\\') === 0;
                    });

    $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);

    (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
                ->load($providers->collapse()->toArray());
}
BootProviders::class类

该类是启动服务提供者

php
 public function bootstrap(Application $app)
{
    $app->boot();
}

public function boot()
{
    // 如果已经启动过,则直接返回
    if ($this->isBooted()) {
        return;
    }

    // 启动前调用保存在bootingCallbacks中回调
    $this->fireAppCallbacks($this->bootingCallbacks);

    // 调用服务提供者的bootProvider方法
    array_walk($this->serviceProviders, function ($p) {
        $this->bootProvider($p);
    });
    // 标志已经启动
    $this->booted = true;
    // 启动后调用保存在bootedCallbacks中回调
    $this->fireAppCallbacks($this->bootedCallbacks);
}

至此,引导程序执行完成。

php
$this->bootstrap();

return

php
// new Pipeline($this->app)时将容器类绑定到$container变量
return (new Pipeline($this->app))
                // 将请求对象绑定到passable变量
                ->send($request)
                /**
                 * shouldSkipMiddleware()首先判断中间件是否跳过,跳过则返回空数组,否则返回中间件数组
                 * $this->middleware的值为类App\Http\Kernel中定义$middleware
                 * protected $middleware = [
                 *       // \App\Http\Middleware\TrustHosts::class,
                 *       \App\Http\Middleware\TrustProxies::class,
                 *       \Fruitcake\Cors\HandleCors::class,
                 *       \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
                 *       \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
                 *       \App\Http\Middleware\TrimStrings::class,
                 *       \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
                 *   ];
                 * 该方法将中间件数组给$pipes变量
                 */
                ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                ->then($this->dispatchToRouter());
dispatchToRouter()方法

dispatchToRouter()方法返回的时一个闭包,该闭包将请求对象$request绑定到容器上,然后调用Router类的dispatch()方法

php
protected function dispatchToRouter()
{
    return function ($request) {
        $this->app->instance('request', $request);

        return $this->router->dispatch($request);
    };
}
then()方法

这里是laravel路由中间件的洋葱模型,请求经过第一个、第二、第三..等中间件,到达控制器,执行控制器的方法。在到达控制器之前,如果有未授权、未登录、未验证的异常,则会抛出异常。

php
public function then(Closure $destination)
{
    // 先看array_reduce函数
    $pipeline = array_reduce(
        array_reverse($this->pipes()), $this->carry(), $this->prepareDestination($destination)
    );

    return $pipeline($this->passable);
}
array_reduce()函数

看一下官方解释, 第一个参数为数组,第二个参数是一个匿名函数,第三个参数是一个初始值。 举个例子,

php
$arr = [1,2,3,4,5];
$sum = array_reduce($arr, function($carry, $item){
    return $carry + $item;
}, 0);
echo $sum; // 15

这里,执行流程为:

  1. 首次执行时,第三个参数0会赋值给$carray$item第一次的值为数组的第一个元素1, 返回值为1,之后将返回的值赋值给$carry,进行下一次循环,循环次数为数组的长度。
  2. 第二次执行时,$carry的值为1$item的值为数组的第二个元素2,返回值为3
  3. 第三次执行时,$carry的值为3$item的值为数组的第三个元素3,返回值为6
  4. 第四次执行时,$carry的值为6$item的值为数组的第四个元素4,返回值为10
  5. 第五次执行时,$carry的值为10$item的值为数组的第五个元素5,返回值为15,并将返回值给$sum

看源码实现,

参数分析
php
$this->pipes() // 获取中间件数组
# ------------------------------------------
$this->carry() // 要执行的回调函数
protected function carry()
{
    return function ($stack, $pipe) {
        // $passable是$request请求对象
        return function ($passable) use ($stack, $pipe) {
            try {
                if (is_callable($pipe)) {
                    // 如果是匿名函数,则直接执行该函数
                    return $pipe($passable, $stack);
                } elseif (! is_object($pipe)) {
                    // 将中间件和参数分开
                    [$name, $parameters] = $this->parsePipeString($pipe);

                    // 解析中间件名称,并将其实例化
                    $pipe = $this->getContainer()->make($name);
                    // 将请求对象、迭代的中间件、参数合并
                    $parameters = array_merge([$passable, $stack], $parameters);
                } else {
                    // 如果传入的$pipe是实例对象,则将请求对象、迭代的中间件合并
                    $parameters = [$passable, $stack];
                }

                // 执行中间件的handle方法
                $carry = method_exists($pipe, $this->method)
                                ? $pipe->{$this->method}(...$parameters)
                                : $pipe(...$parameters);

                return $this->handleCarry($carry);
            } catch (Throwable $e) {
                // 如果有异常,捕获并抛出
                return $this->handleException($passable, $e);
            }
        };
    };
}
# ------------------------------------------
$this->prepareDestination($destination) // 初始值
# ------------------------------------------

要理解carry的执行流程,再举一个例子,由于在内部会解析出该中间件执行中间件的方法,所以,我们用匿名函数放在数组中进行迭代 参考

php
$f1 = function (\Closure $callback) {
    echo 'f1 start'.PHP_EOL;
    $callback();
    echo 'f1 end'.PHP_EOL;
};

$f2 = function (\Closure $callback) {
    echo 'f2 start'.PHP_EOL;
    $callback();
    echo 'f2 end'.PHP_EOL;
};

$f3 = function (\Closure $callback) {
    echo 'f3 start'.PHP_EOL;
    $callback();
    echo 'f3 end'.PHP_EOL;
};

$f4 = function () {
    echo 'f4 执行结束'.PHP_EOL;
};

$action = array_reduce(array_reverse([$f1, $f2, $f3]), function ($carry, $item) {
    return function () use ($carry, $item) {
        return $item($carry);
    };
}, $f4);
// 调用
$action();

执行流程,

  1. 第一次迭代,$carry$f4$item$f3,返回的$carry为闭包
php
function () use ($carry, $item) {
        return $f3($f4);
};
  1. 第二次迭代,$item$f2,返回的$carry为闭包
php
function () use ($carry, $item) {
        return $f2($f3($f4));
};
  1. 第三次迭代,$item$f1,返回的$carry为闭包
php
function () use ($carry, $item) {
        return $f1($f2($f3($f4)));
};

调用$action()闭包,相当于执行$f1($f2($f3($f4)))方法,参数为$f2($f3($f4))

  1. 先输出f1 start,然后调用传入的闭包$f2($f3($f4)),参数为$f3($f4)
  2. 再执行$f2($f3($f4))方法,输出f2 start,调用传入的闭包$f3($f4),参数为$f4
  3. 再执行$f3($f4)方法,输出f3 start,调用传入的闭包$f4,参数为空
  4. 再执行$f4方法,输出f4 执行结束
  5. 第三步的回调函数执行结束,输出f3 end,
  6. 第二步步的回调函数执行结束,输出f2 end,
  7. 第一步的回调函数执行结束,输出f1 end

提示

由此不难理解,中间件的执行方式,第一个中间件执行完,传递给下一个中间件,依次类推。最终最后一个中间件调用$this->prepareDestination($destination)这个方法内的闭包

prepareDestination()查看该方法
php
protected function prepareDestination(Closure $destination)
{
  return function ($passable) use ($destination) {
      try {
          return $destination($passable);
      } catch (Throwable $e) {
          return $this->handleException($passable, $e);
      }
  };
}

查看传进来的destination闭包,闭包为$this->dispatchToRouter(),进入该方法,

php
protected function dispatchToRouter()
{
  return function ($request) {
      $this->app->instance('request', $request);

      return $this->router->dispatch($request);
  };
}

可以发现,最终执行了$this->router->dispatch($request),也就是执行了路由的分发。