Code Monkey home page Code Monkey logo

laravel's Introduction

laravel's People

Contributors

xiaohuilam avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

laravel's Issues

11. 事件机制

根据 Laravel 的 Events 文档 (中文版:事件系统), EventServiceProvider 的使用方法为:

/**
 * 注册应用中的其它事件。
 *
 * @return void
 */
public function boot()
{
    parent::boot();

    Event::listen('event.name', function ($foo, $bar) {
        //
    });
}

如果花点时间研究 Event 这个 Facade,我们可以找到他穿透类其实为 Illuminate\Events\Dispatcher

Illuminate\Events\Dispatcher 类的 __construct 方法为:

public function __construct(ContainerContract $container = null)
{
$this->container = $container ?: new Container;
}

作用是将容器设置到 $this->container 属性

监听

我们刚刚在 EventServiceProvider 调用的 Event::listen 方法的代码为:

public function listen($events, $listener)
{
foreach ((array) $events as $event) {
if (Str::contains($event, '*')) {
$this->setupWildcardListen($event, $listener);
} else {
$this->listeners[$event][] = $this->makeListener($listener);
}
}
}

如果监听了通配事件,setupWildcardListen 在辗转后跟 else 一样的调用到了 makeListener

public function makeListener($listener, $wildcard = false)
{
if (is_string($listener)) {
return $this->createClassListener($listener, $wildcard);
}
return function ($event, $payload) use ($listener, $wildcard) {
if ($wildcard) {
return $listener($event, $payload);
}
return $listener(...array_values($payload));
};
}

注意,返回的是闭包!并不会立即执行。

在存放到 $this->listeners 后,事件们就静静地等待被触发

$this->listeners[$event][] = $this->makeListener($listener);

触发

在我们调用 event() 辅助方法后,触发的逻辑其实走到了 dispatch 方法

public function dispatch($event, $payload = [], $halt = false)
{
// When the given "event" is actually an object we will assume it is an event
// object and use the class as the event name and this event itself as the
// payload to the handler, which makes object based events quite simple.
list($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;
}

核心逻辑就这句:

$response = $listener($event, $payload);

16. UserProvider、Guard 和 Authenticatable 机制

Laravel 提供了完善的用户状态判断鉴定、验证机制,这篇文章我们用十五分钟左右的时间来深入框架内部来了解它。
在学习之前,建议先查阅 Laravel China 文档库 > Laravel 5.7 > 用户认证,熟悉用法后,学习效率更高。

相关类

描述
Illuminate\Auth\SessionGuard guard 定义为 web 生效
Illuminate\Auth\EloquentUserProvider 默认的用户提供者
App\Http\Middleware\Authenticate 中间件
App\User 应用自定义 User 模型
Illuminate\Auth\Authenticatable 这是一个 trait,被 App\User 的父类 Illuminate\Foundation\Auth\User 所使用

配置文件解析

Auth::guard 可用的守护者定义在:

laravel/config/auth.php

Lines 38 to 48 in 4d76a68

'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
],
],

这些配置文件中的 providers 定义在

laravel/config/auth.php

Lines 67 to 77 in 4d76a68

'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],


'driver' => 'session',
'driver' => 'token',

sessiontoken 分别指的是 Illuminate\Auth\SessionGuardIlluminate\Auth\TokenGuard

具体的构建方法见 AuthManager::createSessionDriverAuthManager::createTokenDriver,这两个方法是在 AuthManager::resolve 时用以下代码调用的。

$driverMethod = 'create'.ucfirst($config['driver']).'Driver';

注册流程 基础 + 容器部分

Illuminate\Auth\AuthManager

class AuthManager implements FactoryContract
{
use CreatesUserProviders;

用到了 Illuminate\Auth\CreatesUserProviders trait
此 trait 提供如下方法

  • public createUserProvider($provider = null): Illuminate\Contracts\Auth\UserProvider|null
  • protected getProviderConfiguration($provider): array|null
  • protected createDatabaseProvider($config): Illuminate\Auth\DatabaseUserProvider
  • protected createEloquentProvider($config): Illuminate\Auth\EloquentUserProvider
  • public getDefaultUserProvider(): string

Illuminate\Foundation\ApplicationregisterCoreContainerAliases 方法中,注册 auth 时, Illuminate\Auth\AuthManager 被使用。

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\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) {
$this->alias($key, $alias);
}
}
}

容器对象的 registerCoreContainerAliases 方法是在 __construct 阶段被触发的。

注册流程 服务提供者 Illuminate\Auth\AuthServiceProvider

Illuminate\Auth\AuthServiceProvider 中的 register 方法,

public function register()
{
$this->registerAuthenticator();
$this->registerUserResolver();
$this->registerAccessGate();
$this->registerRequestRebindHandler();
}

registerAuthenticator 方法为

protected function registerAuthenticator()
{
$this->app->singleton('auth', function ($app) {
// Once the authentication service has actually been requested by the developer
// we will set a variable in the application indicating such. This helps us
// know that we need to set any queued cookies in the after event later.
$app['auth.loaded'] = true;
return new AuthManager($app);
});
$this->app->singleton('auth.driver', function ($app) {
return $app['auth']->guard();
});
}

registerUserResolver 方法为

protected function registerUserResolver()
{
$this->app->bind(
AuthenticatableContract::class, function ($app) {
return call_user_func($app['auth']->userResolver());
}
);
}

这里便将 authIlluminate\Contracts\Auth\Authenticatable 成功的从 Application 中的 alias 升级为 singleton 。

在我们的 config/app.phpproviders 第一个便是 Illuminate\Auth\AuthServiceProvider

laravel/config/app.php

Lines 122 to 127 in 4d76a68

'providers' => [
/*
* Laravel Framework Service Providers...
*/
Illuminate\Auth\AuthServiceProvider::class,

到这里,web 环境框架启动部分所有初始化用户状态维护机制就完成。

中间件流程 限制登陆后可访问

当一个路由声明必须登陆后可访问,也就是 auth 中间件,会触发:

public function __construct(Auth $auth)
{
$this->auth = $auth;
}

触发容器构建 AuthManager

在容器构建 AuthManager 时,会触发 __construct

public function __construct($app)
{
$this->app = $app;
$this->userResolver = function ($guard = null) {
return $this->guard($guard)->user();
};
}

中间件的 handle

public function handle($request, Closure $next, ...$guards)
{
$this->authenticate($request, $guards);
return $next($request);
}

调用了中间件 authenticate 方法

protected function authenticate($request, array $guards)
{
if (empty($guards)) {
$guards = [null];
}
foreach ($guards as $guard) {
if ($this->auth->guard($guard)->check()) {
return $this->auth->shouldUse($guard);
}
}
throw new AuthenticationException(
'Unauthenticated.', $guards, $this->redirectTo($request)
);
}

如果没登陆,报错

throw new AuthenticationException(
'Unauthenticated.', $guards, $this->redirectTo($request)
);

获取当前用户流程

如果手工在业务中使用 auth()->user() 会触发

public function __call($method, $parameters)
{
return $this->guard()->{$method}(...$parameters);
}

AuthManagerguard() 默认返回的是 SessionGuard,所以 auth()->user() 调用的是

public function user()
{
if ($this->loggedOut) {
return;
}
// If we've already retrieved the user for the current request we can just
// return it back immediately. We do not want to fetch the user data on
// every call to this method because that would be tremendously slow.
if (! is_null($this->user)) {
return $this->user;
}
$id = $this->session->get($this->getName());
// First we will try to load the user using the identifier in the session if
// one exists. Otherwise we will check for a "remember me" cookie in this
// request, and if one exists, attempt to retrieve the user using that.
if (! is_null($id) && $this->user = $this->provider->retrieveById($id)) {
$this->fireAuthenticatedEvent($this->user);
}
// If the user is null, but we decrypt a "recaller" cookie we can attempt to
// pull the user data on that cookie which serves as a remember cookie on
// the application. Once we have a user we can return it to the caller.
$recaller = $this->recaller();
if (is_null($this->user) && ! is_null($recaller)) {
$this->user = $this->userFromRecaller($recaller);
if ($this->user) {
$this->updateSession($this->user->getAuthIdentifier());
$this->fireLoginEvent($this->user, true);
}
}
return $this->user;
}

其 131 行的 $this->provider->retrieveById($id) 调用了 EloquentUserProvider::retrieveById

public function retrieveById($identifier)
{
$model = $this->createModel();
return $model->newQuery()
->where($model->getAuthIdentifierName(), $identifier)
->first();
}

$this->createModel() 返回的是 $this->model,是在

public function __construct(HasherContract $hasher, $model)
{
$this->model = $model;
$this->hasher = $hasher;
}
设置进去的。

AuthManager 调用 createUserProvider 后调用 createEloquentProvider 方法时,

public function createUserProvider($provider = null)
{
if (is_null($config = $this->getProviderConfiguration($provider))) {
return;
}
if (isset($this->customProviderCreators[$driver = ($config['driver'] ?? null)])) {
return call_user_func(
$this->customProviderCreators[$driver], $this->app, $config
);
}
switch ($driver) {
case 'database':
return $this->createDatabaseProvider($config);
case 'eloquent':
return $this->createEloquentProvider($config);
default:
throw new InvalidArgumentException(
"Authentication user provider [{$driver}] is not defined."
);
}

protected function createEloquentProvider($config)
{
return new EloquentUserProvider($this->app['hash'], $config['model']);
}

其传进去的 $config 的获取逻辑为

protected function getProviderConfiguration($provider)
{
if ($provider = $provider ?: $this->getDefaultUserProvider()) {
return $this->app['config']['auth.providers.'.$provider];
}
}

也就是这里设定的 'model' => App\User::class

laravel/config/auth.php

Lines 67 to 77 in 4d76a68

'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],

前面 getAuthIdentifierName 的方法,就是调用 App\User::getAuthIdentifierName 来查找用户了。

return $model->newQuery()
->where($model->getAuthIdentifierName(), $identifier)
->first();


本文章采用「署名 4.0 国际」创作共享协议,
转载前请阅读 相关说明 »

14. Response 对象和 Kernel::terminate

在前面 01. HTTP 入口解析 的第 54-56 行代码分析时

laravel/public/index.php

Lines 54 to 56 in 7028b17

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

我们只分析了 $request 的产生,却没有分析 $response
这节,我们就来研究下 Laravel 的 Response 对象: Illuminate\Http\Response

Illuminate\Http\Response 初识

不难看出,这句代码拿到的一定是个对象。为什么?
因为在下文,就调用了他的 ->send() 方法。

$response->send();

那么 Illuminate\Http\Response 对象是从哪里来的呢?

在从 入口Kernel::handle()管道 的过程尾声,我们拿到了这句逻辑

return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run()
);
});

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

这个 prepareResponse 是干嘛的呢?

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 SymfonyResponse &&
($response instanceof Arrayable ||
$response instanceof Jsonable ||
$response instanceof ArrayObject ||
$response instanceof JsonSerializable ||
is_array($response))) {
$response = new JsonResponse($response);
} elseif (! $response instanceof SymfonyResponse) {
$response = new Response($response);
}
if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) {
$response->setNotModified();
}
return $response->prepare($request);
}

过程为:

  • 判断是否为 PsrResponseInterface 对象;
  • 则判断为模型,且刚创建,将响应头 statusCoe 强制覆盖成 HTTP 201 created
  • 否则判断返回的对象是否具有序列号特征,如果是就重置成 JsonResponse
  • 否则的话覆盖成 Response

于是乎,我们才可以在 controller 中大胆的直接返回字符串而转换成 Response 对象。因为在这里他帮我们转了。

经过上面这一波加工后,在 public/index.php 里面才敢执行 $response->send()

Illuminate\Http\Response::send() 的逻辑

public function send()
{
$this->sendHeaders();
$this->sendContent();
if (\function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
} elseif (!\in_array(\PHP_SAPI, array('cli', 'phpdbg'), true)) {
static::closeOutputBuffers(0, true);
}
return $this;
}

调用的 sendContent 逻辑为

