Laravel 5.8 中使用 telescope 并自定义扩展缓存驱动报错分析及解决方案

前情提要

  1. 因为 FileStore 在存储不过时的key的expire时使用了 9999999999, 致使最后在使用 Carbon 处理时日期溢出, 所以本身修改了一下, 新增一个 App\Extensions\Cache\FileStore 文件php

    <?php
    namespace App\Extensions\Cache;
    
    class FileStore extends \Illuminate\Cache\FileStore
    {
        protected function expiration($seconds)
        {
            $expiration = parent::expiration($seconds);
            return $expiration === 9999999999 ? 2147483600 : $expiration;
        }
    }
  2. 并在 App\Providers\AppServiceProvider::boot() 中扩展该缓存驱动laravel

    Cache::extend('file2', function ($app, $config) {
        return Cache::repository(new FileStore($app['files'], $config['path']));
    });
  3. 最后修改了默认的缓存驱动 config/cache.phpjson

    return [
        'default' => 'file2',
    
        'stores' => [
            ...
            'file' => [
                'driver' => 'file2',
                ...
            ],
            ...
        ]    
    ];

这时候问题出来了, 不管是启动 php artisan tinker 或网页直接访问, 都会报错:bootstrap

Driver [file2] is not supported

先一顿分析

  1. laravel/telescope 在 composer.json 中配置了包自动发现策略:

    extra.laravel.providers: ["Laravel\\Telescope\\TelescopeServiceProvider"]数组

    composer 在安装/更新包时, 会将全部安装的包的信息存储在 vendor/composer/installed.json, 其中包含每一个包的安装信息及其配置的composer.json文件缓存

  2. 项目 composer.json 根据其配置 `scripts.post-autoload-dumpautoload-dump 后会执行 php artisan package:discover --ansi 命令app

    上述命令对应的是 Illuminate\Foundation\Console\PackageDiscoverCommand 文件.composer

    它会调用 Illuminate\Foundation\PackageManifest::build() , 该方法会将 vendor/composer/installed.json中配置了 extra.laravel.providers 的项提取出来, 并保存在 bootstrap/cache/packages.php 中.ide

    这部分解析能够参考: https://divinglaravel.com/lar...
  1. 在laravel启动过程当中, Illuminate\Foundation\Application::registerConfiguredProviders()会逐个注册所需的服务提供者, 服务提供者列表来源包括: config('app.providers') 以及 laravel 包自动发现策略.post

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

    上述代码分析:

    第3行: 将配置中 app.providers 中的服务提供者根据字符串前缀匹配分开, 此时 $providers 值大体是这样的:

    {
        0 : [
            "Illuminate\....."
            ...
            "Illuminate\....."
        ],
        1 : [
            "App\Providers\AppServiceProvider::class",
            ...
            "App\Providers\RouteServiceProvider::class",
        ],
    }

    第8行: 将laravel包自动发现策略获取的服务提供者列表插入到 $providers 数组中 1 的位置, 原先的 1 挪到 2, 此时 $providers 数组大体以下:

    {
        0 : [
            "Illuminate\....."
            ...
            "Illuminate\....."
        ],
        1 : [
            ...
            "Laravel\Telescope\TelescopeServiceProvider",
            ...
        ],
        2 : [
            "App\Providers\AppServiceProvider",
            ...
            "App\Providers\RouteServiceProvider",
        ],
    }

    第10行: 将 $providers 数组扁平化, 顺序则是依次 0, 1, 2 这样分别 register(注册) 这些服务提供者.

  1. 在laravel启动初始化的最后还会依次按 register(注册) 的顺序依次 boot(启动)上述注册的服务提供者.

    对于 Laravel\Telescope\TelescopeServiceProvider 这个服务提供者, 按照以下的调用顺序

    Laravel\Telescope\TelescopeServiceProvider::boot()
        |
        V
    Laravel\Telescope\Telescope::start()
        |
        V
    Laravel\TelescopeRegistersWatchers::registerWatchers()
        |
        V
    Laravel\Telescope\Watchers\DumpWatcher::register()
        ↑ 这里的代码调用 $this->cache->get("...")

    Laravel\Telescope\Watchers\DumpWatcher::register() 其中的代码调用了缓存相关接口, 然而此时根本就没有执行到 App\Providers\AppServiceProvider::boot(), 天然会致使报错没法找到该缓存驱动.

解决办法

基本思路就是调整 Laravel\Telescope\TelescopeServiceProvider 服务提供者的加载顺序, 使其在 App\Providers\AppServiceProvider 以后加载.

这里给出一个方案:

  1. 配置项目的 composer.json, 使laravel的包自动发现策略忽略 TelescopeServiceProvider

    {
        ...
        "extra": {
            "laravel": {
                "dont-discover": [
                    "laravel/telescope"
                ]
            }
        },
        ...
    }
  2. TelescopeServiceProvider 手动加入到服务提供者列表中, 注意顺序

    修改 config/app.php

    return [
        ...
        'providers' => [
            ...
            App\Providers\AppServiceProvider::class,
            ...
            Laravel\Telescope\TelescopeServiceProvider::class,        
            App\Providers\TelescopeServiceProvider::class,
            ...        
        ],    
        ...
    ];
相关文章
相关标签/搜索