Laravel 一些核心思想

一、依赖注入

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

通俗一点来讲不是由自己内部 new 对象或者实例,通过构造函数,或者方法传入的都属于 依赖注入

这里我们使用开发中经常使用到的功能发邮件,在日常开发中,肯定会遇到有俩个服务商的短信吧。

下面使用代码来演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?php
// 定义服务类
interface MailInterface
{
public function send();
}

// 服务实现类A
class MailA implements MailInterface
{
public function send()
{
echo 'MailA 发送';
}
}

// 服务实现类B
class MailB implements MailInterface
{
public function send()
{
echo 'MailB 发送';
}
}

// 服务提供者
class UserMail
{
protected $mail;

public function __construct()
{
$this->mail = new MailB();
}

public function sendTo()
{
echo '发送短信了';
$this->mail->send();
}
}

$mail = new UserMail();
$mail->sendTo();

这里插一个重要概念:服务提供者

Laravel 服务提供者主要用来进行注册服务容器绑定(即注册接口及其实现类的绑定)。

再通俗一点,第一步是为了发邮件写了接口,接口的实现类写好了,那需要有地方来调用,服务提供者就是调用的类(这里的服务提供者是 UserMail),绑定和实例化应该是在这里完成。

这个时候,可以看到如果我们要更换服务商A的话,就需要去修改操作类。所以现在使用依赖注入来对操作类代码进行优化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

// 操作类
class UserMail
{
protected $mail;

public function __construct(MailInterface $mail)
{
$this->mail = $mail;
}

public function sendTo()
{
echo '发送短信了';
$this->mail->send();
}
}

$mail = new UserMail(new MailA());
$mail->sendTo();

这样一看代码,再加上上面的解释,是不是很清晰明了。

二、反射机制

反射是指在PHP运行状态中,扩展分析PHP程序,导出或提出关于类、方法、属性、参数等的详细信息,包括注释。这种动态获取信息以及动态调用对象方法的功能称为反射API。

PHP 5 及以上版本中有 ReflectionClass ,报告了一个类的有关信息。

示例:

1
2
3
4
5
6
7
8
9
10
<?php

// 获取 UserMail 的 reflectionClass 对象
$reflector = new ReflectionClass(UserMail::class);
// 拿到 UserMail 的构造函数
$constructor = $reflector->getConstructor();
// 拿到构造函数的所有依赖
$dependencies = $constructor->getParameters();
// 创建新的 UserMail 对象
$newUserMail = $reflector->newInstance();

现在可以通过创建一个 make 方法,利用反射机制拿到 UserMail 的构造函数,进而得到构造函数的依赖参数,通过递归方式创建参数依赖,最后调用 newInstanceArgs() 方法创建实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php

// 操作类
class UserMail
{
protected $mail;

public function __construct(MailA $mail)
{
$this->mail = $mail;
}

public function sendTo()
{
echo '发送短信了';
$this->mail->send();
}
}

function make($concrete)
{
$reflector = new ReflectionClass($concrete);
$constructor = $reflector->getConstructor();

if (is_null($constructor)) {
return $reflector->newInstance();
} else {
$dependencies = $constructor->getParameters();

$instances = getDependencies($dependencies);

return $reflector->newInstanceArgs($instances);
}
}

function getDependencies($params)
{
$dependencies = [];

foreach ($params as $param) {
$dependencies[] = make($param->getClass()->name);
}

return $dependencies;
}

$mail = make('UserMail');
$mail->sendTo();

注意,这里没有使用到 依赖注入 ,没有完全达到解偶。所以这样肯定是不合格的。这就引出了另外一个概念 Ioc容器

三、Ioc 容器

Ioc—Inversion of Control,即“控制反转”,就是具有依赖注入功能的容器,是可以创建对象的容器,IOC容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。

我们可以借助一个容器,提前把服务商对象、操作类都绑定到 Ioc 容器 中,如果后期需要修改服务商,也只需要修改 Ioc 容器 里的绑定记录就行了,就很好的解耦上面代码。具体实现思路

  1. Ioc 容器维护 binding 数组记录 bind 方法传入的键值对如:mail => MailA,userMail => UserMail
  2. 在 ioc -> make(‘UserMail’) 的时候,通过反射拿到 UserMail 的构造函数,拿到构造函数的参数,发现参数是 UserMail 的构造函数参数 mail, 然后根据 mail 得到 MailA。
  3. 然后通过反射机制创建 $mail = new MailA();
  4. 最后通过 newInstanceArgs() 再创建 new UserMail($mail)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