public function sendContent()
{
echo $this->content;
return $this;
}
其中的 echo 代码清晰可见,非常简单。

先于 sendContentsendHeaders 的代码要稍微复杂一点

public function sendHeaders()
{
// headers have already been sent by the developer
if (headers_sent()) {
return $this;
}
// headers
foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) {
foreach ($values as $value) {
header($name.': '.$value, false, $this->statusCode);
}
}
// cookies
foreach ($this->headers->getCookies() as $cookie) {
header('Set-Cookie: '.$cookie->getName().strstr($cookie, '='), false, $this->statusCode);
}
// status
header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);
return $this;
}

先是将 http 响应头依次输出,然后将 Cookie 处理后输出。

send 方法的后面,还有几句

if (\function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
} elseif (!\in_array(\PHP_SAPI, array('cli', 'phpdbg'), true)) {
static::closeOutputBuffers(0, true);
}
作用是判断是否是 fastcgi 或者 cli 模式调用,如果是就关闭连接、刷新缓冲区。

至此,send 逻辑就讲完了。

Kernel::terminate() 的逻辑

App\Http\Kernel 并没有 terminate 方法,一路穿透到了 Illuminate\Foundation\Http\Kernel 里面

public function terminate($request, $response)
{
$this->terminateMiddleware($request, $response);
$this->app->terminate();
}

Illuminate\Foundation\Http\Kernel::terminateMiddleware() 逻辑

protected function terminateMiddleware($request, $response)
{
$middlewares = $this->app->shouldSkipMiddleware() ? [] : array_merge(
$this->gatherRouteMiddleware($request),
$this->middleware
);
foreach ($middlewares as $middleware) {
if (! is_string($middleware)) {
continue;
}
list($name) = $this->parseMiddleware($middleware);
$instance = $this->app->make($name);
if (method_exists($instance, 'terminate')) {
$instance->terminate($request, $response);
}
}
}

将容器中 bind 过的中间件 terminate() 掉。

而这句就比较好理解了,直接调用了容器的 terminate()

容器的 terminate() 方法也比较简单,触发 $terminatingCallbacks 注册的回调。

public function terminate()
{
foreach ($this->terminatingCallbacks as $terminating) {
$this->call($terminating);
}
}

PHP的不常驻特性,一个请求完了就自动回收资源,那么除了需要触发 $terminatingCallbacks 回调外,为什么还要实现 terminate() 呢?

Laravel 为我们提供了一个 php artisan serve 的便捷服务器,启动后 Application 对象是常驻的。所以需要单独回收需要回收的资源。

php artisan serve 的本质也是 php -S,详细资料 PHP 的命令行模式>内置Web Server

扩展阅读:在 Laravel 5.3 以前,Kernel::terminate() 还调用了 fastcgi_finish_request()。后来移除了,主要原因是因为这个函数会阻断 http 响应的头和 body,造成部分特殊情况的 session 问题,不过也可能还有其他原因。

09. 容器的依赖注入机制

本章节以前面没有分析完成的 02. HTTP Kernel Handle 解析06. RouteServiceProvider 详解 做铺垫,通过讲述其最后 runController 的内部逻辑,抛砖引玉,分析 Laravel 依赖注入的原理,

因为前面我们讲了容器对象的绑定的实现 (见:10. 容器的 singleton 和 bind 的实现),所以在这里不再详述。

定义

在大型项目中,因为类的繁多,彼此需要调用到的代码的更多,如果我们在用到对象的每一个地方去 new FooClass(... $parameters ),那么我们的项目重复代码太多,早晚会失去优雅性和可维护性。

所以依赖注入就是在用到一个对象的方法入参声明,你要什么对象,他基于什么实现。框架 (亦或独立的 容器服务 )在执行到这里的时候,发现你声明了需要那几个对象,在 容器服务 里找到这几个对象的实例化逻辑,实例化出来扔进去执行。

路由执行中的依赖注入的实现

解析反射

/**
* Run the route action and return the response.
*
* @return mixed
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
protected function runController()
{
return $this->controllerDispatcher()->dispatch(
$this, $this->getController(), $this->getControllerMethod()
);
}

这里的 Route 对象本身已经是单个路由了,所以 $this->getController()$this->getControllerMethod() 取得的具体的 Controller 类名和方法名。

核心代码其实就是这个 $this->controllerDispatcher()->dispatch() 方法了

/**
* Dispatch a request to a given controller and method.
*
* @param \Illuminate\Routing\Route $route
* @param mixed $controller
* @param string $method
* @return mixed
*/
public function dispatch(Route $route, $controller, $method)
{
$parameters = $this->resolveClassMethodDependencies(
$route->parametersWithoutNulls(), $controller, $method
);
if (method_exists($controller, 'callAction')) {
return $controller->callAction($method, $parameters);
}
return $controller->{$method}(...array_values($parameters));
}

第一步 解析参数

$parameters = $this->resolveClassMethodDependencies(
$route->parametersWithoutNulls(), $controller, $method
);

resolveClassMethodDependencies 的实现

/**
* Resolve the object method's type-hinted dependencies.
*
* @param array $parameters
* @param object $instance
* @param string $method
* @return array
*/
protected function resolveClassMethodDependencies(array $parameters, $instance, $method)
{
if (! method_exists($instance, $method)) {
return $parameters;
}
return $this->resolveMethodDependencies(
$parameters, new ReflectionMethod($instance, $method)
);
}

其中

if (! method_exists($instance, $method)) {
return $parameters;
}

为什么没报错,是考虑到 controller 也是可以用 __call 魔术方法来绑定路由的。

resolveMethodDependencies 的逻辑就是将传入的反射对象的入参解析完成

/**
* Resolve the given method's type-hinted dependencies.
*
* @param array $parameters
* @param \ReflectionFunctionAbstract $reflector
* @return array
*/
public function resolveMethodDependencies(array $parameters, ReflectionFunctionAbstract $reflector)
{
$instanceCount = 0;
$values = array_values($parameters);
foreach ($reflector->getParameters() as $key => $parameter) {
$instance = $this->transformDependency(
$parameter, $parameters
);
if (! is_null($instance)) {
$instanceCount++;
$this->spliceIntoParameters($parameters, $key, $instance);
} elseif (! isset($values[$key - $instanceCount]) &&
$parameter->isDefaultValueAvailable()) {
$this->spliceIntoParameters($parameters, $key, $parameter->getDefaultValue());
}
}
return $parameters;
}

容器挂靠

紧接前文代码

/**
* Attempt to transform the given parameter into a class instance.
*
* @param \ReflectionParameter $parameter
* @param array $parameters
* @return mixed
*/
protected function transformDependency(ReflectionParameter $parameter, $parameters)
{
$class = $parameter->getClass();
// If the parameter has a type-hinted class, we will check to see if it is already in
// the list of parameters. If it is we will just skip it as it is probably a model
// binding and we do not want to mess with those; otherwise, we resolve it here.
if ($class && ! $this->alreadyInParameters($class->name, $parameters)) {
return $parameter->isDefaultValueAvailable()
? $parameter->getDefaultValue()
: $this->container->make($class->name);
}
}

[划重点,前方高能]
如果此参数为一个类,且不是路由中出现的参数,返回 默认值(如有),否则从容器创建

路由的执行

dispatch 方法的下半段

if (method_exists($controller, 'callAction')) {
return $controller->callAction($method, $parameters);
}
return $controller->{$method}(...array_values($parameters));

如果控制器定义了 callAction,执行。
接着直接调用 controller 的方法。

17. 通知/广播 (Broadcast) 机制

鉴权

config/app.php 中我们能看到 Illuminate\Broadcasting\BroadcastServiceProvider 这个服务提供者被注册:

Illuminate\Broadcasting\BroadcastServiceProvider::class,

在前面对服务提供者的讲解中,我们得出了一个服务提供者肯定会包含 bootregister 方法之一,这 BroadcastServiceProvider 就是一个包含而且只包含了 boot 方法的服务提供者:
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Broadcast::routes();
require base_path('routes/channels.php');
}

第17行调用到了 Illuminate\Support\Facades\Broadcast::routes() 假面方法。

顾名思义,就是注册路由的。而这个方法,最终是穿透到了 Illuminate\Broadcasting\BroadcastManager::routes()

/**
* Register the routes for handling broadcast authentication and sockets.
*
* @param array|null $attributes
* @return void
*/
public function routes(array $attributes = null)
{
if ($this->app->routesAreCached()) {
return;
}
$attributes = $attributes ?: ['middleware' => ['web']];
$this->app['router']->group($attributes, function ($router) {
$router->match(
['get', 'post'], '/broadcasting/auth',
'\\'.BroadcastController::class.'@authenticate'
);
});
}

第19行是为了在 config/app.php 卸载 BroadcastServiceProvider 时,直观的让 routes/channels.php 失效的做法,所以就不放在别处

require base_path('routes/channels.php');

第61~63行是如果缓存过路由,则跳出。

第62~第72行相当于与执行了

Route::group(['middleware' => ['web']], function () {
    Route::match(['get', 'post'], '/broadcasting/auth', '\\'.BroadcastController::class.'@authenticate');
});

方法调用到的 controller action 其实是

/**
* Authenticate the request for channel access.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function authenticate(Request $request)
{
return Broadcast::auth($request);
}

这个假面方法 Broadcast::auth() 比较特殊,是根据 config('broadcasting.default') 分发到 Illuminate/Broadcasting/Broadcasters 具体的类中:

  • LogBroadcaster.php
  • NullBroadcaster.php
  • PusherBroadcaster.php
  • RedisBroadcaster.php

我们以 redis 的配置为例 (laravel-echo-server) 。

/**
* Authenticate the incoming request for a given channel.
*
* @param \Illuminate\Http\Request $request
* @return mixed
*
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/
public function auth($request)
{
if (Str::startsWith($request->channel_name, ['private-', 'presence-']) &&
! $request->user()) {
throw new AccessDeniedHttpException;
}
$channelName = Str::startsWith($request->channel_name, 'private-')
? Str::replaceFirst('private-', '', $request->channel_name)
: Str::replaceFirst('presence-', '', $request->channel_name);
return parent::verifyUserCanAccessChannel(
$request, $channelName
);
}

第一步,如果 $request->channel_name 不以 private- 或者 presence- 开头,或者没有提供用户授权导致请求取不到用户,抛出 Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException 异常

第二步,将 $request->channel_name 中的 private- 或者 presence- 还原移除。

第三步,调用 Broadcaster:: verifyUserCanAccessChannel()

/**
* Authenticate the incoming request for a given channel.
*
* @param \Illuminate\Http\Request $request
* @param string $channel
* @return mixed
*
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/
protected function verifyUserCanAccessChannel($request, $channel)
{
foreach ($this->channels as $pattern => $callback) {
if (! Str::is(preg_replace('/\{(.*?)\}/', '*', $pattern), $channel)) {
continue;
}
$parameters = $this->extractAuthParameters($pattern, $channel, $callback);
$handler = $this->normalizeChannelHandlerToCallable($callback);
if ($result = $handler($request->user(), ...$parameters)) {
return $this->validAuthenticationResponse($request, $result);
}
}
throw new AccessDeniedHttpException;
}

routes/channels.php 中定义的 channel_name 匹配上的通道的鉴权回调执行,拿到返回值。如果 if ($result = $handler($request->user(), ...$parameters)) 返回成功的 RedisBroadcaster::validAuthenticationResponse(),否则抛出 Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException 异常

而成功的响应 RedisBroadcaster::validAuthenticationResponse() 逻辑为返回 user_iduser_info 给 websocket 客户端:

/**
* Return the valid authentication response.
*
* @param \Illuminate\Http\Request $request
* @param mixed $result
* @return mixed
*/
public function validAuthenticationResponse($request, $result)
{
if (is_bool($result)) {
return json_encode($result);
}
return json_encode(['channel_data' => [
'user_id' => $request->user()->getAuthIdentifier(),
'user_info' => $result,
]]);
}

至此,鉴权部分完成。


