记一次莫名奇妙的未知d错误

发表于2023年05月17日 20:24:14 • 阅读219

目录索引

阅读本文大约需要5分钟。


记一次莫名奇妙的未知d错误

人呀,难免犯错。

怕的不是承受结果,怕的是错得莫名其妙。

总结踩过的坑,以后才能走得更顺畅。

本文没啥技术含量,主要是记录和整理思路,适合比较闲的人看。

运行环境

  • php7
  • nginx
  • docker

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

文章标题:记一次莫名奇妙的未知d错误

文章作者:大古

文章链接:https://blog.8wkj.com/archives/remember-an-inexplicable-unknown-d-error.html

版权声明:本站原创内容均采用《署名 - 非商业性使用 - 相同方式共享 4.0 国际》进行许可,转载请注明出处。

温馨提示:请注意文章发表时间,某些文章所述内容可能具有时效性,文章描述可能已经失效。

添加新评论
暂未有人评论
快来抢沙发