<?php

// 定义接口
interface MailInterface
{
public function send();
}

// 服务商A
class MailA implements MailInterface
{
public function send()
{
echo 'MailA 发送';
}
}

// 服务商B
class MailB implements MailInterface
{
public function send()
{
echo 'MailB 发送';
}
}

// 操作类
class UserMail
{
protected $mail;

public function __construct(MailInterface $mail)
{
$this->mail = $mail;
}

public function sendTo()
{
echo '发送短信了';

$this->mail->send();
}
}

class Ioc
{
public $binding = [];

public function bind($abstract, $concrete)
{
$this->binding[$abstract]['concrete'] = function ($ioc) use ($concrete) {
return $ioc->build($concrete);
};
}

public function make($abstract)
{
$concrete = $this->binding[$abstract]['concrete'];

return $concrete($this);
}

public function build($concrete)
{
$reflector = new ReflectionClass($concrete);
$constructor = $reflector->getConstructor();

if (is_null($constructor)) {
return $reflector->newInstance();
} else {
$dependencies = $constructor->getParameters();
$instances = $this->getDependencies($dependencies);

return $reflector->newInstanceArgs($instances);
}
}

protected function getDependencies($paramters)
{
$dependencies = [];
foreach ($paramters as $paramter) {
$dependencies[] = $this->make($paramter->getClass()->name);
}

return $dependencies;
}
}

$ioc = new Ioc();
// $ioc->bind('MailInterface', 'MailA'); ## 如果更换服务商只需要修改 bind 记录
$ioc->bind('MailInterface', 'MailB'); ## 如果更换服务商只需要修改 bind 记录
$ioc->bind('userMail', 'UserMail');
$mail = $ioc->make('userMail');
$mail->sendTo();

Ï

四、Laravel 生命周期

Laravel 的生命周期可以分成四个阶段

  • 注册自动加载程序(加载项目依赖)
  • 创建应用实例(服务容器),并绑定内核到容器
  • 处理请求,响应
  • 结束,进行回调

在入口文件 index.php 文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
// 第一步 注册自动加载程序(加载项目依赖)
require __DIR__.'/../vendor/autoload.php';

// 第二步 创建应用实例(服务容器),并绑定内核到容器
$app = require_once __DIR__.'/../bootstrap/app.php';

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

// 第三步 处理请求,响应
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);

$response->send();

// 第四步 结束,释放资源
$kernel->terminate($request, $response);

接下来,我们来走一遍这个流程

4.1 注册自动加载程序(加载项目依赖)

现在 PHP 依赖于 Composer 包管理器,所以在入口文件引入由 Composer 包管理器自动生成的类,可以轻松注册并加载第三方包

1
2
3
<?php
// 第一步 注册自动加载程序(加载项目依赖)
require __DIR__.'/../vendor/autoload.php';

4.2 创建应用实例(服务容器),并绑定内核到容器

在入口文件 index.php 中,

1
2
3
4
5
6
7
8
9
10
11
12
13
/*
|--------------------------------------------------------------------------
| Turn On The Lights
|--------------------------------------------------------------------------
|
| We need to illuminate PHP development, so let us turn on the lights.
| This bootstraps the framework and gets it ready for use, then it
| will load up this application so that we can run it and send
| the responses back to the browser and delight our users.
|
*/

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

现在让我们来看看 /bootstrap/app.php 这里面发生什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php