推送

broadcast() 方法定义于

function broadcast($event = null)
{
return app(BroadcastFactory::class)->event($event);
}

此方法最终走到了

/**
* Begin broadcasting an event.
*
* @param mixed|null $event
* @return \Illuminate\Broadcasting\PendingBroadcast|void
*/
public function event($event = null)
{
return new PendingBroadcast($this->app->make('events'), $event);
}

PendingBroadcast 类比较特殊,有个 __destruct 解构方法:

/**
* Handle the object's destruction.
*
* @return void
*/
public function __destruct()
{
$this->events->dispatch($this->event);
}

走到

/**
* Fire an event and call the listeners.
*
* @param string|object $event
* @param mixed $payload
* @param bool $halt
* @return array|null
*/
public function dispatch($event, $payload = [], $halt = false)
{
// When the given "event" is actually an object we will assume it is an event
// object and use the class as the event name and this event itself as the
// payload to the handler, which makes object based events quite simple.
list($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;
}

调用了

/**
* Broadcast the given event class.
*
* @param \Illuminate\Contracts\Broadcasting\ShouldBroadcast $event
* @return void
*/
protected function broadcastEvent($event)
{
$this->container->make(BroadcastFactory::class)->queue($event);
}

还是回到了 BroadcastManager

/**
* Queue the given event for broadcast.
*
* @param mixed $event
* @return void
*/
public function queue($event)
{
$connection = $event instanceof ShouldBroadcastNow ? 'sync' : null;
if (is_null($connection) && isset($event->connection)) {
$connection = $event->connection;
}
$queue = null;
if (method_exists($event, 'broadcastQueue')) {
$queue = $event->broadcastQueue();
} elseif (isset($event->broadcastQueue)) {
$queue = $event->broadcastQueue;
} elseif (isset($event->queue)) {
$queue = $event->queue;
}
$this->app->make('queue')->connection($connection)->pushOn(
$queue, new BroadcastEvent(clone $event)
);
}

根据前文的 config('broadcasting.default') 配置,

第127~129行,是将广播事件 BroadcastEvent 入队。

根据我们对队列的了解,BroadcastEvent 应该有 handle() 方法

/**
* Handle the queued job.
*
* @param \Illuminate\Contracts\Broadcasting\Broadcaster $broadcaster
* @return void
*/
public function handle(Broadcaster $broadcaster)
{
$name = method_exists($this->event, 'broadcastAs')
? $this->event->broadcastAs() : get_class($this->event);
$broadcaster->broadcast(
Arr::wrap($this->event->broadcastOn()), $name,
$this->getPayloadFromEvent($this->event)
);
}

第46~49行,是执行广播,调用了 RedisBroadcaster::broadcast()

/**
* Broadcast the given event.
*
* @param array $channels
* @param string $event
* @param array $payload
* @return void
*/
public function broadcast(array $channels, $event, array $payload = [])
{
$connection = $this->redis->connection($this->connection);
$payload = json_encode([
'event' => $event,
'data' => $payload,
'socket' => Arr::pull($payload, 'socket'),
]);
foreach ($this->formatChannels($channels) as $channel) {
$connection->publish($channel, $payload);
}
}

消息进入 redis。

// TODO: laravel-echo-server 部分。

06. RouteServiceProvider 详解

RouteServiceProvider 详解

RouteServiceProvider::boot 阶段,所有路由都只是执行登记,匹配的逻辑是在 02. HTTP Kernel Handle解析 的 dispatchToRouter 阶段。

我们先找到 RouteServiceProvider.php

config/app.phpproviders 中定义的 RouteServiceProvider 其实是:

App\Providers\RouteServiceProvider::class,

而这个类其实继承自 Illuminate\Foundation\Support\Providers\RouteServiceProvider
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
class RouteServiceProvider extends ServiceProvider
{

我们分析 Illuminate\Foundation\Support\Providers\RouteServiceProvider 的代码

register() 阶段

register 方法是空的

/**
* Register the service provider.
*
* @return void
*/
public function register()
{
//
}

boot() 阶段

而在 boot 阶段,几乎运行了 RouteServiceProvider 中的所有方法

/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
$this->setRootControllerNamespace();
if ($this->app->routesAreCached()) {
$this->loadCachedRoutes();
} else {
$this->loadRoutes();
$this->app->booted(function () {
$this->app['router']->getRoutes()->refreshNameLookups();
$this->app['router']->getRoutes()->refreshActionLookups();
});
}
}

一. setRootControllerNamespace()

/**
* Set the root controller namespace for the application.
*
* @return void
*/
protected function setRootControllerNamespace()
{
if (! is_null($this->namespace)) {
$this->app[UrlGenerator::class]->setRootControllerNamespace($this->namespace);
}
}

二. 判断路由是否缓存过

Illuminate\Foundation\Application::routesAreCached
其实就是判断文件 bootstrap/cache/routes.php 是否存在

/**
* Determine if the application routes are cached.
*
* @return bool
*/
public function routesAreCached()
{
return $this['files']->exists($this->getCachedRoutesPath());
}

/**
* Get the path to the routes cache file.
*
* @return string
*/
public function getCachedRoutesPath()
{
return $this->bootstrapPath().'/cache/routes.php';
}

存在则直接加载
/**
* Load the cached routes for the application.
*
* @return void
*/
protected function loadCachedRoutes()
{
$this->app->booted(function () {
require $this->app->getCachedRoutesPath();
});
}

三. 如果没有缓存,则遍历路由

/**
* Load the application routes.
*
* @return void
*/
protected function loadRoutes()
{
if (method_exists($this, 'map')) {
$this->app->call([$this, 'map']);
}
}

map 方法在这里
/**
* Define the routes for the application.
*
* @return void
*/
public function map()
{
$this->mapApiRoutes();
$this->mapWebRoutes();
//
}

/**
* Define the "web" routes for the application.
*
* These routes all receive session state, CSRF protection, etc.
*
* @return void
*/
protected function mapWebRoutes()
{
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
}
/**
* Define the "api" routes for the application.
*
* These routes are typically stateless.
*
* @return void
*/
protected function mapApiRoutes()
{
Route::prefix('api')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
}

这里的 mapWebRoutesmapApiRoutes 是分别将 routes/web.php 和 routes/api.php 用 Route 门面类的 group 加载了一遍。 Route::group 实质运行到的是 Illuminate\Routing\Router::group (关于门面类的文章请见 [TODO]),代码为
/**
* Create a route group with shared attributes.
*
* @param array $attributes
* @param \Closure|string $routes
* @return void
*/
public function group(array $attributes, $routes)
{
$this->updateGroupStack($attributes);
// Once we have updated the group stack, we'll load the provided routes and
// merge in the group's attributes when the routes are created. After we
// have created the routes, we will pop the attributes off the stack.
$this->loadRoutes($routes);
array_pop($this->groupStack);
}
/**
* Update the group stack with the given attributes.
*
* @param array $attributes
* @return void
*/
protected function updateGroupStack(array $attributes)
{
if (! empty($this->groupStack)) {
$attributes = RouteGroup::merge($attributes, end($this->groupStack));
}
$this->groupStack[] = $attributes;
}
/**
* Merge the given array with the last group stack.
*
* @param array $new
* @return array
*/
public function mergeWithLastGroup($new)
{
return RouteGroup::merge($new, end($this->groupStack));
}
/**
* Load the provided routes.
*
* @param \Closure|string $routes
* @return void
*/
protected function loadRoutes($routes)
{
if ($routes instanceof Closure) {
$routes($this);
} else {
$router = $this;
require $routes;
}
}

上面的逻辑是一层层剥开 Route::group 去执行里面的 Route::get / Route::post ...
* Register a new GET route with the router.
*
* @param string $uri
* @param \Closure|array|string|null $action
* @return \Illuminate\Routing\Route
*/
public function get($uri, $action = null)
{
return $this->addRoute(['GET', 'HEAD'], $uri, $action);
}
/**
* Register a new POST route with the router.
*
* @param string $uri
* @param \Closure|array|string|null $action
* @return \Illuminate\Routing\Route
*/
public function post($uri, $action = null)
{
return $this->addRoute('POST', $uri, $action);
}
/**
* Register a new PUT route with the router.
*
* @param string $uri
* @param \Closure|array|string|null $action
* @return \Illuminate\Routing\Route
*/
public function put($uri, $action = null)
{
return $this->addRoute('PUT', $uri, $action);
}
/**
* Register a new PATCH route with the router.
*
* @param string $uri
* @param \Closure|array|string|null $action
* @return \Illuminate\Routing\Route
*/
public function patch($uri, $action = null)
{
return $this->addRoute('PATCH', $uri, $action);
}
/**
* Register a new DELETE route with the router.
*
* @param string $uri
* @param \Closure|array|string|null $action
* @return \Illuminate\Routing\Route
*/
public function delete($uri, $action = null)
{
return $this->addRoute('DELETE', $uri, $action);
}
/**
* Register a new OPTIONS route with the router.
*
* @param string $uri
* @param \Closure|array|string|null $action
* @return \Illuminate\Routing\Route
*/
public function options($uri, $action = null)
{
return $this->addRoute('OPTIONS', $uri, $action);
}
/**
* Register a new route responding to all verbs.
*
* @param string $uri
* @param \Closure|array|string|null $action
* @return \Illuminate\Routing\Route
*/
public function any($uri, $action = null)
{
return $this->addRoute(self::$verbs, $uri, $action);
}

不难发现,不管是 GET/POST... 还是 any,都只是调用了 addRoute 将这个路由的属性登记了一下:
/**
* Add a route to the underlying route collection.
*
* @param array|string $methods
* @param string $uri
* @param \Closure|array|string|null $action
* @return \Illuminate\Routing\Route
*/
public function addRoute($methods, $uri, $action)
{
return $this->routes->add($this->createRoute($methods, $uri, $action));
}

$this->routes 是一个 \Illuminate\Routing\RouteCollection 的集合类,add 由这个方法生成的路由
/**
* Create a new route instance.
*
* @param array|string $methods
* @param string $uri
* @param mixed $action
* @return \Illuminate\Routing\Route
*/
protected function createRoute($methods, $uri, $action)
{
// If the route is routing to a controller we will parse the route action into
// an acceptable array format before registering it and creating this route
// instance itself. We need to build the Closure that will call this out.
if ($this->actionReferencesController($action)) {
$action = $this->convertToControllerAction($action);
}
$route = $this->newRoute(
$methods, $this->prefix($uri), $action
);
// If we have groups that need to be merged, we will merge them now after this
// route has already been created and is ready to go. After we're done with
// the merge we will be ready to return the route back out to the caller.
if ($this->hasGroupStack()) {
$this->mergeGroupAttributesIntoRoute($route);
}
$this->addWhereClausesToRoute($route);
return $route;
}

四. dispatch() -> runRoute() 阶段

路由登记完成后,就是 Kernel 触发管道层层剥洋葱调用中间件最后触发路由 dispatch (当然,这已经运行到 RouteServiceProvider 的外面了)。

剥洋葱的过程请查阅 05. Pipeline 解析

/**
* Run the given route within a Stack "onion" instance.
*
* @param \Illuminate\Routing\Route $route
* @param \Illuminate\Http\Request $request
* @return mixed
*/
protected function runRouteWithinStack(Route $route, Request $request)
{
$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(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run()
);
});
}

第679行的 $route->run 是至关重要的,调用到了 controller (本质其实是使用 09. 容器的依赖注入机制 将路由方法所依赖参数解析出来,并运行)

/**
* Run the route action and return the response.
*
* @return mixed
*/
public function run()
{
$this->container = $this->container ?: new Container;
try {
if ($this->isControllerAction()) {
return $this->runController();
}
return $this->runCallable();
} catch (HttpResponseException $e) {
return $e->getResponse();
}
}

15. Laravel 神奇的 Manager 类

Laravel 有不少门面类,如 SessionView 初一看令人诈舌,这些类的功能类不只一个,那么 Laravel 是怎么让这些奇葩的门面通往不止一个后门的?

