Recca Chao 的 gitHub page

推廣網站開發,包含 Laravel 和 Kotlin 後端撰寫、自動化測試、讀書心得等。Taiwan Kotlin User Group 管理員。

View on GitHub

voyager 作為一個有大量後台頁面的套件,自然需要一個處理套件的方式,這是怎麼做到的呢?

外層結構設計

首先,針對使用者看得到的部分,是這樣的:

Route::group(['prefix' => 'admin'], function () {
    Voyager::routes();
});

這對 laravel 使用者來說很直觀,只要是 prefix 是 admin 的路徑,通通會被導入 Voyager 自己定義的路徑裡面。如果我有需要其他 Voyager 沒有定義的路徑,那我就在下面加入新的路徑,像是這樣:

Route::group(['prefix' => 'admin'], function () {
   Voyager::routes();

   // Your overwrites here
   Route::post('login', ['uses' => 'MyAuthController@postLogin', 'as' => 'postlogin']);
});

內部實作

在 routes/web.php 裡面寫入路徑宣告

上面所說的段落是在呼叫指令 php artisan voyager:install 時產生的,這是怎麼實現的呢?

我們來看看 voyager:install 時做了哪些事情

vendor/tcg/voyager/src/Commands/InstallCommand.php 裡面

可以看到這一段:

$this->info('Adding Voyager routes to routes/web.php');
$routes_contents = $filesystem->get(base_path('routes/web.php'));
if (false === strpos($routes_contents, 'Voyager::routes()')) {
    $filesystem->append(
        base_path('routes/web.php'),
        "\n\nRoute::group(['prefix' => 'admin'], function () {\n    Voyager::routes();\n});\n"
    );
}

這邊的 $filesystem 是一個 Illuminate\Filesystem\Filesystem 物件

所以這一段的邏輯就是,如果執行 voyager:install 時,routes/web.php裡面沒有看到 Voyager::routes() 這段文字,那麼就會在下面加上

Route::group(['prefix' => 'admin'], function () {
    Voyager::routes();
});

這麼一段。

連接物件

不過這邊有個奇怪的地方是,laravel 怎麼會認得什麼是 Voyager 物件,知道呼叫 Voyager::routes() 時要去哪邊找程式呢?

這一段的處理,在 vendor/tcg/voyager/VoyagerServiceProvider.php 裡面

        $loader = AliasLoader::getInstance();
        $loader->alias('Voyager', VoyagerFacade::class);

        $this->app->singleton('voyager', function () {
            return new Voyager();
        });

VoyagerFacade 裡面則是

<?php

namespace TCG\Voyager\Facades;

use Illuminate\Support\Facades\Facade;

class Voyager extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'voyager';
    }
}

這邊透過了 Illuminate\Foundation\AliasLoader 註冊了 Voyager 這個別名,所以在 routes/web.php 裡面,不需要宣告整個 TCG\Voyager\Voyager 物件(看起來很醜),只需要宣告 Voyager::routes(),laravel 就知道是要找 TCG\Voyager\Voyager 裡面的 routes()

連接 routes 檔案

那麼,routes() 裡面又是怎麼實作,才能讓 laravel 知道所有 voyager 套件所需要的路徑呢?

TCG\Voyager\Voyager是這樣實作:

    public function routes()
    {
        require __DIR__.'/../routes/voyager.php';
    }

routes/voyager.php 這個檔案裡面,可以看到包含所有 voyager 所需要的路徑,都會在這個時候被引入 routes 裡面。

所以整個流程就清楚了。先透過 VoyagerServiceProvider 引入 Voyager 別名,讓這個別名綁定 TCG\Voyager\Voyager 物件。然後在裡面宣告 routes(),require /routes/voyager.php 這個檔案之後,就可以透過 (new Illuminate\Filesystem\Filesystem())->append() 的方式,在原本的 routes/web.php 裡面寫入 Voyager::routes(),這樣就能完整的引入套件所需要的路徑了!