laravel5.5框架解析系列文章属于对laravel5.5框架源码分析,如有需要,建议按顺序阅读该系列文章, 不定期更新,欢迎关注
掌握laravel应用的代码执行流程, 对解决项目构建过程中遇到的一些疑难杂症大有裨益.
index.php
作为一个单入口的应用, 想要了解执行流程当然是去看index.php咯
// 记录一下框架启动时间, 可以看一次请求花了多长时间来响应
define('LARAVEL_START', microtime(true));
// composer自动加载
require __DIR__.'/../vendor/autoload.php';
// 这个bootstrap文件里创建了一个Application实例
$app = require_once __DIR__.'/../bootstrap/app.php';
// 通过容器创建了一个http kernel
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
// Request类通过全局变量创建了一个Request实例,
// 通过调用kernel的handle方法, 就得到了一个response
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
// 把response内容发送到浏览器
$response->send();
// 执行一些耗时的后续工作
$kernel->terminate($request, $response);
好简单有木有:)
如何启动Application
创建应用实例
创建Application实例的bootstrap.php代码如下
// 传入项目目录,实例化Application, 即容器. Application继承自Container
$app = new Illuminate\Foundation\Application(
realpath(__DIR__.'/../')
);
// 绑定http kernel实现类, 单例模式
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
// 绑定 console kernel 实现类
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
// 绑定异常处理实现类
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
return $app;
好像没啥步骤啊, 为啥还把创建Application单独写一个文件, 不放在index.php里? 因为index.php是由cgi进程执行的,但是你可能需要在cli环境下运行Application哟, 比如测试, 和artisan. 所以就单独放在文件, 需要的地方再require.
你可能奇怪了怎么没有注册绑定那些service啊? 请继续往下看
Application 初始化
Application代码
public function __construct($basePath = null)
{
// 项目目录, 很多地方要用
if ($basePath) {
$this->setBasePath($basePath);
}
// 基本绑定
$this->registerBaseBindings();
// 基本service绑定
$this->registerBaseServiceProviders();
// 别名
$this->registerCoreContainerAliases();
}
protected function registerBaseBindings()
{
// 把实例存在类里边
static::setInstance($this);
// 把自己放到容器
$this->instance('app', $this);
// 把自己放到容器again, 并绑到Container的实现
$this->instance(Container::class, $this);
// PackageManifest,这个东西是laravel5.5新增的,
// 5.5 安装拓展包, 不需要手动配provider了(如果拓展包支持的话),
// 他会从拓展包的composer.json extra配置中读取.
// 个人认为其实可以通过composer插件的方式安装拓展包,
// 这样可以在安装阶段配置service provider, 而不是运行阶段
$this->instance(PackageManifest::class, new PackageManifest(
new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
));
}
// 加载基本的service provider, 启动模块
protected function registerBaseServiceProviders()
{
// 事件模块
$this->register(new EventServiceProvider($this));
// log模块
$this->register(new LogServiceProvider($this));
// 路由模块
$this->register(new RoutingServiceProvider($this));
}
// 这就把web组件都给绑定到实现类并注册了一个别名, 然后你就可以各种app('config'), $app['config'], $app->make('config')
public function registerCoreContainerAliases()
{
foreach ([
'app' => [\Illuminate\Foundation\Application::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class],
'auth' => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
'auth.driver' => [\Illuminate\Contracts\Auth\Guard::class],
'blade.compiler' => [\Illuminate\View\Compilers\BladeCompiler::class],
'cache' => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
'cache.store' => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class],
'config' => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\Repository::class],
'cookie' => [\Illuminate\Cookie\CookieJar::class, \Illuminate\Contracts\Cookie\Factory::class, \Illuminate\Contracts\Cookie\QueueingFactory::class],
'encrypter' => [\Illuminate\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\Encrypter::class],
'db' => [\Illuminate\Database\DatabaseManager::class],
'db.connection' => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
'events' => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class],
'files' => [\Illuminate\Filesystem\Filesystem::class],
'filesystem' => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class],
'filesystem.disk' => [\Illuminate\Contracts\Filesystem\Filesystem::class],
'filesystem.cloud' => [\Illuminate\Contracts\Filesystem\Cloud::class],
'hash' => [\Illuminate\Contracts\Hashing\Hasher::class],
'translator' => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class],
'log' => [\Illuminate\Log\Writer::class, \Illuminate\Contracts\Logging\Log::class, \Psr\Log\LoggerInterface::class],
'mailer' => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class],
'auth.password' => [\Illuminate\Auth\Passwords\PasswordBrokerManager::class, \Illuminate\Contracts\Auth\PasswordBrokerFactory::class],
'auth.password.broker' => [\Illuminate\Auth\Passwords\PasswordBroker::class, \Illuminate\Contracts\Auth\PasswordBroker::class],
'queue' => [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class],
'queue.connection' => [\Illuminate\Contracts\Queue\Queue::class],
'queue.failer' => [\Illuminate\Queue\Failed\FailedJobProviderInterface::class],
'redirect' => [\Illuminate\Routing\Redirector::class],
'redis' => [\Illuminate\Redis\RedisManager::class, \Illuminate\Contracts\Redis\Factory::class],
'request' => [\Illuminate\Http\Request::class, \Symfony\Component\HttpFoundation\Request::class],
'router' => [\Illuminate\Routing\Router::class, \Illuminate\Contracts\Routing\Registrar::class, \Illuminate\Contracts\Routing\BindingRegistrar::class],
'session' => [\Illuminate\Session\SessionManager::class],
'session.store' => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class],
'url' => [\Illuminate\Routing\UrlGenerator::class, \Illuminate\Contracts\Routing\UrlGenerator::class],
'validator' => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class],
'view' => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
] as $key => $aliases) {
foreach ($aliases as $alias) {
$this->alias($key, $alias);
}
}
}
Application 貌似启动完成, 看一下这个时候容器里有啥
在index.php里 执行
$app = require_once __DIR__.'/../bootstrap/app.php';
// ------------- 加入下面这些 -----------
$rf = new ReflectionClass(\Illuminate\Container\Container::class);
$p = $rf->getProperty('resolved');
$p->setAccessible(true);
dd($p->getValue($app));
浏览器输出结果
[]
哈哈, 啥都没有. 虽然只是注册了服务,并未resolve, 可是之前不是有注册service provider吗, 难道这个也不需要resolve?
实际上此时service provider都还没有被resolve并执行. 那么真正执行在哪儿呢, 实际上在http kernel的handle
流程里, 请看下节
handle($request)
一个request到底经历了怎样的千难万险,才得以历练出正确的response呢? 来一探究竟
Illuminate\Foundation\Http\Kernel
:
public function handle($request)
{
try {
// 开启请求method覆盖, 就是文档里提到的如何在不支持的浏览器里发送delete等请求
$request->enableHttpMethodParameterOverride();
// 把请求发送给路由, 得到response, 详情在下边
$response = $this->sendRequestThroughRouter($request);
} catch (Exception $e) { // Request->Response途中遇到异常, 在这进行处理
// 记录日志
$this->reportException($e);
// 生成异常相应, 以便发送到浏览器
$response = $this->renderException($request, $e);
} catch (Throwable $e) {
// 致命错误
$this->reportException($e = new FatalThrowableError($e));
$response = $this->renderException($request, $e);
}
// 触发相应事件
$this->app['events']->dispatch(
new Events\RequestHandled($request, $response)
);
return $response;
}
// 请求变响应
protected function sendRequestThroughRouter($request)
{
// request 保存到容器
$this->app->instance('request', $request);
// 门面缓存清除, 因为门面会从容器中取实例然后缓存,
// 刚刚刷新了容器中的Request, 为了让facade能更新实例, 就清楚缓存
Facade::clearResolvedInstance('request');
// 这个就是前面提到的会在handle流程里初始化Application的service provider
// 为什么Application 会放在这启动呢? 因为不同情况下Application需要的加载不同的基础service provider,
// 所以就没有放在Application中启动, 而是提供了bootstrapWith的公开方法,
// 供外部按需传入service provider 进行启动, 这里传入了下面这些provider
// 用于加载 .env 配置文件
// \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
// 加载config文件夹下配置
// \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
// 注册异常处理, laravel5.5 用whoops来渲染异常
// \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
// 注册门面服务
// \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
// laravel5.5 新功能, 注册通过composer.json来提供provider类的 provider
// \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
// register 完毕之后, 最后boot注册过的service provider
// \Illuminate\Foundation\Bootstrap\BootProviders::class,
$this->bootstrap();
// 最后通过pipeline, 把Request经过全局中间件, 发送到路由分发过程
// 后面的文章会有pipeline实现原理分析
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
下面是如何将Request发送到匹配路由 Illuminate\Routing\Router
public function dispatchToRoute(Request $request)
{
// 通过Request匹配到路由, 匹配不到直接抛异常, 而不是返回null.
// 是不是返回null ,在这里在抛 404 更合理呢?
$route = $this->findRoute($request);
// 把route绑到request上, 这样在其他地方, 你可以通过request 获取到匹配的路由
$request->setRouteResolver(function () use ($route) {
return $route;
});
// 触发路由匹配事件
$this->events->dispatch(new Events\RouteMatched($route, $request));
// 执行路由, 得到相应
$response = $this->runRouteWithinStack($route, $request);
// 把控制器或者路由级中间件返回的结果(可能是array,string, int 或其他类型), 转换成Response实例
return $this->prepareResponse($request, $response);
}
// router中执行路由的过程
protected function runRouteWithinStack(Route $route, Request $request)
{
// 是否跳过中间件, 特殊情况下, 或者测试时有可能需要跳过
$shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
$this->container->make('middleware.disable') === true;
//取出路由中间件
$middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
// 把Request过一遍路由中间件, 然后执行路由,
// 这里也调用了prepareResponse, 上面那也调用了, 为什么会调用2次? 有趣吧,哈哈.
// 这里的response其实是返回给全局中间件那里去了, 而全局中间件可能会根据response的内容,
// 决定不予发送给浏览器, 而是自己发送了一个其他响应, 比如返回一个数组[code=>500, msg=>'sth. went wrong'],
// 所以, 经过全局回来的response还要再prepare一遍. 且保证中间件的handle流程里$next()返回的是一个Response实例
return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run()
);
});
}
terminate()
pipeline管道是有始有终的, 从哪里进去, 就得从哪里出来, 不过大变活人, Request进去, Response出来了.所以相应最后到了http kernel 的handle
方法里,在index.php中,相应被发送到浏览器
最后执行terminal方法 Http\Kernel
public function terminate($request, $response)
{
// 注册的中间件如果有terminate调用terminate方法
// session 存盘就是在中间件terminate中完成的, 所以很多人在controller
// 中使用了dd()函数, 就发现session出问题了. 因为dd()会使程序直接退出,
// 这时候请使用dump()来输出变量
$this->terminateMiddleware($request, $response);
// Application的terminate, 他会调用通过terminating方法注册的回调
$this->app->terminate();
}