类声明

namespace Illuminate\Support;
use Closure;
use InvalidArgumentException;
abstract class Manager
{

这是一个抽象类。

属性

/**
* The application instance.
*
* @var \Illuminate\Foundation\Application
*/
protected $app;
/**
* The registered custom driver creators.
*
* @var array
*/
protected $customCreators = [];
/**
* The array of created "drivers".
*
* @var array
*/
protected $drivers = [];

$app 是容器,这个 $customCreators 是什么鬼没看懂我们可以不管它。后面这个 $drivers,是一个数组,放的什么东西呢?

driver 在英语中是驱动器的意思。

算了。。。再这么讲下去太拖了。我们拉到最后

魔术函数

public function __call($method, $parameters)
{
return $this->driver()->$method(...$parameters);
}

我们看到,当调用到当前实现类不存在方法时会调用 $this->driver() 下的同名方法。

public function driver($driver = null)
{
$driver = $driver ?: $this->getDefaultDriver();
if (is_null($driver)) {
throw new InvalidArgumentException(sprintf(
'Unable to resolve NULL driver for [%s].', static::class
));
}
// If the given driver has not been created before, we will create the instances
// here and cache it so we can return it next time very quickly. If there is
// already a driver created by this name, we'll just return that instance.
if (! isset($this->drivers[$driver])) {
$this->drivers[$driver] = $this->createDriver($driver);
}
return $this->drivers[$driver];
}

然后 getDefaultDriver

abstract public function getDefaultDriver();

这是抽象函数,我们到具体的例子中看吧。比如 session

public function getDefaultDriver()
{
return $this->app['config']['session.driver'];
}


所以,这些继承于 Illuminate\Support\Manager 的门后类,实现了向 driver 的穿透。

总结: Manager first, driver end. 经理先行,司机垫后

10. 容器的 singleton 和 bind 的实现

容器的 singletonbind 方法在整个 Laravel 框架或扩展中是调用的比较频繁的底层方法,掌握其原理能帮助我们加深 laravel 容器的理解。

初识

singleton() 代码

单身狗模式

/**
* Register a shared binding in the container.
*
* @param string $abstract
* @param \Closure|string|null $concrete
* @return void
*/
public function singleton($abstract, $concrete = null)
{
$this->bind($abstract, $concrete, true);
}

bind() 代码

分裂模式

/**
* Register a binding with the container.
*
* @param string $abstract
* @param \Closure|string|null $concrete
* @param bool $shared
* @return void
*/
public function bind($abstract, $concrete = null, $shared = false)
{
// If no concrete type was given, we will simply set the concrete type to the
// abstract type. After that, the concrete type to be registered as shared
// without being forced to state their classes in both of the parameters.
$this->dropStaleInstances($abstract);
if (is_null($concrete)) {
$concrete = $abstract;
}
// If the factory is not a Closure, it means it is just a class name which is
// bound into this container to the abstract type and we will just wrap it
// up inside its own Closure to give us more convenience when extending.
if (! $concrete instanceof Closure) {
$concrete = $this->getClosure($abstract, $concrete);
}
$this->bindings[$abstract] = compact('concrete', 'shared');
// If the abstract type was already resolved in this container we'll fire the
// rebound listener so that any objects which have already gotten resolved
// can have their copy of the object updated via the listener callbacks.
if ($this->resolved($abstract)) {
$this->rebound($abstract);
}
}

初略看二者差异可能看不大出来。
我们从他们暴露的接口入手,先用,再啪(扒)源码。

调试

祭出大杀器:php artisan tinker

bind

>>> app()->bind('test', function(){dump(1); return new \StdClass();});
1
=> null
>>>
>>> app()->make('test')
1
=> {#2895}
>>>
>>> app()->make('test')
1
=> {#2896}

可以看出,调用 bind 时,每次取出被绑定的对象时,都会重新去构建一次。

如果用singleton 呢。

singleton

>>> app()->singleton('test', function(){dump(1); return new \StdClass();});
1
=> null
>>> app()->make('test')
=> {#2915}
>>> app()->make('test')
=> {#2915}
>>>

分析

bind 的实质,容器中捆绑

我们回头再看下 bind() 方法

/**
* Register a binding with the container.
*
* @param string $abstract
* @param \Closure|string|null $concrete
* @param bool $shared
* @return void
*/
public function bind($abstract, $concrete = null, $shared = false)
{
// If no concrete type was given, we will simply set the concrete type to the
// abstract type. After that, the concrete type to be registered as shared
// without being forced to state their classes in both of the parameters.
$this->dropStaleInstances($abstract);
if (is_null($concrete)) {
$concrete = $abstract;
}
// If the factory is not a Closure, it means it is just a class name which is
// bound into this container to the abstract type and we will just wrap it
// up inside its own Closure to give us more convenience when extending.
if (! $concrete instanceof Closure) {
$concrete = $this->getClosure($abstract, $concrete);
}
$this->bindings[$abstract] = compact('concrete', 'shared');
// If the abstract type was already resolved in this container we'll fire the
// rebound listener so that any objects which have already gotten resolved
// can have their copy of the object updated via the listener callbacks.
if ($this->resolved($abstract)) {
$this->rebound($abstract);
}
}

逻辑步骤为:

  • 参数依次为 $abstract$concrete
  • dropStaleInstances 移除之前 bind 过的 $abstract 的数据(旧的 $concret
  • 如果没有传 $concrete,则将 $concrete 设置成 $abstract
  • 如果为闭包 如果不为闭包(感谢 @HubQin 的指正),则通过 Container::getClosure() 方法将闭包改成一个接受 $container$parameters 的包裹后的闭包。此闭包实质就是在需要运行的时候,运行闭包。
  • Container::$bindings 数组 $abstract 为 key 的值设置为 compact('concrete', 'shared'),即 ["concrete" => $concrete, "shared" => $shared]
  • 如果 bind 的对象已经 resolved 过了,通过 Container::rebound() 触发 reboundCallbacks 中的回调。

到目前为止,暂时还没看出 $sharesingletonbind 的影响。只知道 bind 在执行过程中,将 $shared$concrete 一起存到了 Container::$bindings 中。

用句四川话,莫慌,我们马上就要揭开关键的面纱了。。。

揭开 Container::make() 神秘的面纱

/**
* Resolve the given type from the container.
*
* @param string $abstract
* @param array $parameters
* @return mixed
*/
public function make($abstract, array $parameters = [])
{
return $this->resolve($abstract, $parameters);
}

其实 make 是直接透传给了 resolve

/**
* Resolve the given type from the container.
*
* @param string $abstract
* @param array $parameters
* @return mixed
*/
protected function resolve($abstract, $parameters = [])
{
$abstract = $this->getAlias($abstract);
$needsContextualBuild = ! empty($parameters) || ! is_null(
$this->getContextualConcrete($abstract)
);
// If an instance of the type is currently being managed as a singleton we'll
// just return an existing instance instead of instantiating new instances
// so the developer can keep using the same objects instance every time.
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
return $this->instances[$abstract];
}
$this->with[] = $parameters;
$concrete = $this->getConcrete($abstract);
// We're ready to instantiate an instance of the concrete type registered for
// the binding. This will instantiate the types, as well as resolve any of
// its "nested" dependencies recursively until all have gotten resolved.
if ($this->isBuildable($concrete, $abstract)) {
$object = $this->build($concrete);
} else {
$object = $this->make($concrete);
}
// If we defined any extenders for this type, we'll need to spin through them
// and apply them to the object being built. This allows for the extension
// of services, such as changing configuration or decorating the object.
foreach ($this->getExtenders($abstract) as $extender) {
$object = $extender($object, $this);
}
// If the requested type is registered as a singleton we'll want to cache off
// the instances in "memory" so we can return it later without creating an
// entirely new instance of an object on each subsequent request for it.
if ($this->isShared($abstract) && ! $needsContextualBuild) {
$this->instances[$abstract] = $object;
}
$this->fireResolvingCallbacks($abstract, $object);
// Before returning, we will also set the resolved flag to "true" and pop off
// the parameter overrides for this build. After those two things are done
// we will be ready to return back the fully constructed class instance.
$this->resolved[$abstract] = true;
array_pop($this->with);
return $object;
}

第一步骤的

$abstract = $this->getAlias($abstract);

是做了森么呢?
还原用 Container::alias() 方法设置过别名的原始 $abstract

下一步,

$needsContextualBuild = ! empty($parameters) || ! is_null(
$this->getContextualConcrete($abstract)
);

Container::getContextualConcrete() 其实是取这里设置进取的 上下文绑定 的数据

/**
* Define the implementation for the contextual binding.
*
* @param \Closure|string $implementation
* @return void
*/
public function give($implementation)
{
$this->container->addContextualBinding(
$this->concrete, $this->needs, $implementation
);
}

ContextualBindingBuilder::give() 暂时就不在这里暂开研究了。咱们继续往下看

$needsContextualBuild 值为 make() 方法是否传了第二个参数,也就是 $parametersneeds contextual build 意思是

构建时,是否具有上下文关联性

if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
return $this->instances[$abstract];
}

判断 Container::$instances 数组中是否能通过 $abstract 找到对象。如果找得到且不具有上下文关联性,就用之前创建好的对象。

$this->with[] = $parameters;
$concrete = $this->getConcrete($abstract);

getConcrete() 代码为

* @param string $abstract
* @return mixed $concrete
*/
protected function getConcrete($abstract)
{
if (! is_null($concrete = $this->getContextualConcrete($abstract))) {
return $concrete;
}
// If we don't have a registered resolver or concrete for the type, we'll just
// assume each type is a concrete name and will attempt to resolve it as is
// since the container should be able to resolve concretes automatically.
if (isset($this->bindings[$abstract])) {
return $this->bindings[$abstract]['concrete'];
}
return $abstract;
}

相信你能看出来 return $this->bindings[$abstract]['concrete'] ,就是根据 $abstract 把前面 bindsingleton 的数据返回。

这里的 isBuildable 是判断 $abstract 是否与 $concrete 一致或 $concrete 为闭包

如果不是,那么可能是还有递归调用,需要再走一次 make

if ($this->isBuildable($concrete, $abstract)) {
$object = $this->build($concrete);
} else {
$object = $this->make($concrete);
}

645 行调用的 isBuildable()

protected function isBuildable($concrete, $abstract)
{
return $concrete === $abstract || $concrete instanceof Closure;
}

接着调用了 Container::build() 方法

public function build($concrete)
{
// If the concrete type is actually a Closure, we will just execute it and
// hand back the results of the functions, which allows functions to be
// used as resolvers for more fine-tuned resolution of these objects.
if ($concrete instanceof Closure) {
return $concrete($this, $this->getLastParameterOverride());
}
$reflector = new ReflectionClass($concrete);
// If the type is not instantiable, the developer is attempting to resolve
// an abstract type such as an Interface of Abstract Class and there is
// no binding registered for the abstractions so we need to bail out.
if (! $reflector->isInstantiable()) {
return $this->notInstantiable($concrete);
}
$this->buildStack[] = $concrete;
$constructor = $reflector->getConstructor();
// If there are no constructors, that means there are no dependencies then
// we can just resolve the instances of the objects right away, without
// resolving any other types or dependencies out of these containers.
if (is_null($constructor)) {
array_pop($this->buildStack);
return new $concrete;
}
$dependencies = $constructor->getParameters();
// Once we have all the constructor's parameters we can create each of the
// dependency instances and then use the reflection instances to make a
// new instance of this class, injecting the created dependencies in.
$instances = $this->resolveDependencies(
$dependencies
);
array_pop($this->buildStack);
return $reflector->newInstanceArgs($instances);
}

其中的

$reflector = new ReflectionClass($concrete);
$dependencies = $constructor->getParameters();
// Once we have all the constructor's parameters we can create each of the
// dependency instances and then use the reflection instances to make a
// new instance of this class, injecting the created dependencies in.
$instances = $this->resolveDependencies(
$dependencies
);
array_pop($this->buildStack);
return $reflector->newInstanceArgs($instances);
是通过反射解析待构建类所需参数中其他对象参数,从容器中取出填入。

再往下面就是执行 Container::extend 设置的扩充流程了。

foreach ($this->getExtenders($abstract) as $extender) {
$object = $extender($object, $this);
}

再后面,才是对我们 singleton()bind() 产生深远影响的代码:

if ($this->isShared($abstract) && ! $needsContextualBuild) {
$this->instances[$abstract] = $object;
}

我们在前面知道,如果为 singleton() 执行到 resolve() 的第二个参数 $sharedtrue, bindfalse
前面 $needsContextualBuild 判断的位置,判断了 Container::$instance 中是否有 $abstract, 所以在这里如果 resolve 没有将 $object 绑入 Container::$instance,那么下次 Container::make() 依旧会在去 build() 一次,这就产生了 singleton()bind()的特性。

再接着,就是触发事件了。

$this->fireResolvingCallbacks($abstract, $object);

最后是一些收尾工作

$this->resolved[$abstract] = true;
array_pop($this->with);
return $object;

此章节为什么叫做单身狗呢?因为 Container::make() 严格遵守 $shared,如果声明了 true,他保证不搞一个分身(所以是单身)给你。

05. Pipeline 和 Middleware

Laravel 的管道机制,是其中间件 (middleware) 得以被序列执行的基础

管道机制

在开始之前我们先帖一段管理的应用代码,位于 02. HTTP Kernel Handle解析

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

语义化得出的分析为
携带 send 请求对象,经过中间件的处理,然后进入路由。

代码解析

  1. send 携带方法

    /**
    * Set the object being sent through the pipeline.
    *
    * @param mixed $passable
    * @return $this
    */
    public function send($passable)
    {
    $this->passable = $passable;
    return $this;
    }

    好像没啥出奇的,就是传入并储存一个对象,返回 $this

  2. through 经过方法

    /**
    * Set the array of pipes.
    *
    * @param array|mixed $pipes
    * @return $this
    */
    public function through($pipes)
    {
    $this->pipes = is_array($pipes) ? $pipes : func_get_args();
    return $this;
    }

    好像也没啥关键的实现,传入“被管道”的集合,返回 $this

  3. then 异步方法

    /**
    * Run the pipeline with a final destination callback.
    *
    * @param \Closure $destination
    * @return mixed
    */
    public function then(Closure $destination)
    {
    $pipeline = array_reduce(
    array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
    );
    return $pipeline($this->passable);
    }

之前的都只是做登记一下就GG的作用,这个 then 才是根本大法,
使用 array_reduce 处理得到 $pipeline。这个 array_reduce 是何方神圣呢?我们查文档得到:
Iteratively reduce the array to a single value using a callback function(中文意思就是 使用回调函数,迭代地将数组减少为单个值)。

但在走到 array_reduce 之前,调用了 carry 方法 (在 5.3 及以前版本,carry 方法其实为 getSlice 方法,而过程几乎一样 来源),其内部逻辑为:

/**
* Get a Closure that represents a slice of the application onion.
*
* @return \Closure
*/
protected function carry()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
if (is_callable($pipe)) {
// If the pipe is an instance of a Closure, we will just call it directly but
// otherwise we'll resolve the pipes out of the container and call it with
// the appropriate method and arguments, returning the results back out.
return $pipe($passable, $stack);
} elseif (! is_object($pipe)) {
list($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];
}
$response = method_exists($pipe, $this->method)
? $pipe->{$this->method}(...$parameters)
: $pipe(...$parameters);
return $response instanceof Responsable
? $response->toResponse($this->container->make(Request::class))
: $response;
};
};
}

Laravel 的这个 carry 方法返回的是一个闭包,执行返回的闭包所返回的又是一个闭包,只不过前一个返回是 array_reduce 批量执行时拿到的,后面是处理原始对象(请求)时,处理中间件时候拿到的。

