从解决 bug 谈到学习

本文篇幅较长,可以先收藏再看,我相信认真看完本文的,都会产生思维的碰撞,所以欢迎留言。

起因

下午的时候,社区里的一位朋友,问我怎么解决某个 bug 的问题,其实我觉得表面上看起来解决一个 bug,但更多的是怎么去思考,怎么去学习的问题。

先上几张聊天截图(有删减)

file

file

file

debug

问题: 想去除 Laravel MongoDB 拓展包,已经在 config/app.php 删掉了 Jenssegers\Mongodb\MongodbServiceProvider::class,,依旧报错

这种情况其实经常会发生,解决思路一般先从这 3 步入手排除,如果不行,再考虑其他情况。

1、可能在 config/app.php 没有删掉对应的服务提供者
2、可能在你自己自定义的 ServiceProvider 中注册了第三方的服务提供者,没有删除或注释
3、可能你运行的时候编译缓存了(5.4 中移除了 optimize 命令全线OPcache ),没有执行php artisan clear-compiled 或者直接删掉 bootstrap\cache\services.php

其实从上述的可以延伸到几个知识点

拓展包的的服务提供者怎么注册,可以在哪些地方注册?

实际上无论上述的情况 1 还是情况 2 服务提供者归根到底都是有容器进行注册的

情况 1 在 config/app.php 中注册
例如通过情况 1 在 config/app.php 中的 providers中定义的那些服务提供者,启动流程如下

请求应用的时候
首先在 public/index.php 中触发 handle 方法

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

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

接着,在 Illuminate/Foundation/Http/Kernel.php 中初始化各种服务提供者

 /**
     * 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) {
        ...
    }

        /**
     * Send the given request through the middleware / router.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    protected function sendRequestThroughRouter($request)
    {
        ...
        $this->bootstrap();
        ...
    }

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

     /**
     * Get the bootstrap classes for the application.
     *
     * @return array
     */
    protected function bootstrappers()
    {
        return $this->bootstrappers;
    }

    /**
     * The bootstrap classes for the application.
     *
     * @var array
     */
    protected $bootstrappers = [
        ...
        \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
        ...
    ];

而在 Illuminate/Foundation/Bootstrap/RegisterProviders.php 中开始注册配置中的提供者,也就是情况 1 中的config/app.php里面的 providers

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

