基於 Laravel 開發過程中,ThinkSNS+ 是如何做到 Laravel 配置可以網站後臺配置的。

medz發表於2017-05-12

距離上一次分享差不多一週了,這篇文章,就分享下利用 Laravel 的 Bootstrapping 達到網站後臺設定 laravel 配置。

需求場景

首先,ThinkSNS+ 作為一個使用者可以使用的「社交系統」和開源網站程式一樣擁有後臺,那有一些配置,Laravel 是要求寫在 /config/*.php 的配置檔案中的,例如 app.nameapp.debug 等資訊的配置,以及 Jobs 的驅動配置,廣播系統的配置等,我們都搬到了網站後臺,使用者安裝後可以不用修改配置檔案的情況下映象配置。

如何覆蓋配置

我們首先開啟 Illuminate\Foundation\Application::bootstrapWith 方法,程式碼如下:

    /**
     * Run the given array of bootstrap classes.
     *
     * @param  array  $bootstrappers
     * @return void
     */
    public function bootstrapWith(array $bootstrappers)
    {
        $this->hasBeenBootstrapped = true;

        foreach ($bootstrappers as $bootstrapper) {
            $this['events']->fire('bootstrapping: '.$bootstrapper, [$this]);

            $this->make($bootstrapper)->bootstrap($this);

            $this['events']->fire('bootstrapped: '.$bootstrapper, [$this]);
        }
    }

重點程式碼在 $this['events']->fire('bootstrapping: '.$bootstrapper, [$this]);$this['events']->fire('bootstrapped: '.$bootstrapper, [$this]); 上,很明顯是載入並執行 bootstrapper 的前置和後置事件。

所以,我們看還有一個方法叫做 beforeBootstrappingafterBootstrapping 然後怎麼做呢?我們看

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

沒錯,這裡是固定了順序的,我錯誤的載入順序,會造成laravel的失敗,所以,我們選擇在 之前繼承 Illuminate\Foundation\Application 的 應用基礎上增加一個事件,程式碼如下:

    public function __construct($basePath = null)
    {
        parent::__construct($basePath);

        // Load configuration after.
        $this->afterBootstrapping(\Illuminate\Foundation\Bootstrap\LoadConfiguration::class, function ($app) {
            $app->make(\Zhiyi\Plus\Bootstrap\LoadConfiguration::class)
                ->handle();
        });
    }

哪裡新增的事件?

因為 ThinkSNS+ 是繼承了 Illuminate\Foundation\Application 實現了新的 Application 類,所以我們直接在構造方法裡面增加了程式碼。
這樣,當 Laravel 啟動,但是還沒有載入 bootstrapper 的時候,已經把 載入配置的後置事件注入進去了。當載入配置執行完成後就會執行我注入的後置事件。

後置事件的實現

我們在建立了 \Zhiyi\Plus\Bootstrap\LoadConfiguration 這樣一個類,註冊為後置事件,路徑為: /app/Bootstrap/LoadConfiguration.php ,然後實現程式碼如下:

<?php

namespace Zhiyi\Plus\Bootstrap;

use Zhiyi\Plus\Support\Configuration;
use Illuminate\Contracts\Foundation\Application;

class LoadConfiguration
{
    protected $app;
    protected $configuration;

    /**
     * 載入配置構造方法.
     *
     * @author Seven Du <shiweidu@outlook.com>
     */
    public function __construct(Application $app, Configuration $configuration)
    {
        $this->app = $app;
        $this->configuration = $configuration;
    }

    /**
     *  Run handler.
     *
     * @return void
     * @author Seven Du <shiweidu@outlook.com>
     */
    public function handle()
    {
        static $loaded = false;
        if ($loaded) {
            return;
        }

        $this->app->config->set(
            $this->configuration->getConfigurationBase()
        );
    }
}

很簡單,因為 app('config') 是一個 Illuminate\Contracts\Config\Repository 介面的例項,所以直接呼叫 set 方法進行配置覆蓋。
Zhiyi\Plus\Support\Configuration 類是封裝的自定義配置載入類,載入的配置檔案存放在一個 YAML 檔案中,這個類實現了 自定義配置檔案的載入和儲存。這樣,我們從後臺呼叫 API 然後 constroller 呼叫這個類的 save 方法進行持久化。

Zhiyi\Plus\Support\Configuration::getConfigurationBase

為什麼要特殊說一下這個方法?因為這個方法的特殊性,也是 depth merge 實現的重要函式,在 Repository 中支援 app.name = value 這樣的形式進行深曾鍵值賦值,利用這一個特性,這個函式將 多維陣列轉換為一維。
效果:

// 多維陣列
$arr = [
    'app' => [
        'name' => 'xxx',
        'env' = 'local'
    ]
];

// 轉換後
$arr = [
    'app.name' => 'xxx',
    'app.env' => 'local',
];

然後呼叫 app('config')->set($arr) 就對 Laravel 的 config 進行了 depth merge。
最後,持久化儲存的 YAML 內容如下:

app:
    name: ThinkSNS+
queue:
        default: database
cache:
        default: memcached
        stores: 
                memcached:
                        servers:
                                host: 127.0.0.1
                                port: 11211

所以,基於 depth merge 在 .plus.yml 配置中,只需要儲存部分配置,即可不想配置結構的完整性的情況下對 Laravel 映象配置合併。


ThinkSNS+ 倉庫:https://github.com/zhiyicx/thinksns-plus

感謝大家的閱讀,開源不易,請為我們點一個 Star ?

Seven 的程式碼太渣,歡迎關注我的新擴充包 medz/cors 解決 PHP 專案程式設定跨域需求。

相關文章