  • 闭包(前一个)的第一个参数 $stack 为 null 或者闭包对象,第二个参数 $pipe 为从1开始的调用顺序。
  • 闭包(前一个)的返回值,为管道中被执行的逻辑,也就是 middleware 的 handle。

具体 handle 代码我们举一个例子:

/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*
* @throws \Illuminate\Session\TokenMismatchException
*/
public function handle($request, Closure $next)
{
if (
$this->isReading($request) ||
$this->runningUnitTests() ||
$this->inExceptArray($request) ||
$this->tokensMatch($request)
) {
return tap($next($request), function ($response) use ($request) {
if ($this->addHttpCookie) {
$this->addCookieToResponse($request, $response);
}
});
}
throw new TokenMismatchException;
}

其中的 $next($request) 表示执行下一个中间件, $next 指的就是下面这个闭包

return function ($passable) use ($stack, $pipe) {
if (is_callable($pipe)) {
// If the pipe is an instance of a Closure, we will just call it directly but
// otherwise we'll resolve the pipes out of the container and call it with
// the appropriate method and arguments, returning the results back out.
return $pipe($passable, $stack);
} elseif (! is_object($pipe)) {
list($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];
}
$response = method_exists($pipe, $this->method)
? $pipe->{$this->method}(...$parameters)
: $pipe(...$parameters);
return $response instanceof Responsable
? $response->toResponse($this->container->make(Request::class))
: $response;
};

关于中间件执行的流程,是不是有眉目了?
image

等上面执行完成,Laravel 就要执行这个闭包了

/**
* Get the route dispatcher callback.
*
* @return \Closure
*/
protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);
return $this->router->dispatch($request);
};
}

也就是执行路由的 dispatch 逻辑(包含被调用的逻辑一共 82 行代码)。

/**
* Dispatch the request to the application.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
*/
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 mixed
*/
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->current = $route = $this->routes->match($request);
$this->container->instance(Route::class, $route);
return $route;
}
/**
* Return the response for the given route.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Routing\Route $route
* @return mixed
*/
protected function runRoute(Request $request, Route $route)
{
$request->setRouteResolver(function () use ($route) {
return $route;
});
$this->events->dispatch(new Events\RouteMatched($route, $request));
return $this->prepareResponse($request,
$this->runRouteWithinStack($route, $request)
);
}
/**
* Run the given route within a Stack "onion" instance.
*
* @param \Illuminate\Routing\Route $route
* @param \Illuminate\Http\Request $request
* @return mixed
*/
protected function runRouteWithinStack(Route $route, Request $request)
{
$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(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run()
);
});
}

你可能会因为这里又有中间件而诧异

return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run()
);
});

其实这里的中间件和前面的不一样,前面的是在kernel 中注册的中间件。这里的中间件是在路由绑定中注册的中间件。是两部分。

于是乎,Laravel 鬼使神差的实现了中间件的管道执行机制。

接下来,请查看 09. 容器的依赖注入机制 了解 $route->run() 背后的故事。

03. ServiceProvider Register 解析

ServiceProvider Register 解析

前面 《02. Kernel Handle解析》 结尾,我们留下了 注册服务提供者启动服务提供者 是两个比较重要的步骤的悬念,接下来两篇文章,我们来分别解析这两大流程。

代码

<?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();
}
}

解析

整个 RegisterProviders 都是粮衣,其没有任何功能功能代码,而是调用到了 Illuminate\Foundation\ ApplicationregisterConfiguredProviders

注意运行时实际并没有调用 Illuminate\Contracts\Foundation\Application 的 registerConfiguredProviders,而是调用了其具体实现类 Illuminate\Foundation\ Application 的 registerConfiguredProviders。不要被上面代码中的注解表象所迷惑

/**
* Register all of the configured providers.
*
* @return void
*/
public function registerConfiguredProviders()
{
$providers = Collection::make($this->config['app.providers'])
->partition(function ($provider) {
return Str::startsWith($provider, 'Illuminate\\');
});
$providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);
(new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
->load($providers->collapse()->toArray());
}

上面第 540-543 行是将 config/app.php 中配置的 providers

laravel/config/app.php

Lines 122 to 163 in d081c91

'providers' => [
/*
* Laravel Framework Service Providers...
*/
Illuminate\Auth\AuthServiceProvider::class,
Illuminate\Broadcasting\BroadcastServiceProvider::class,
Illuminate\Bus\BusServiceProvider::class,
Illuminate\Cache\CacheServiceProvider::class,
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
Illuminate\Cookie\CookieServiceProvider::class,
Illuminate\Database\DatabaseServiceProvider::class,
Illuminate\Encryption\EncryptionServiceProvider::class,
Illuminate\Filesystem\FilesystemServiceProvider::class,
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
Illuminate\Hashing\HashServiceProvider::class,
Illuminate\Mail\MailServiceProvider::class,
Illuminate\Notifications\NotificationServiceProvider::class,
Illuminate\Pagination\PaginationServiceProvider::class,
Illuminate\Pipeline\PipelineServiceProvider::class,
Illuminate\Queue\QueueServiceProvider::class,
Illuminate\Redis\RedisServiceProvider::class,
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
Illuminate\Session\SessionServiceProvider::class,
Illuminate\Translation\TranslationServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
/*
* Package Service Providers...
*/
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
],

读取出来,判断是否为 Illuminate\ 开头,归为两组:

Illuminate\Support\Collection {#2825
     all: [
       Illuminate\Support\Collection {#2822
         all: [
            "Illuminate\Auth\AuthServiceProvider",
            "Illuminate\Broadcasting\BroadcastServiceProvider",
            "Illuminate\Bus\BusServiceProvider",
            "Illuminate\Cache\CacheServiceProvider",
            "Illuminate\Foundation\Providers\ConsoleSupportServiceProvider",
            "Illuminate\Cookie\CookieServiceProvider",
            "Illuminate\Database\DatabaseServiceProvider",
            "Illuminate\Encryption\EncryptionServiceProvider",
            "Illuminate\Filesystem\FilesystemServiceProvider",
            "Illuminate\Foundation\Providers\FoundationServiceProvider",
            "Illuminate\Hashing\HashServiceProvider",
            "Illuminate\Mail\MailServiceProvider",
            "Illuminate\Notifications\NotificationServiceProvider",
            "Illuminate\Pagination\PaginationServiceProvider",
            "Illuminate\Pipeline\PipelineServiceProvider",
            "Illuminate\Queue\QueueServiceProvider",
            "Illuminate\Redis\RedisServiceProvider",
            "Illuminate\Auth\Passwords\PasswordResetServiceProvider",
            "Illuminate\Session\SessionServiceProvider",
            "Illuminate\Translation\TranslationServiceProvider",
            "Illuminate\Validation\ValidationServiceProvider",
            "Illuminate\View\ViewServiceProvider",
         ],
       },
       Illuminate\Support\Collection {#2823
         all: [
            "22" => "App\Providers\AppServiceProvider",
            "22" => "App\Providers\AuthServiceProvider",
            "22" => "App\Providers\EventServiceProvider",
            "22" => "App\Providers\RouteServiceProvider",
         ],
       },
     ],
   }

接着 $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]) 这一行会得到如下结果

