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,
            ...        
        ],    
        ...
    ];
相關文章
相關標籤/搜索