// 创建应用实例(服务容器)
$app = new Illuminate\Foundation\Application(
realpath(__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;

怎么创建实例呢?

Illuminate\Foundation\Application 文件中,主要

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
// 创建一个app实例
public function __construct($basePath = null)
{
// 设置应用程序的基本路径
if ($basePath) {
$this->setBasePath($basePath);
}

// 注册基本绑定
$this->registerBaseBindings();

// 注册所有基本服务提供商
$this->registerBaseServiceProviders();

// 在容器中注册核心类的别名
$this->registerCoreContainerAliases();
}

4.2.1 setBasePath()

设置应用程序的基本路径

在这个 setBasePath() 方法中,调用了 bindPathsInContainer()

1
2
3
4
5
6
7
8
9
10
11
12
protected function bindPathsInContainer()
{
$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 = [])中去。

4.2.2 registerBaseBindings()

注册基本绑定

1
2
3
4
5
6
7
8
9
10
11
12
protected function registerBaseBindings()
{
static::setInstance($this);

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

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

$this->instance(PackageManifest::class, new PackageManifest(
new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
));
}

这段代码,是将实例注入到容器中。

1
static::setInstance($this);

就是将 $this 复制给了父类中的静态变量 $instance (当前的全局可用容器)

重点看一下

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

这也是父类的一个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Illuminate\Container\Container.php

/**
* Register an existing instance as shared in the container.
* 将现有实例注册为在容器中共享的实例
*
* @param string $abstract
* @param mixed $instance
* @return mixed
*/
public function instance($abstract, $instance)
{
$this->removeAbstractAlias($abstract);

$isBound = $this->bound($abstract);

unset($this->aliases[$abstract]);

// We'll check to determine if this type has been bound before, and if it has
// 我们将检查以确定此类型之前是否已绑定
// we will fire the rebound callbacks registered with the container and it
// 我们将触发在容器中注册的回调
// can be updated with consuming classes that have gotten resolved here.
// 可以使用在这里已解决的消耗类进行更新
$this->instances[$abstract] = $instance;

if ($isBound) {
$this->rebound($abstract);
}

return $instance;
}

instance() 方法的作用就是绑定一个已有对象到容器中,这个对象在容器中共享并可以通过键获得。

4.2.3 registerBaseServiceProviders()

注册所有基本服务提供商

1
2
3
4
5
6
7
8
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));

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

$this->register(new RoutingServiceProvider($this));
}

这一步注册了三个 ServiceProvider ,在注册过程中,每个 ServiceProviderregister() 都会被执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* Register a service provider with the application.
*
* @param \Illuminate\Support\ServiceProvider|string $provider
* @param array $options
* @param bool $force
* @return \Illuminate\Support\ServiceProvider
*/
public function register($provider, $options = [], $force = false)
{
if (($registered = $this->getProvider($provider)) && ! $force) {
return $registered;
}

// If the given "provider" is a string, we will resolve it, passing in the
// application instance automatically for the developer. This is simply
// a more convenient way of specifying your service provider classes.
if (is_string($provider)) {
$provider = $this->resolveProvider($provider);
}

if (method_exists($provider, 'register')) {
$provider->register();
}

$this->markAsRegistered($provider);

// If the application has already booted, we will call this boot method on
// the provider class so it has an opportunity to do its boot logic and
// will be ready for any usage by this developer's application logic.
if ($this->booted) {
$this->bootProvider($provider);
}

return $provider;
}

这些 ServiceProviders 都是 Illuminate\Support\ServiceProvider 的子类,他接受一个 Application 对象作为析构函数参数,存储在实例变量 $app 中。

在上面代码的 register() 方法中, ServiceProviderregister() 都会被执行。

那我们来看看 Illuminate\Events\EventServiceProvider 这个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

namespace Illuminate\Events;

use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Queue\Factory as QueueFactoryContract;

class EventServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->singleton('events', function ($app) {
return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
return $app->make(QueueFactoryContract::class);
});
});
}
}

上面代码将 Illuminate\Events\Dispatcher 对象以键 events 绑定到容器中,来负责实现事件的调度。

再来看看 Illuminate\Log\LogServiceProvider

1
2
3
4
5
6
7
8
9
10
11
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->singleton('log', function () {
return $this->createLogger();
});
}

上面将创建 logger 对象以键 log 绑定到容器中,来负责实现记录日志行为。

最后看看 lluminate\Routing\RoutingServiceProvider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->registerRouter();

$this->registerUrlGenerator();

$this->registerRedirector();

$this->registerPsrRequest();

$this->registerPsrResponse();

$this->registerResponseFactory();

$this->registerControllerDispatcher();
}

这里以类似上面两个的方式注册了大量的路由方法到容器中。

4.2.4 registerCoreContainerAliases()

在容器中注册核心类的别名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* Register the core class aliases in the container.
*
* @return void
*/
public function registerCoreContainerAliases()
{
foreach ([
'app' => [\Illuminate\Foundation\Application::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\Contracts\Hashing\Hasher::class],
'translator' => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class],
'log' => [\Illuminate\Log\Writer::class, \Illuminate\Contracts\Logging\Log::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) {
$this->alias($key, $alias);
}
}
}

可以看到,这里注册大量的别名到容器中去,方便后续直接调用。

4.2.5 绑定内核

现在回到 bootstrap/app.php ,代码执行到这里,会看到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 绑定内核到容器
$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
);

singleton() 这个函数,前一个参数是实际类名,后面则是类的 “别名”。

$app 对象声明了3个单例对象。注意,这里只是声明了,也就是只起了个 ”别名“。