Illuminate\Support\Collection {#33
  #items: array:3 [
    0 => Illuminate\Support\Collection {#21
      #items: array:22 [
        0 => "Illuminate\Auth\AuthServiceProvider"
        1 => "Illuminate\Broadcasting\BroadcastServiceProvider"
        2 => "Illuminate\Bus\BusServiceProvider"
        3 => "Illuminate\Cache\CacheServiceProvider"
        4 => "Illuminate\Foundation\Providers\ConsoleSupportServiceProvider"
        5 => "Illuminate\Cookie\CookieServiceProvider"
        6 => "Illuminate\Database\DatabaseServiceProvider"
        7 => "Illuminate\Encryption\EncryptionServiceProvider"
        8 => "Illuminate\Filesystem\FilesystemServiceProvider"
        9 => "Illuminate\Foundation\Providers\FoundationServiceProvider"
        10 => "Illuminate\Hashing\HashServiceProvider"
        11 => "Illuminate\Mail\MailServiceProvider"
        12 => "Illuminate\Notifications\NotificationServiceProvider"
        13 => "Illuminate\Pagination\PaginationServiceProvider"
        14 => "Illuminate\Pipeline\PipelineServiceProvider"
        15 => "Illuminate\Queue\QueueServiceProvider"
        16 => "Illuminate\Redis\RedisServiceProvider"
        17 => "Illuminate\Auth\Passwords\PasswordResetServiceProvider"
        18 => "Illuminate\Session\SessionServiceProvider"
        19 => "Illuminate\Translation\TranslationServiceProvider"
        20 => "Illuminate\Validation\ValidationServiceProvider"
        21 => "Illuminate\View\ViewServiceProvider"
      ]
    }
    1 => array:5 [
      0 => "BeyondCode\DumpServer\DumpServerServiceProvider"
      1 => "Fideloper\Proxy\TrustedProxyServiceProvider"
      2 => "Laravel\Tinker\TinkerServiceProvider"
      3 => "Carbon\Laravel\ServiceProvider"
      4 => "NunoMaduro\Collision\Adapters\Laravel\CollisionServiceProvider"
    ]
    2 => Illuminate\Support\Collection {#31
      #items: array:4 [
        22 => "App\Providers\AppServiceProvider"
        23 => "App\Providers\AuthServiceProvider"
        24 => "App\Providers\EventServiceProvider"
        25 => "App\Providers\RouteServiceProvider"
      ]
    }
  ]
}

最后一句代码

(new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath())) 
                     ->load($providers->collapse()->toArray()); 

第一步是将加载缓存好的 bootstrap/cache/services.php

/**
* Get the path to the cached services.php file.
*
* @return string
*/
public function getCachedServicesPath()
{
return $this->bootstrapPath().'/cache/services.php';
}

其实就是执行 ProviderRepository::load() $providers-> collapse() 得到的数组 (这个数组是前面 splice 拆分后再并入的)。

/**
* Register the application service providers.
*
* @param array $providers
* @return void
*/
public function load(array $providers)
{
$manifest = $this->loadManifest();
// First we will load the service manifest, which contains information on all
// service providers registered with the application and which services it
// provides. This is used to know which services are "deferred" loaders.
if ($this->shouldRecompile($manifest, $providers)) {
$manifest = $this->compileManifest($providers);
}
// Next, we will register events to load the providers for each of the events
// that it has requested. This allows the service provider to defer itself
// while still getting automatically loaded when a certain event occurs.
foreach ($manifest['when'] as $provider => $events) {
$this->registerLoadEvents($provider, $events);
}
// We will go ahead and register all of the eagerly loaded providers with the
// application so their services can be registered with the application as
// a provided service. Then we will set the deferred service list on it.
foreach ($manifest['eager'] as $provider) {
$this->app->register($provider);
}
$this->app->addDeferredServices($manifest['deferred']);
}

这句 loadManifest 即为加载前面传入的 bootstrap/cache/services.php

/**
* Load the service provider manifest JSON file.
*
* @return array|null
*/
public function loadManifest()
{
// The service manifest is a file containing a JSON representation of every
// service provided by the application and whether its provider is using
// deferred loading or should be eagerly loaded on each request to us.
if ($this->files->exists($this->manifestPath)) {
$manifest = $this->files->getRequire($this->manifestPath);
if ($manifest) {
return array_merge(['when' => []], $manifest);
}
}
}

第60行调用到的的 shouldRecompile 逻辑为判断是否 provider 不符合,代码

/**
* Determine if the manifest should be compiled.
*
* @param array $manifest
* @param array $providers
* @return bool
*/
public function shouldRecompile($manifest, $providers)
{
return is_null($manifest) || $manifest['providers'] != $providers;
}

如果需要重新编译此服务提供者缓存的 bootstrap/cache/services.php ,把数据 $manifest 提取到 bootstrap/cache/services.php ,具体过程为:

/**
* Compile the application service manifest file.
*
* @param array $providers
* @return array
*/
protected function compileManifest($providers)
{
// The service manifest should contain a list of all of the providers for
// the application so we can compare it on each request to the service
// and determine if the manifest should be recompiled or is current.
$manifest = $this->freshManifest($providers);
foreach ($providers as $provider) {
$instance = $this->createProvider($provider);
// When recompiling the service manifest, we will spin through each of the
// providers and check if it's a deferred provider or not. If so we'll
// add it's provided services to the manifest and note the provider.
if ($instance->isDeferred()) {
foreach ($instance->provides() as $service) {
$manifest['deferred'][$service] = $provider;
}
$manifest['when'][$provider] = $instance->when();
}
// If the service providers are not deferred, we will simply add it to an
// array of eagerly loaded providers that will get registered on every
// request to this application instead of "lazy" loading every time.
else {
$manifest['eager'][] = $provider;
}
}
return $this->writeManifest($manifest);
}

/**
* Write the service manifest file to disk.
*
* @param array $manifest
* @return array
*
* @throws \Exception
*/
public function writeManifest($manifest)
{
if (! is_writable(dirname($this->manifestPath))) {
throw new Exception('The bootstrap/cache directory must be present and writable.');
}
$this->files->put(
$this->manifestPath, '<?php return '.var_export($manifest, true).';'
);
return array_merge(['when' => []], $manifest);
}

到这时,ProviderRepository::load() 的服务提供者缓存的判断和执行流程就结束了。我们回到 ProviderRepository::load() 的后面流程,接下来就是

// Next, we will register events to load the providers for each of the events
// that it has requested. This allows the service provider to defer itself
// while still getting automatically loaded when a certain event occurs.
foreach ($manifest['when'] as $provider => $events) {
$this->registerLoadEvents($provider, $events);
}

没错,触发 registerLoadEvents 方法了。感谢 laravel 代码做到了足够语义化,我们猜到了这里就是触发服务提供者注册事件的。具体流程为:

/**
* Register the load events for the given provider.
*
* @param string $provider
* @param array $events
* @return void
*/
protected function registerLoadEvents($provider, array $events)
{
if (count($events) < 1) {
return;
}
$this->app->make('events')->listen($events, function () use ($provider) {
$this->app->register($provider);
});
}

  • 若需关注更多关于 Laravel 事件触发机制,请关注 [TODO]

再继续后面,就是 ProviderRepository::load() 触发容器的 register 的流程了

// We will go ahead and register all of the eagerly loaded providers with the
// application so their services can be registered with the application as
// a provided service. Then we will set the deferred service list on it.
foreach ($manifest['eager'] as $provider) {
$this->app->register($provider);
}

Illuminate\Foundation\Container\Appplication 容器的 register 方法代码为

/**
* Register a service provider with the application.
*
* @param \Illuminate\Support\ServiceProvider|string $provider
* @param bool $force
* @return \Illuminate\Support\ServiceProvider
*/
public function register($provider, $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();
}
// If there are bindings / singletons set as properties on the provider we
// will spin through them and register them with the application, which
// serves as a convenience layer while registering a lot of bindings.
if (property_exists($provider, 'bindings')) {
foreach ($provider->bindings as $key => $value) {
$this->bind($key, $value);
}
}
if (property_exists($provider, 'singletons')) {
foreach ($provider->singletons as $key => $value) {
$this->singleton($key, $value);
}
}
$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;
}

步骤

  1. 先尝试取 getProvider 这个服务,如果能取到证明注册过了,除非要求强硬方式 $force 就不再注册
  2. 解析出 $provier 为具体的 ServiceProvider 对象
  3. 判断 $provider 有无 register 方法,有则运行
  4. 判断 $provider有无定义 $bindings 属性,若有,则依次 $key$value 为参,进入 Illuminate\Foundation\Container\Appplication::bind($key, $value) 执行
  5. 判断 $provider 有无定义 $singletons 属性,若有,则依次 $key$value 为参,进入 Illuminate\Foundation\Container\Appplication::singleton($key, $value) 执行
  6. 标记这个服务提供者已被注册过了 Illuminate\Foundation\Container\Appplication::$loadedProviders[ServiceProvider::class] = true
  7. 如果服务提供者已经被启动过了 ($booted == true), 调用 bootProvider($provider)

12. Facade 机制

总结: Facade 就是一个标记便捷的调用容器盛放对象的方法的设计。

一般的,Facade 类都继承于 Illuminate\Support\Facades\Facade

比如 Illuminate\Support\Facades\Gate 这个门面:

<?php
namespace Illuminate\Support\Facades;

use Illuminate\Contracts\Auth\Access\Gate as GateContract;

class Gate extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return GateContract::class;
    }
}

代码中我们只需要留意 getFacadeAccessor 方法是在 Illuminate\Support\Facades\Gate 中定义的就好了。
我们来分析 Illuminate\Support\Facades\Facade

魔术方法 part 1

首先我们来看末尾的 __callStatic 方法:

/**
* Handle dynamic, static calls to the object.
*
* @param string $method
* @param array $args
* @return mixed
*
* @throws \RuntimeException
*/
public static function __callStatic($method, $args)
{
$instance = static::getFacadeRoot();
if (! $instance) {
throw new RuntimeException('A facade root has not been set.');
}
return $instance->$method(...$args);
}

还记得 Facade 类我们是怎么调用的吗?

静态调用!比如 Gate::allows()

因为在 Facade 类中定义了 __callStatic 魔术方法,所以只要静态调用了里面没有提供的方法,都会走到魔术方法里面。
这个魔术方法内部有一个取出实例的逻辑:

$instance = static::getFacadeRoot();

代码位于

/**
* Get the root object behind the facade.
*
* @return mixed
*/
public static function getFacadeRoot()
{
return static::resolveFacadeInstance(static::getFacadeAccessor());
}

前面,我们留意了 static::getFacadeAccessor() 是位于 Illuminate\Support\Facades\Gate 中的,而 static::getFacadeAccessor() 运行后作为参数给了 static::resolveFacadeInstance(),后者实现为

/**
* Resolve the facade root instance from the container.
*
* @param string|object $name
* @return mixed
*/
protected static function resolveFacadeInstance($name)
{
if (is_object($name)) {
return $name;
}
if (isset(static::$resolvedInstance[$name])) {
return static::$resolvedInstance[$name];
}
return static::$resolvedInstance[$name] = static::$app[$name];
}

static::$app 就是容器对象

/**
* The application instance being facaded.
*
* @var \Illuminate\Contracts\Foundation\Application
*/
protected static $app;

这句

return static::$resolvedInstance[$name] = static::$app[$name];

尝试从容器对象 数组取 操作,就是调用了 make
public function offsetGet($key)
{
return $this->make($key);
}

就是从容器取出对象。

总结 getFacadeRoot 的逻辑大概为

  1. 判断 static::getFacadeAccessor() 返回是否对象,如果对象则直接使用。
  2. Facade::$resolvedInstance 数组中尝试根据 $name 取出对象,避免多次调用时,每次都取。
  3. Illuminate\Foundation\Application 容器取出对象。

魔术方法 part 2

拿到 $instance 后,先验证 $instance 是否为空。如果是就报错 A facade root has not been set.

if (! $instance) {
throw new RuntimeException('A facade root has not been set.');
}

所以如果遇到 A facade root has not been set. 这个错误,一般都是 a. 相应的服务提供者未注册; b. 对象绑定到容器的别名跟 static::getFacadeAccessor() 返回不一致。

最后,直接把方法和参数穿透到背后的 $instance 去执行。

Laravel 有很多 facade 门面,背后穿透的不只一个类(譬如 DBViewSession),这是怎么回事呢?
请看 15. Laravel 神奇的 Manager 类

01. HTTP 入口解析

HTTP 入口解析

public/index.php 入口

public/index.php 中第 24 行

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

逻辑为,引入 ../vendor/autoload.php,即 composer 产生的 vendor/autoload.php

接着第 38 行

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

这里面做的事情不少,具体的我们看 bootstrap/app.php 的代码。

bootstrap/app.php 实例化容器

在第 [14-16行]

$app = new Illuminate\Foundation\Application(
realpath(__DIR__.'/../')
);

