目录索引
阅读本文大约需要5分钟。
记一次莫名奇妙的未知d错误
人呀,难免犯错。
怕的不是承受结果,怕的是错得莫名其妙。
总结踩过的坑,以后才能走得更顺畅。
本文没啥技术含量,主要是记录和整理思路,适合比较闲的人看。
运行环境
d错误的出现
最近,接手一个php项目,基于Laravel开发。
其中踩坑很多,略过不表。
今日在修bug过程中,突然终端执行命令php artisan -v
时结果会多出一个诡异的 d
。简单描述就是在正常输出的Laravel Framework 5.5.x
前面多了一个空格和字母d。
bash# php artisan -v
dLaravel Framework 5.5.x
先经过一波操作之后,最后把目光放到程序代码逻辑上。
发现api返回json前面也会有 d
,简直无法描述自己的心情。
d{"status":"success","code":200,"data":["helloword!"],"message":""}
在解决过程中一度让人啼笑不已,因为很明显,这并不是一个严重的问题,但偏偏此问题要是无法解决,api和终端执行命令都会受到影响。
踩坑历程
异常最先出现在终端(当时还未请求api),加上之前曾经遇到过docker容器mysql终端无法输入中文,所以怀疑是终端编码导致的乱码。
所以最先想到的第一个解决思路就是,更改docker容器的终端编码。
docker exec -it php env LANG=C.UTF-8 bash
。
无果,担心未生效,随进入容器直接设置env,大致就是export LANG=C.UTF-8
。
第一次尝试之后,思考片刻,觉得还是借助搜索引擎和gpt,然而还是无果。
gpt提供一些思路:
- 让博主检查执行命令后是否多打一个
d
- 重新检查
php版本
- 让更新
composer
what?最后还是选择不敢相信…
因为一般情况,我用docker配的环境,一开始没出现这个问题,那么大概率正常修bug也不会出现。
又一个途径解决失败,所以准备暂缓。
谁知道,随便打开一个api测试,又发现了 d
错误。
无奈,只好继续排查。
第二个思路就是莽撞的想要排查nginx,因为api里面有 d
,但很快又被自我否定,因为命令行可和nginx没啥关系。
第三个思路,也就是最后一个思路,怀疑问题出现在代码逻辑,只好逐层debug。
当然,在博主最后解决之后,其实还想到一个候补思路,不过不太成熟。
至少对博主的情况不太成熟,简单来说就是有git就快了,只需要git diff
查看更改文件排查即可,很遗憾,这个项目情况特殊,最新代码是从线上打包下来,还未整理到git仓库。
至于全局搜索,也曾尝试,但是就这字符,确实在项目里太常见了,而且项目文件确实太多了。
解决思路
综上所述,博主最终采用逐层debug的模式来搞定。
既然决定要逐层debug,所以最起码得先了解laravel框架内部的运行流程,否则到时候有些小伙伴不明白博主的排查顺序为什么要这么选。
laravel框架内部的运行流程(网上搬运)
1、载入 Composer 的自动加载文件,自动加载的真正实现是通过/vendor/autoload.php
实现的
2、加载/ bootstrap /app.php
文件,实例化服务容器,存在$app
3、向服务容器里绑定了三个服务:HTTP、Console、Excepiton
4、make方法取出http,存到$request
变量中($request
变量贯穿始终)
5、按照app配置文件顺序register所有的服务提供者
6、按照注册顺序执行所有服务提供者的boot方法
7、将请求发送到route
8、执行中间件
9、发送请求
10、返回响应
基于大致这个流程,博主的最先排查bootstrap/app.php
文件。
<?php
$app = new Illuminate\Foundation\Application(
realpath(__DIR__.'/../')
);
$env = $app->detectEnvironment(function() {
return getenv('APP_ENV') ?: 'dev';
});
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
return $app;
博主首先选择在return之前var_dump('2222')
,随便打印无所谓。
api返回结果如下
string(4) "2222"
d{"status":"success","code":200,"data":["helloword!"],"message":""}
所以把思路瞄向根目录下public/index.php
,因为输出结果说明打印太靠前了,得往流程后面走。
入口文件代码
<?php
define('LARAVEL_START', microtime(true));
require __DIR__.'/../vendor/autoload.php';
$app = require_once __DIR__.'/../bootstrap/app.php';
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
var_dump("2222");
$response->send();
$kernel->terminate($request, $response);
api返回结果如下:
dstring(4) "2222"
{"status":"success","code":200,"data":["helloword!"],"message":""}
反正定位到是$kernel->handle
执行之后才出现的,所以我又看了下流程,很自然跟到了vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
。
关键代码如下:
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;
}
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());
}
public function bootstrap()
{
if (! $this->app->hasBeenBootstrapped()) {
$this->app->bootstrapWith($this->bootstrappers());
}
}
排查到关键方法$response = $this->sendRequestThroughRouter($request);
,
然后又往下跟到了bootstrap方法
。
从Kernel->bootstrap
方法又跟到了vendor/laravel/framework/src/Illuminate/Foundation/Application.php
下的bootstrapWith
方法。
关键代码如下
public function bootstrapWith(array $bootstrappers)
{
$this->hasBeenBootstrapped = true;
// var_dump($bootstrappers);
foreach ($bootstrappers as $bootstrapper) {
// var_dump('dddd2');
$this['events']->fire('bootstrapping: '.$bootstrapper, [$this]);
$this->make($bootstrapper)->bootstrap($this);
$this['events']->fire('bootstrapped: '.$bootstrapper, [$this]);
}
}
经过进一步排查,发现$bootstrapper
数组中的Illuminate\Foundation\Bootstrap\LoadConfiguration
很可疑。
数组打印结果如下
array(6) {
[0]=>
string(56) "Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables"
[1]=>
string(49) "Illuminate\Foundation\Bootstrap\LoadConfiguration"
[2]=>
string(48) "Illuminate\Foundation\Bootstrap\HandleExceptions"
[3]=>
string(47) "Illuminate\Foundation\Bootstrap\RegisterFacades"
[4]=>
string(49) "Illuminate\Foundation\Bootstrap\RegisterProviders"
[5]=>
string(45) "Illuminate\Foundation\Bootstrap\BootProviders"
}
在LoadConfiguration.php
中一顿排查,从bootstrap
方法排查到了loadConfigurationFiles
方法。
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);
}
// var_dump('dddd3');
// 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');
}
/**
* Load the configuration items from all of the files.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @param \Illuminate\Contracts\Config\Repository $repository
* @return void
* @throws \Exception
*/
protected function loadConfigurationFiles(Application $app, RepositoryContract $repository)
{
$files = $this->getConfigurationFiles($app);
if (! isset($files['app'])) {
throw new Exception('Unable to load the "app" configuration file.');
}
foreach ($files as $key => $path) {
// if($key === 'database'){
// var_dump(require $path);
// }
$repository->set($key, require $path);
}
}
到这里只要稍微有点基础的人都能看懂,loadConfigurationFiles
主要是加载配置文件。
但是博主当时去看了一下,反正先是将某自定义配置文件的json删除,后继续结合debug排查,还是有毒,又跟到了config/database.php
。
因为里面用了env,所以我排查了env文件
以及database.php
中的异常地方,比如去除了一些空格,或者有些只末尾看看是否存在异常符号。
一顿排查,觉得确实没发现啥问题,所以博主选择直接打印读取到的database.php
文件时,博主震惊了。
原来一顿操作猛如虎,一看结果想抽自己两个耳刮子。
因为在打印出来的结果上第一个就是d
错误。
darray(4) {
["default"]=> string(5) "mysql"
....
}
简单来说,就是database.php
文件上<?php
前多了几个字符,彻底为自己的大意花了很长时间买单。
d<?php
some code....
总结
原因未知,可能中间打开文件的时候,不小心误操作导致。
排查过程很浪费时间,但是理论上来说中途其实在定位到配置文件时,就可以仔细简单配置文件,至少可以节约30分钟以上。
但是当时太耿直,老以为会是什么代码内部逻辑问题,万万没想到,既然会是这种低级错误。
所以写下这篇文章,不为别的,就想为自己留个教训,仅此而已。
所属分类:
后端
文章标签:
#docker
#php
#laravel