Laravel请求源码简单流程

175 阅读3分钟

本文Laravel版本是8

如果请求的是一个http请求,那么所有的请求都先经过入口文件public/index.php

<?php

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

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


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



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


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

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

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

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

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

1. 引入 Composer 自动加载器

当执行 require __DIR__.'/../vendor/autoload.php'; 时,PHP 会包含并执行 vendor/autoload.php 文件。这个文件是由 Composer 生成的,位于项目根目录下的 vendor 目录中。

2. 初始化 Composer 自动加载

vendor/autoload.php 文件主要做了以下几件事:

2.1 引入 Composer 生成的类加载器

vendor/autoload.php 文件会引入由 Composer 生成的 vendor/composer/autoload_real.php 文件,这个文件包含了实际的自动加载逻辑:

require_once __DIR__ . '/composer/autoload_real.php';

2.2 加载 PSR-4 和 Classmap 配置

autoload_real.php 文件中的 ComposerAutoloaderInit 类负责初始化自动加载器,并根据 composer.json 文件中的 autoload 部分配置 PSR-4 和 Classmap 规则。

2.3 返回自动加载器实例

ComposerAutoloaderInit::getLoader() 方法返回一个自动加载器实例。这个实例会被存储在 $loader 变量中,以便后续使用:

$loader = ComposerAutoloaderInit::getLoader();

3. 注册自动加载器

返回的 $loader 实例会被配置为 PHP 的自动加载器之一,这意味着当尝试使用未定义的类时,PHP 将调用这个自动加载器来查找和加载相应的类文件。

4. 自动加载文件

根据 composer.json 文件中的 files 部分,Composer 会自动加载一些指定的文件。这些文件通常包含一些全局函数或工具类。

5. 自动加载命名空间

根据 composer.json 文件中的 psr-4 部分,Composer 会设置多个命名空间前缀和对应的目录路径。这样,当需要加载某个命名空间下的类时,Composer 可以正确地找到并加载相应的类文件。

总结

require __DIR__.'/../vendor/autoload.php'; 这一行代码其实执行了以下几个关键步骤:

  1. 引入 Composer 自动加载器:引入并执行 vendor/autoload.php 文件。
  2. 初始化自动加载器:通过 autoload_real.php 文件初始化 Composer 的自动加载器实例。
  3. 注册自动加载器:将 Composer 的自动加载器注册为 PHP 的自动加载器之一。
  4. 加载预定义文件:根据 composer.json 文件中的配置信息,自动加载一些指定的文件和类。

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

$app = require_once __DIR__.'/../bootstrap/app.php';会创建一个应用实例,

<?php

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



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

这句点进去看,可以看到是注册信息比如服务提供者(config/app.php中的providers数组里面的服务提供者不在这里注册的  具体看下面)等

public function __construct($basePath = null)
    {
        if ($basePath) {
            $this->setBasePath($basePath);
        }

        $this->registerBaseBindings();
        $this->registerBaseServiceProviders();
        $this->registerCoreContainerAliases();
    }

然后是

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

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

是注册HTTP和Console的内核类,http内核类包含了应用程序的全局中间件和路由中间件定义

具体可以看App\Http\Kernel::class的代码

然后会进行路由分发,根据Route的配置【RouteServiceProvider提供者已经在之前加载了】会将请求映射到相应的控制器方法或闭包函数。

我们回到index.php里面,还有几段代码:

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

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

$kernel = $app->make(Kernel::class);是实例化对象,但是Kernel::class是个接口【Illuminate\Contracts\Http下的Kernel】,所以这边是实例化他的实现类【App\Http\Kernel】类,为什么是这个类呢,前面的bootstrap\app.php里面绑定了:

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

也就是说$kernel是App\Http\Kernel类的实例,然后调用handle方法,其实App\Http\Kernel类没有handle方法,但是它继承了Illuminate\Foundation\Http\Kernel类,这个类有handle方法:

public function handle($request)
    {
        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;
    }

点击$response = $this->sendRequestThroughRouter($request);进去看:

 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();代码行,点进去看:

public function bootstrap()
    {
        if (! $this->app->hasBeenBootstrapped()) {
            $this->app->bootstrapWith($this->bootstrappers());
        }
    }

$this->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,
    ];

$this->app->bootstrapWith这个$this->app其实是Illuminate\Foundation\Application类,bootstrapWith方法如下:

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

通过make方法把bootstrappers数组里的类一个个实例化,并且调用bootstrap()方法注意看bootstrappers数组的\Illuminate\Foundation\Bootstrap\RegisterProviders::class

点进去:

<?php

namespace Illuminate\Foundation\Bootstrap;

use Illuminate\Contracts\Foundation\Application;

class RegisterProviders
{
    /**
     * Bootstrap the given application.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @return void
     */
    public function bootstrap(Application $app)
    {
        $app->registerConfiguredProviders();
    }
}

bootstrap()方法调用registerConfiguredProviders(),这里的$app也是Illuminate\Foundation\Application 类,所以调用的是Illuminate\Foundation\ApplicationregisterConfiguredProviders()方法:

public function registerConfiguredProviders()
    {
        $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());
    }

看到了没$this->make('config')->get('app.providers'),这就是config/app.php里面providers数组的服务提供者注册代码。

如果使用的是php artisan命令的话:

php artisan 的原理和流程

  1. 入口文件: 当你在命令行中运行 php artisan 时,PHP 会执行 Laravel 项目根目录下的 artisan 文件。

  2. 引导框架: 这个 artisan 文件实际上是一个 PHP 脚本,它首先加载自动加载文件 vendor/autoload.php,然后引导 Laravel 框架的启动过程。

  3. 创建应用实例: 在 artisan 文件中,会创建一个 Laravel 应用实例:

    $app = require_once __DIR__.'/bootstrap/app.php';
    
  4. 绑定服务容器: 在 bootstrap/app.php 文件中,会初始化 Laravel 服务容器(Service Container),并注册核心的服务提供者。

  5. 创建 Artisan Console 实例: 然后,artisan 脚本会创建一个 Artisan Console 实例,并将应用实例传递给它:

    $kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
    
  6. 处理命令: 接下来,artisan 脚本会调用 $kernel->handle() 方法,处理传入的命令行参数并执行相应的命令:

    $status = $kernel->handle(
        $input = new Symfony\Component\Console\Input\ArgvInput, //获取php artisan参数
        new Symfony\Component\Console\Output\ConsoleOutput //返回不同参数的不同对象命令
    );
    
  7. 命令注册: Laravel 的 Console Kernel 会在 app/Console/Kernel.php 文件中注册所有可用的 Artisan 命令。这些命令可以是在 commands 属性中显式声明的,也可以是在 routes/console.php 文件中定义的命令。

    protected $commands = [
        // 注册自定义命令
        Commands\YourCustomCommand::class,
    ];
    
  8. 命令发现: Laravel 还支持通过自动发现命令,利用命名空间扫描来发现命令。

  9. 执行命令: 当用户输入一个具体的命令时,例如 php artisan migrate,Artisan Console 会找到相应的命令类并执行其 handle 方法,从而完成特定的任务。