这是 Laravel 框架内为数不多的直接 new 出来的对象,也就是框架的应用对象,
此对象继承于 Illuminate\Container\Container (容器),
所以, Laravel 框架中的容器对象一般就是指的 Illuminate\Foundation\Application 对象(因在实际运行过程中,不会直接 new Illuminate\Container\Container 的)。

在第 [29-42行]

$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是 public/index.php 中,用 Illuminate\Contracts\Http\Kernel 能注入后取出 App\Http\Kernel 的关键。

关于 singleton() 的解析,请见 10. 容器的 singleton 和 bind 的实现

第 55行

return $app;

在前面 public/index.php 中的

$app = require_once __DIR__.'/../bootstrap/app.php';
拿到了 return$app

public/index.php 构建 + 处理

public/index.php 第 52 行

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

这里调用了 Illuminate\Foundation\Application 的 make 方法(具体解析请见本篇结尾的链接),获得了 http 处理器 ( $kernel )

然后第 54-56 行

laravel/public/index.php

Lines 54 to 56 in 7028b17

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

先用 Illuminate\Http\Request::capture() 抓出一个 Illuminate\Http\Request HTTP请求对象

Request::capture 请求捕获

Illuminate\Http\Request::capture() 的代码为

public static function capture()
{
static::enableHttpMethodParameterOverride();
return static::createFromBase(SymfonyRequest::createFromGlobals());
}

其中调用了 Symfony\Component\HttpFoundation\Request::createFromGlobals()

public static function createFromGlobals()
{
$request = self::createRequestFromFactory($_GET, $_POST, array(), $_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')), array('PUT', 'DELETE', 'PATCH'))
) {
parse_str($request->getContent(), $data);
$request->request = new ParameterBag($data);
}
return $request;
}

其作用是讲 HTTP 请求过来的参数,按照 key => value 格式生成 Symfony\Component\HttpFoundation\ParameterBag 对象。
然后将包含 ParameterBag$request 对象交给 Illuminate\Http\Request::createFromBase()
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;
}

(new static)->duplicate() 最终执行了

public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null)
{
$dup = clone $this;
if (null !== $query) {
$dup->query = new ParameterBag($query);
}
if (null !== $request) {
$dup->request = new ParameterBag($request);
}
if (null !== $attributes) {
$dup->attributes = new ParameterBag($attributes);
}
if (null !== $cookies) {
$dup->cookies = new ParameterBag($cookies);
}
if (null !== $files) {
$dup->files = new FileBag($files);
}
if (null !== $server) {
$dup->server = new ServerBag($server);
$dup->headers = new HeaderBag($dup->server->getHeaders());
}
$dup->languages = null;
$dup->charsets = null;
$dup->encodings = null;
$dup->acceptableContentTypes = null;
$dup->pathInfo = null;
$dup->requestUri = null;
$dup->baseUrl = null;
$dup->basePath = null;
$dup->method = null;
$dup->format = null;
if (!$dup->get('_format') && $this->get('_format')) {
$dup->attributes->set('_format', $this->get('_format'));
}
if (!$dup->getRequestFormat(null)) {
$dup->setRequestFormat($this->getRequestFormat(null));
}
return $dup;
}

其将重要的 http 请求头、请求参数、请求文件、cookie 等信息赋给相应属性。

在后面的

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

Request::getInputSource() 的代码是
protected function getInputSource()
{
if ($this->isJson()) {
return $this->json();
}
return in_array($this->getRealMethod(), ['GET', 'HEAD']) ? $this->query : $this->request;
}

作用是调用 $request->input() 之时

GET、HEAD 请求,能取到 GET 参数,
POST 请求,能取到 POST 参数。

至此,Request::capture() 就执行完了。

public/index.php 运行逻辑

然后交给 App\Http\Kernelhandle 方法进行处理,获得 $response (Illuminate\Http\Request 对象)。

Laravel 框架的设计绝大可扩展部分(如服务提供者、中间件)都是在这个阶段处理和触发的,
所以我单独把 02. HTTP Kernel Handle 解析 拆出去了。

最后,在第 58-60行

laravel/public/index.php

Lines 58 to 60 in 7028b17

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

调用 Illuminate\Http\Response 的 send 方法,将响应的状态码/头/内容返回给客户端(浏览器),具体过程可见本篇末尾的引用。


02. HTTP Kernel Handle 解析

Kernel Handle

App\Http\Kernel 继承自 Illuminate\Foundation\Http\Kernel 类,所以本文章的分析主要集中在 app/Http/Kernel.phpvendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php 两个类中

__construct 解析

因为 App\Http\Kernel 没有 __construct 方法,所以穿透到了 Illuminate\Foundation\Http\Kernel 的 __construct:

/**
* Create a new HTTP kernel instance.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @param \Illuminate\Routing\Router $router
* @return void
*/
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);
}
}

首先是此方法传入参数的 __construct(Application $app, Router $router) 声明,这里恰好使用了 Laravel 容器的依赖注入(Dependency Injection,Inversion of Control的一种)设计。具体的在本篇不讲述,可参见本篇末尾的单独分析的链接的文章。

执行到 __construct 时, Illuminate\Foundation\Application 容器会将 Illuminate\Foundation\Application(即应用容器自身)和 Illuminate\Routing\Router 注入到方法内。然后逻辑代码将两个对象赋给 App\Http\Kernel$this 属性中。

然后将 Illuminate\Foundation\Http\Kernel 的 $middlewarePriority 属性

/**
* The priority-sorted list of middleware.
*
* Forces the listed middleware to always be in the given order.
*
* @var array
*/
protected $middlewarePriority = [
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Auth\Middleware\Authenticate::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];

赋值给 Illuminate\Routing\Router 的 $middlewarePriority 属性

根据其注释,此属性是强制对 middleward 中间件执行顺序进行排序的作用。

在后面将 App\Http\Kernel 中声明的 $middlewareGroup

/**
* 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',
],
];

Illuminate\Foundation\Http\Kernel 的 96-98行,调用 Illuminate\Routing\Router 的 middlewareGroup 方法,存到 $router 的 $middlewareGroup 中
/**
* Register a group of middleware.
*
* @param string $name
* @param array $middleware
* @return $this
*/
public function middlewareGroup($name, array $middleware)
{
$this->middlewareGroups[$name] = $middleware;
return $this;
}

紧接着,将

/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* @var array
*/
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,
];

的中间件,调用 Illuminate\Routing\Router 的 aliasMiddleware 的方法,绑定到 $router 的 $middleware 中
/**
* Register a short-hand name for a middleware.
*
* @param string $name
* @param string $class
* @return $this
*/
public function aliasMiddleware($name, $class)
{
$this->middleware[$name] = $class;
return $this;
}

至此, Kernel::__construct() 解析完毕。

handle 解析

/**
* Handle an incoming HTTP request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function handle($request)
{
try {
$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;
}

调用到 Symfony\Component\HttpFoundation\Request 的 enableHttpMethodParameterOverride 方法

Illuminate\Http\Request 继承自 Symfony\Component\HttpFoundation\Request
并且 Illuminate\Http\Request 未覆盖 enableHttpMethodParameterOverride

/**
* Enables support for the _method request parameter to determine the intended HTTP method.
*
* Be warned that enabling this feature might lead to CSRF issues in your code.
* Check that you are using CSRF tokens when required.
* If the HTTP method parameter override is enabled, an html-form with method "POST" can be altered
* and used to send a "PUT" or "DELETE" request via the _method request parameter.
* If these methods are not protected against CSRF, this presents a possible vulnerability.
*
* The HTTP method can only be overridden when the real HTTP method is POST.
*/
public static function enableHttpMethodParameterOverride()
{
self::$httpMethodParameterOverride = true;
}

然后,调用 Illuminate\Foundation\Http\Kernel 的 sendRequestThroughRouter

/**
* Send the given request through the middleware / router.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
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());
}

通过第142行,将 $request 注入进 Illuminate\Foundation\Application 容器。

144行,将门面类中的 request 数据清理掉

/**
* Clear a resolved facade instance.
*
* @param string $name
* @return void
*/
public static function clearResolvedInstance($name)
{
unset(static::$resolvedInstance[$name]);
}

Bootstrap 解析

然后146行,调用 Illuminate\Foundation\Http\Kernel 的 bootstrap 方法

/**
* Bootstrap the application for HTTP requests.
*
* @return void
*/
public function bootstrap()
{
if (! $this->app->hasBeenBootstrapped()) {
$this->app->bootstrapWith($this->bootstrappers());
}
}

第161执行到容器的 hasBeenBootstrapped 方法

/**
* Determine if the application has been bootstrapped before.
*
* @return bool
*/
public function hasBeenBootstrapped()
{
return $this->hasBeenBootstrapped;
}

第162实际得到这个数组

/**
* The bootstrap classes for the application.
*
* @var array
*/
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,
];

然后将数组做为参数,执行容器的 bootstrapWith 方法
/**
* Run the given array of bootstrap classes.
*
* @param array $bootstrappers
* @return void
*/
public function bootstrapWith(array $bootstrappers)
{
$this->hasBeenBootstrapped = true;
foreach ($bootstrappers as $bootstrapper) {
$this['events']->fire('bootstrapping: '.$bootstrapper, [$this]);
$this->make($bootstrapper)->bootstrap($this);
$this['events']->fire('bootstrapped: '.$bootstrapper, [$this]);
}
}

特别留意206行的 make 调用

关于容器 make 方法的细节
请查阅 10. 容器的 singleton 和 bind 的实现 的 “揭开 Container::make() 神秘的面纱” 段落

/**
* Resolve the given type from the container.
*
* (Overriding Container::make)
*
* @param string $abstract
* @param array $parameters
* @return mixed
*/
public function make($abstract, array $parameters = [])
{
$abstract = $this->getAlias($abstract);
if (isset($this->deferredServices[$abstract]) && ! isset($this->instances[$abstract])) {
$this->loadDeferredProvider($abstract);
}
return parent::make($abstract, $parameters);
}

关于 loadDeferredProvider 的逻辑会最终执行到
/**
* Register a deferred provider and service.
*
* @param string $provider
* @param string|null $service
* @return void
*/
public function registerDeferredProvider($provider, $service = null)
{
// Once the provider that provides the deferred service has been registered we
// will remove it from our local list of the deferred services with related
// providers so that this container does not try to resolve it out again.
if ($service) {
unset($this->deferredServices[$service]);
}
$this->register($instance = new $provider($this));
if (! $this->booted) {
$this->booting(function () use ($instance) {
$this->bootProvider($instance);
});
}
}

第710行的 booting 方法,是登记一个闭包 (并不会马上执行这个闭包), 然后这个服务提供者在 boot() 阶段的 $this->fireAppCallbacks($this->bootingCallbacks) 才会真正被创建。 关联阅读请见 04. ServiceProvider Boot 解析

然后结果就是依次执行

  • \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables
  • \Illuminate\Foundation\Bootstrap\LoadConfiguration
  • \Illuminate\Foundation\Bootstrap\HandleExceptions
  • \Illuminate\Foundation\Bootstrap\RegisterFacades
  • \Illuminate\Foundation\Bootstrap\RegisterProviders
  • \Illuminate\Foundation\Bootstrap\BootProviders

这些 Bootstrap 类的 bootstrap 方法

