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

嘉兴ing

前情提要

  1. 由于 FileStore 在存储不过期的key的expire时使用了 9999999999, 导致最后在使用 Carbon 处理时日期溢出, 因此自己修改了一下, 新增一个 App\Extensions\Cache\FileStore 文件

    <?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() 中扩展该缓存驱动

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

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

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

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 命令

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

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

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

    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,
            ...        
        ],    
        ...
    ];
阅读 1.4k

尤嘉兴ing
个人专栏

PHPer@厦门

195 声望
9 粉丝
0 条评论

PHPer@厦门

195 声望
9 粉丝
宣传栏