Illuminate/Foundation/Application.php 可以看到取的值是 $this->config['app.providers']

    /**
     * Register all of the configured providers.
     *
     * @return void
     */
    public function registerConfiguredProviders()
    {
        (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
                    ->load($this->config['app.providers']);
    }

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

而在 Illuminate/Foundation/ProviderRepository.php

调用 app 的 register方法进行注册,而这就是核心

/**
     * Create a new service repository instance.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @param  \Illuminate\Filesystem\Filesystem  $files
     * @param  string  $manifestPath
     * @return void
     */
    public function __construct(ApplicationContract $app, Filesystem $files, $manifestPath)
    {
        $this->app = $app;
        $this->files = $files;
        $this->manifestPath = $manifestPath;
    }

   /**
     * Register the application service providers.
     *
     * @param  array  $providers
     * @return void
     */
    public function load(array $providers)
    {
        $manifest = $this->loadManifest();

        // 加载编译的缓存文件,可以用来知道哪些提供者是延期加载的
        if ($this->shouldRecompile($manifest, $providers)) {
            $manifest = $this->compileManifest($providers);
        }
        // 接下来就是注册延期加载的提供者,当他符合条件,需要的时候,也会自动加载
        foreach ($manifest['when'] as $provider => $events) {
            $this->registerLoadEvents($provider, $events);
        }
        // 这里就是注册非延期加载的服务提供者,可以看到用到的还是 app 的 register方法。
        foreach ($manifest['eager'] as $provider) {
            $this->app->register($provider);
        }
        // 延期加载的就不在这里细说了
        $this->app->addDeferredServices($manifest['deferred']);
    }

            /**
     * Compile the application service manifest file.
     *
     * @param  array  $providers
     * @return array
     */
    protected function compileManifest($providers)
    {
        // 用来判断现在已有还是需要重新编译缓存文件
        $manifest = $this->freshManifest($providers);
        foreach ($providers as $provider) {
            $instance = $this->createProvider($provider);
            // 检查是否延迟加载的服务提供者
            if ($instance->isDeferred()) {
                foreach ($instance->provides() as $service) {
                    $manifest['deferred'][$service] = $provider;
                }
                $manifest['when'][$provider] = $instance->when();
            }
            // 如果不是延迟加载的就添加到缓存文件的 eager 数组中
            else {
                $manifest['eager'][] = $provider;
            }
        }
        return $this->writeManifest($manifest);
    }

          /**
     * Write the service manifest file to disk.
     *
     * @param  array  $manifest
     * @return array
     */
    public function writeManifest($manifest)
    {
        $this->files->put(
            $this->manifestPath, '<?php return '.var_export($manifest, true).';'
        );
        return array_merge(['when' => []], $manifest);
    }

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

以上就是 情况 1 在 config/app.php 注册的。可以总结到两点

1、会生成缓存文件,延迟加载的,保存在缓存文件的 deferred 数组中,非延迟加载的在缓存文件的 eager 数组中。缓存文件保存在 $this->bootstrapPath().'/cache/services.php' 也就是 bootstrap/cache/services.php (细心的读者发现,聊天记录截图中存在笔误)

2、通过 app 的 register 注册

情况 2 在自定义服务提供者中注册

这个就不展开篇幅来讲具体过程了,例如这个 bug 重现,可能他在 app/Providers 目录下新建了一个 MongodbServiceProvider 类,在里面写了:

class MongodbServiceProvider extends ServiceProvider
{
    /**
     * Register bindings in the container.
     *
     * @return void
     */
    public function register()
    {
        $this->app->register(Jenssegers\Mongodb\MongodbServiceProvider::class);
    }
}

所以情况 2 还是一样的,1、生成缓存文件,2、通过 register 注册

情况 3 历史遗留问题

所以不难知道情况 3 ,可能其他都删掉或者注释掉,但编译的缓存文件没删除或更新。


从 debug 中看问题

其实从聊天截图中可以看出,我说清除一下缓存文件(原谅我这句话的确可能引起误解),他真的去执行了 php artisan cache:clear
但没有去执行我真正想表达的 php artisan clear-compiled

想想为什么,其实是因为学习的时候,心太浮了,或者说不会去类比,你学了 cache 清除的,你当时的想法可能就是

哦,我懂了,可以这样清除 cache

但你不会想到其他的是不是也可以类似清除 ,或者说压根没想过其他的还有编译出来的文件。

这其实就是学习的分水岭,当你学会 cache 可以清除的时候,不妨留个心眼看看 artisan 输出的命令

file

file

你就会发现哦,原来还有其他的,这时候就会让你去思考,这些都有什么用,什么时候会用到。有清除,那怎么生成。

学习为什么说一环扣一环,是因为你的思维是连贯的,而不是想文档是分散的

如果他当时知道有这么一回事,那么他就不会无从下手,就不会不知道 debug 的方向。

debug 难,难在定位 bug 所在,而这又关乎于你平时是否多想多观察,没人敢说一看就知道是怎么回事,但却有人敢说,一看就可能是怎么回事,可以往这个方向去试试。这可以说是经验,但这经验不只是单纯踩坑多而积累的,而是平常多思考而积累的,也许有人没踩过这坑,但他想过这问题,他就知道应该往哪方面去尝试了。不要把所有的问题都推给 经验,其实没经验的只是你的思维,思维太幼稚了。你要做的不是书本让你学什么就学什么,而是你想学什么去找什么书

再谈学习

其实问题终究不会有什么大改变。羊毛出在羊身上。真的不是某些人的智商高,他学习快,而是他的思维。从一定程度上可以理解为情商。他知道什么是他想要的,什么是不想要的。跟这个妹子应该聊星座,跟那个妹子应该聊韩剧,而不是遇见妹子就聊

你知道世界上最好的语言是什么吗?PHP!

所以,我们先来知道什么叫学习。

在百度百科(先把你们的吐槽放放)中,是这么定义的

file

学习,是指通过阅读、听讲、思考、研究、实践等途径获得知识或技能的过程。

阅读、听讲,其实就是我们平常看文档,看书,看视频,以及任何一切我们能获取知识的途径。
很多不会学习的人,基本完成这两步就直接实践了。美其言曰:实践出真知。

其实这就是我不太赞成的一点,也是这些人学习吃力的地方。为什么这么说?且听我愚见:

1、首先,信息爆炸的时代,且不说吸收的知识好与坏,哪怕全是精华,你阅读,听讲,你只是停留在你知道有这么一回事的阶段罢了,你去实践,你会发现,你会有很多人的通病道理我都懂,臣妾做不到呀,你觉得自己好像 掌握 ,实际上你真的只是 了解 ,这中间真的是有差别的。不要企图问我怎样才算了解,怎样才算掌握。这问题真的只有你自己才知道。(实际很多人都觉得自己知道,实际上并不知道,哲学问题~哈哈)

打个比方,你知道吃饭手口并用呀,你也知道筷子,刀叉,这些餐具。但不代表你真的会使用筷子(至少我身边朋友一大半拿筷子的动作都是错的,一些小的菜就夹不起来),不代表你知道刀要怎么切,叉要怎么用。

这时候可能又有人跳出来说,我多尝试几种方式就好了嘛,总会学会的。

没错,是这样的,但你发现本质没有,在你多尝试几种方式的同时,你思维就在想这几种方式,而这些方式是通过你思考、研究得出来的,然后再加以实践修正。所以

关键的一步是思考、研究,重要的一步是实践修正

所以说了解,掌握,其实这些都不重要,取决于你的目标是什么罢了。重要的是从你获取到知识之后,有没有思考、研究,接着有没有实践修正,如果想学会真正学习,缺一不可。

当你真的会思考了,我相信你也会学习了。

所以我知道你们懒得总结,我帮你们总结一下

  • 阅读、听讲只是你的敲门砖,让你知道有这个点罢了,让你知道还可以往这方面想而已。
  • 思考、研究才是关键。你要明白你想学什么,而不是书本让你学什么。哲学来讲就是主观能动性。不然你看完,美其言曰“学完”,其实本质来讲根本就没学。
  • 实践修正才是重要,无论你只能阅读听讲,还是你已经可以思考研究,都离不开实践,而实践在我看来只是为了去验证你的想法是否正确,让你总结出优与缺,总结出对与错,所以关键是什么:大胆去做,然后总结,接着思考,最后修正

学习,是人在生活过程中,通过获得经验而产生的行为或行为潜能的相对持久的行为方式。

其实学到老活到老就是这个理。为什么很多人看文档看不下去,学习学不下去,其实就是因为你觉得没有用、没意义、学得慢等等各种消极的因素影响。其实想过没有,归根到底,本质是因为,你没有获得经验,所以就没有了后面的转折句而产生的行为或行为潜能的相对持久的行为方式

所以,举个例子,要明白一点,Laravel 文档都是最浅显的知识,因为作者需要给你成就感,所以作者写文档的时候循序渐进,由浅至深。但实际上当你真正学习完文档之后你就会发现,很多东西其实作者并没有提到,而这些点往往就是关键的点。例如为什么作者不写进文档,仅仅因为篇幅问题,还是写进去了之后,初学者在不懂本质的前提下会滥用,会产生其他的影响。所以每一份好的文档,其实都浓缩了作者的思维所在,要真正去学习,其实这些思维才是精华所在。

再比如,框架源码。很多人问,到底应不应该去学框架源码。我会反问她们,你学框架源码学什么,只是学他有哪些语法糖,是怎么实现的么?还是你想学作者的思维方式,数据结构,设计模式,新特性,哪怕往小里讲,作者的变量命名方式。所以这里其实回归到上面一个点不是框架让你学什么,而是你要从框架学什么

搞清楚这点了自然而然的,你就会获得经验,你就可以继续转折句,相对持久的行为。

好了,讲到这里,就该讲讲为什么是相对持久了(坐稳了,开车了)。

学习一个东西,从浅至深,俗话说的好没有耕坏的田,只有累死的牛一样的,人的学习精力,是有限的,不应该什么都学。读书时候有句话伤其十指不如断其一指,也有句话闻道有先后,术业有专攻。你可能现在学某样知识 Z ,你觉得你有毅力有恒心,但实际情况是,当你学的越多,你就会发现你懂得越少,你就会自然而然的去学习其他的,不是说你放弃了 Z,而是你为了更好的学习 Z,你需要去学习 B,这时候你就能 ZB 了不是么。

或许你看完上面一段,你觉得矛盾,又叫我不应该什么都学,又叫我为了更好得装逼,先学会装,再学会逼。其实不矛盾的。正是因为你只是为了装逼,所以你不应该什么都学。你只要学会 ZB 就好。但同时由于你之前只学会 Z,所以你需要去学会 B。

总结

以上只是我个人的一些愚见罢了,一千个读者就有一千个哈姆雷特。觉得对你有用,取其精华去其糟粕,我倍感欣慰。觉得完全不同意我的观点,文明留言你的看法,让我也借鉴学习思考一下。

正因为有差异才组成缤纷多彩的世界,不是么?所以有你更精彩,不说了,情人节,你懂的。

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由 Summer 于 3年前 加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 10
monkey

学习的艺术 :+1:

3年前 评论

挺不错的!当然每个领域具体学习方法不一样:有的需要借势造势,有的需要谋定而后动

3年前 评论
Junting

拜读,受教了

3年前 评论

不错,受教了

3年前 评论

思维的碰撞,第三者的秘密

3年前 评论
Silencewj

5.2用mongodb,几个月前在内网上折腾出来了。但是今天上线放到外网上的时候,连这个扩展都忘记怎么装。 人与人之间的差别啊真大。思维要迭代

2年前 评论
Silencewj

可以进这个讨论群吗

2年前 评论

好文!
学习==>思考==>疑惑==>解惑。学习引发思考,思考产生疑惑,有了疑惑就要去解决,解决了疑惑,从这个过程中获得乐趣与满足,这是学习最为内在的动力。

1年前 评论

深受启发。赞

1年前 评论

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!