/**
* Bootstrap the given application.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function bootstrap(Application $app)
{
if ($app->configurationIsCached()) {
return;
}
$this->checkForSpecificEnvironmentFile($app);
try {
(new Dotenv($app->environmentPath(), $app->environmentFile()))->load();
} catch (InvalidPathException $e) {
//
} catch (InvalidFileException $e) {
echo 'The environment file is invalid: '.$e->getMessage();
die(1);
}
}

/**
* Bootstrap the given application.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function bootstrap(Application $app)
{
$items = [];
// First we will see if we have a cache configuration file. If we do, we'll load
// the configuration items from that file so that it is very quick. Otherwise
// we will need to spin through every configuration file and load them all.
if (file_exists($cached = $app->getCachedConfigPath())) {
$items = require $cached;
$loadedFromCache = true;
}
// Next we will spin through all of the configuration files in the configuration
// directory and load each one into the repository. This will make all of the
// options available to the developer for use in various parts of this app.
$app->instance('config', $config = new Repository($items));
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');
}

/**
* Bootstrap the given application.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function bootstrap(Application $app)
{
$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');
}
}

/**
* Bootstrap the given application.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function bootstrap(Application $app)
{
Facade::clearResolvedInstances();
Facade::setFacadeApplication($app);
AliasLoader::getInstance(array_merge(
$app->make('config')->get('app.aliases', []),
$app->make(PackageManifest::class)->aliases()
))->register();
}

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

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

上面列出的清单中,分别作用为

作用
\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables 加载环境变量
\Illuminate\Foundation\Bootstrap\LoadConfiguration 加载config
\Illuminate\Foundation\Bootstrap\HandleExceptions 错误处理者
\Illuminate\Foundation\Bootstrap\RegisterFacades 注册门面类
\Illuminate\Foundation\Bootstrap\RegisterProviders 注册服务提供者
\Illuminate\Foundation\Bootstrap\BootProviders 启动服务提供者

其中最后两项在 laravel 请求的生命周期中是至关重要的,我们将在后续文章中重点讲解。

bootstrap 阶段结束后,Kernel::sendRequestThroughRouter 后面带 pipeline 关键字的代码就是管道。

关于管道请查阅 05. Pipeline 解析

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

在进入管道前, 调用了 dispatchToRouter 返回一个闭包对象

/**
* Get the route dispatcher callback.
*
* @return \Closure
*/
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 \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
*/
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 mixed
*/
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->current = $route = $this->routes->match($request);
$this->container->instance(Route::class, $route);
return $route;
}
/**
* Return the response for the given route.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Routing\Route $route
* @return mixed
*/
protected function runRoute(Request $request, Route $route)
{
$request->setRouteResolver(function () use ($route) {
return $route;
});
$this->events->dispatch(new Events\RouteMatched($route, $request));
return $this->prepareResponse($request,
$this->runRouteWithinStack($route, $request)
);
}
/**
* Run the given route within a Stack "onion" instance.
*
* @param \Illuminate\Routing\Route $route
* @param \Illuminate\Http\Request $request
* @return mixed
*/
protected function runRouteWithinStack(Route $route, Request $request)
{
$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(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run()
);
});
}

如果一路抽丝剥茧,我们便能找到 Router 调用 controller 的逻辑了。 请见06. RouteServiceProvider 详解 最后段落。

04. ServiceProvider Boot 解析

随着前面 《02. Kernel Handle解析》 和 《03. ServiceProvider Register 解析》 的结束,我们接下来要分析的便是 启动服务提供者 这个步骤。

代码

<?php
namespace Illuminate\Foundation\Bootstrap;
use Illuminate\Contracts\Foundation\Application;
class BootProviders
{
/**
* Bootstrap the given application.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function bootstrap(Application $app)
{
$app->boot();
}
}

ServiceProvider Register 一样的,在 BootProviders 中也是调用了 Illuminate\Foundation\Application::boot()

/**
* Boot the application's service providers.
*
* @return void
*/
public function boot()
{
if ($this->booted) {
return;
}
// Once the application has booted we will also fire some "booted" callbacks
// for any listeners that need to do work after this initial booting gets
// finished. This is useful when ordering the boot-up processes we run.
$this->fireAppCallbacks($this->bootingCallbacks);
array_walk($this->serviceProviders, function ($p) {
$this->bootProvider($p);
});
$this->booted = true;
$this->fireAppCallbacks($this->bootedCallbacks);
}

首先判断是否 boot 过。

然后通过触发 $bootingCallbacks 钩子

/**
* Call the booting callbacks for the application.
*
* @param array $callbacks
* @return void
*/
protected function fireAppCallbacks(array $callbacks)
{
foreach ($callbacks as $callback) {
call_user_func($callback, $this);
}
}

$bootingCallbacks 是来自 02. HTTP Kernel Handle解析 登记的闭包,主要是服务于声明了 $defer = true 的服务提供者。

然后依次遍历 $this->serviceProviders 执行 Illuminate\Foundation\Application::bootProvider()

/**
* Boot the given service provider.
*
* @param \Illuminate\Support\ServiceProvider $provider
* @return mixed
*/
protected function bootProvider(ServiceProvider $provider)
{
if (method_exists($provider, 'boot')) {
return $this->call([$provider, 'boot']);
}
}

其实就是运行一遍 ServiceProvider 的 boot 方法

接着将 Illuminate\Foundation\Application::$boot 设置为 true

最后触发 bootedCallbacks 钩子

13. Macroable 解析

还记得那个《高速修车》的陈年老梗吗:

公司的业务呢,就像跑在高速路上的车,车不能停,但是新需求和修bug也不能停。

这里要跟大家扒代码的 Macroable 呢,有点像上面这个悖论的出路;但其实也不是,因为他没有实现 且不停,只实现了 且不停。请原谅我这尴尬的幽默。


从陈年教程入手

网上很多教程都是提及了,Laravel 绝大部分类,都可以扩展方法。比如这个例子:

use Illuminate\Support\Collection;

Collection::macro('bcsum', function () {
    $sum = 0;
    foreach ($this as $item) {
        $sum = bcadd($sum, $item, 2);
    }
    return $sum;
});

dump((new Collection([1,2,3,4]))->bcsum()); // it says "10.00"

我们可以看到,在不改变代码的前提下,我们在使用的过程中给 Illuminate\Support\Collection 扩充了 bcsum 的功能。就如同在其中加入了如下代码:

<?php

class Collection ...
{
    public function bcsum()
    {
        $sum = 0;
        foreach ($this as $item) {
            $sum = bcadd($sum, $item, 2);
        }
        return $sum;
    }
}

代码解析

class Collection implements ArrayAccess, Arrayable, Countable, IteratorAggregate, Jsonable, JsonSerializable
{
use Macroable;

我们看到,Illuminate\Support\Collection 使用了 Illuminate\Support\Traits\Macroable 这个 trait

macro 的代码

作用是将闭包存进static::$macros

/**
* Register a custom macro.
*
* @param string $name
* @param object|callable $macro
*
* @return void
*/
public static function macro($name, $macro)
{
static::$macros[$name] = $macro;
}

魔术方法

在调用我们的方法时,按名字匹配出来,参数传入触发。

/**
* Dynamically handle calls to the class.
*
* @param string $method
* @param array $parameters
* @return mixed
*
* @throws \BadMethodCallException
*/
public static function __callStatic($method, $parameters)
{
if (! static::hasMacro($method)) {
throw new BadMethodCallException(sprintf(
'Method %s::%s does not exist.', static::class, $method
));
}
if (static::$macros[$method] instanceof Closure) {
return call_user_func_array(Closure::bind(static::$macros[$method], null, static::class), $parameters);
}
return call_user_func_array(static::$macros[$method], $parameters);
}
/**
* Dynamically handle calls to the class.
*
* @param string $method
* @param array $parameters
* @return mixed
*
* @throws \BadMethodCallException
*/
public function __call($method, $parameters)
{
if (! static::hasMacro($method)) {
throw new BadMethodCallException(sprintf(
'Method %s::%s does not exist.', static::class, $method
));
}
$macro = static::$macros[$method];
if ($macro instanceof Closure) {
return call_user_func_array($macro->bindTo($this, static::class), $parameters);
}
return call_user_func_array($macro, $parameters);
}

这里之所以定义了两个方法,__call__callStatic,是因为 laravel 很多类,部分可以静态调用,部分可以动态调用。macroable 如果要能被这两种类可用,那么就需要定义两个魔术方法。


经验分享

失灵的情况

一. 魔术一个魔术方法,不可行

我们现在知道了 Macroable 的神奇,但是其实Macroable 也有失灵的时候。举个例子:

<?php
class JoeDoe
{
    use \Illuminate\Support\Traits\Macroable;
}

JoeDoe::macro('__toString', function () {
    return '123';
});

echo new JoeDoe();

而运行时,其还是报了这个错:

PHP Recoverable fatal error:  Object of class JoeDoe could not be converted to string

但是如果我们在 JoeDoe 这个类中手工硬编码 __toString 这个方法却是可以运行的的;
为什么?

其实道理很简单,因为 echo JoeDoe 对象时候,php 内核检查其中有没有 __toString 这个方法时,就已经报错了,根本还来不及走到 __call 这个魔术方法。

08. SessionServiceProvider 详解

前言还没想好。

服务提供者部分

SessionServiceProvider 没有 boot 方法,其 register 方法为

public function register()
{
$this->registerSessionManager();
$this->registerSessionDriver();
$this->app->singleton(StartSession::class);
}

作用

下面,我们以 SESSION_DRIVER=file 继续分析
SESSION_DRIVERfile 时,会调用到 createFileDriver

protected function createFileDriver()
{
return $this->createNativeDriver();
}

createFileDriver 调用了 createNativeDriver
protected function createNativeDriver()
{
$lifetime = $this->app['config']['session.lifetime'];
return $this->buildSession(new FileSessionHandler(
$this->app['files'], $this->app['config']['session.files'], $lifetime
));
}

这里 $this->app['config']['session.files'] 得到的是

'files' => storage_path('framework/sessions'),

然后通过此处实例化 Illuminate\Session\FileSessionHandler

return $this->buildSession(new FileSessionHandler(

然后调用 buildSession 获得 \Illuminate\Session\Store 对象

protected function buildSession($handler)
{
if ($this->app['config']['session.encrypt']) {
return $this->buildEncryptedSession($handler);
}
return new Store($this->app['config']['session.cookie'], $handler);
}

session 功能 / 操作部分

读取 session

当触发 Session::start 时,

public function start()
{
$this->loadSession();
if (! $this->has('_token')) {
$this->regenerateToken();
}
return $this->started = true;
}

调用的 loadSession

protected function loadSession()
{
$this->attributes = array_merge($this->attributes, $this->readFromHandler());
}

调用的 readFromHandler

protected function readFromHandler()
{
if ($data = $this->handler->read($this->getId())) {
$data = @unserialize($this->prepareForUnserialize($data));
if ($data !== false && ! is_null($data) && is_array($data)) {
return $data;
}
}
return [];
}

这里的 read 方法,就是 FileSessionHandler::read

public function read($sessionId)
{
if ($this->files->isFile($path = $this->path.'/'.$sessionId)) {
if ($this->files->lastModified($path) >= Carbon::now()->subMinutes($this->minutes)->getTimestamp()) {
return $this->files->sharedGet($path);
}
}
return '';
}

执行到这里时, session 中的数据就被存放到了 Illuminate\Session\Store::$attributes 了。

而执行 Session::get 时,

public function get($key, $default = null)
{
return Arr::get($this->attributes, $key, $default);
}

正好就能从 Store::$attributes 中取出 session 的数据了。

写入 session

当我们使用 Illuminate\Support\Facades\Session 时,操作是调用到的 session,也就是 Illuminate\Session\SessionManager。但是在 Illuminate\Support\Manager__call 魔术方法,实现了将 session.driver 载入到 session 中被执行的能力。

public function __call($method, $parameters)
{
return $this->driver()->$method(...$parameters);
}

有兴趣的同学请阅读 15. Laravel 神奇的 Manager 类

假如我们执行 Session::put('key', 'value')
会穿透到

public function put($key, $value = null)
{
if (! is_array($key)) {
$key = [$key => $value];
}
foreach ($key as $arrayKey => $arrayValue) {
Arr::set($this->attributes, $arrayKey, $arrayValue);
}
}

当我们手动 Session::save 时,或者程序终止运行触发 register_shutdown_function 时,会触发

public function save()
{
$this->ageFlashData();
$this->handler->write($this->getId(), $this->prepareForStorage(
serialize($this->attributes)
));
$this->started = false;
}

也就是 FileSessionHandler::write

public function write($sessionId, $data)
{
$this->files->put($this->path.'/'.$sessionId, $data, true);
return true;
}

至此,写入 session 的流程走完。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.