再来到 bootstrap/index.php ,可以看到

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

按照惯例,我们来看看这里的代码

注意哦,因为上面绑定声明了,所以不是去找 Illuminate\Contracts\Http\Kerne ,而应该是 App\Http\Kernel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array
*/
protected $middleware = [
\Illuminate\Foundation\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,
];

/**
* The application's route middleware groups.
*
* @var array
*/
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',
'bindings',
],
];

/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* @var array
*/
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'login' => \App\Http\Middleware\Login::class,
'check_token' => \App\Http\Middleware\CheckAccessToken::class,
];
}

一目了然,这个类定义了各种中间件数组。

4.3 处理请求,响应

请求实例化

在程序启动准备工作完成之后,就开始请求的实例化。

1
2
3
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);

对于请求就是客户端发送的一个请求报文,这个对应着 Illuminate\Http\Request 类。请求的实例创建通过 Illuminate\Http\Request 中的 capture() 方法完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* 创建HTTP请求实例
*/
public static function capture()
{
static::enableHttpMethodParameterOverride();

// 通过Symfony实例创建一个请求实例
// 而Symfony请求实例是通过createFromGlobals()静态函数实现的
return static::createFromBase(SymfonyRequest::createFromGlobals());
}

/**
* 从Symfony实例创建请求
*/
public static function createFromBase(SymfonyRequest $request)
{
if ($request instanceof static) {
return $request;
}

$content = $request->content;

$request = (new static)->duplicate(
$request->query->all(),
$request->request->all(),
$request->attributes->all(),
$request->cookies->all(),
$request->files->all(),
$request->server->all()
);

$request->content = $content;

$request->request = $request->getInputSource();

return $request;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
* 通过PHP全局变量创建一个新的请求实例
*/
public static function createFromGlobals()
{
// With the php's bug #66606, the php's built-in web server
// stores the Content-Type and Content-Length header values in
// HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH fields.
$server = $_SERVER;
if ('cli-server' === \PHP_SAPI) {
if (\array_key_exists('HTTP_CONTENT_LENGTH', $_SERVER)) {
$server['CONTENT_LENGTH'] = $_SERVER['HTTP_CONTENT_LENGTH'];
}
if (\array_key_exists('HTTP_CONTENT_TYPE', $_SERVER)) {
$server['CONTENT_TYPE'] = $_SERVER['HTTP_CONTENT_TYPE'];
}
}

$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($request->getContent(), $data);
$request->request = new ParameterBag($data);
}

return $request;
}

// 如果定义了请求工厂方法,则可以将自定义的工厂方法赋值给属性$requestFactory
// 否则通过new static来完成请求的实例。
// new static语法是后期静态绑定
private static function createRequestFromFactory(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null)
{
if (self::$requestFactory) {
$request = \call_user_func(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);
}

处理请求

实例化完成之后就是要对实例进行处理。

看回来 Illuminate\Foundation\Http\Kernel ,请求处理是通过 handle() 进行的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
* Handle an incoming HTTP request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function handle($request)
{
try {
//启用对_method request参数的支持,以确定所需的HTTP方法。
$request->enableHttpMethodParameterOverride();

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

$response = $this->renderException($request, $e);
} catch (Throwable $e) {
$this->reportException($e = new FatalThrowableError($e));

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

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

return $response;
}

/**
* 通过中间件/路由器发送给定的请求。
*/
protected function sendRequestThroughRouter($request)
{
// 将 $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());
}

这里先不讲 route 部分,之后再深入

最后通过 dispatchToRouter() 响应

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Get the route dispatcher callback.
*
* @return \Closure
*/
protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);

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

然后以 public/index.phpsend() 发送

1
$response->send();

4.4 结束,释放资源

1
$kernel->terminate($request, $response);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Call the terminate method on any terminable middleware.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Response $response
* @return void
*/
public function terminate($request, $response)
{
// 注册的中间件如果有terminate调用terminate方法
// session 存盘就是在中间件terminate中完成的, 所以很多人在controller
// 中使用了dd()函数, 就发现session出问题了. 因为dd()会使程序直接退出,
// 这时候请使用dump()来输出变量
$this->terminateMiddleware($request, $response);

// Application的terminate, 他会调用通过terminating方法注册的回调
$this->app->terminate();
}

参考:

深入 Laravel 核心

https://segmentfault.com/a/1190000014598466

https://www.jianshu.com/p/db2234e81afc

不对之处,请多指教

您的支持将鼓励我继续创作!