国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Laravel 動態(tài)添加 Artisan 命令的最佳實踐

ninefive / 958人閱讀

摘要:初步嘗試既然最常見的注冊命令的方式是修改類中的,那么一般正常人都會從這邊開始下手。又要自己取出實例,又要自己調(diào)用方法,調(diào)用方法之前還有自己先把實例化這么繁瑣,肯定不是運行時添加命令的最佳實踐,所以我決定繼續(xù)尋找更優(yōu)解。

本文首發(fā)于我的博客,原文鏈接:https://blessing.studio/best-...

雖然 Laravel 官方文檔提供的添加 Artisan Command 的方法是直接修改 app/Console/Kernel.php 文件并在 $commands 屬性中注冊要添加的 Artisan 命名的類名(Laravel 服務(wù)容器會自動解析),但是,如果我們出現(xiàn)需要「動態(tài)(運行時)添加 Artisan 命令」的需求的話,就會很容易吃癟。因為,Laravel 的文檔(當(dāng)然,我說的是官網(wǎng)上的)幾乎沒有提到任何關(guān)于這方面的內(nèi)容。

這也是我為什么總是吐槽 Laravel 文檔有些地方很爛的原因 —— 很多時候你為了實現(xiàn)一個文檔里沒提到的功能,需要去翻半天 Laravel 的框架源碼才能找到解決方法(我博客的 Laravel 標(biāo)簽 下已經(jīng)有不少這樣的踩坑文了)。雖然 Laravel 框架的源碼很優(yōu)雅,看著也不會難受,但是在一堆文件中跳來跳去尋找邏輯浪費腦細(xì)胞的行為還是能省則省吧 :(

這次要實現(xiàn)的功能是在運行時動態(tài)加載自定義的 Artisan Command(更詳細(xì)一些的需求就是在皮膚站的一個插件中注冊 Artisan 命令,Laravel 插件系統(tǒng)的實現(xiàn)可以參考我之前的 另一篇文章)。

TL;DR 太長不看

總之先上干貨,畢竟不是所有人都喜歡聽我廢話一大堆后才拿到解決方案的。

Laravel 5.3 及以上:

Artisan::starting(function ($artisan) {
    // 傳入類名字符串即可,會被服務(wù)容器自動解析
    $artisan->resolve("ExampleFooCommand");
    // 批量添加
    $artisan->resolveCommands([
        "ExampleFuckCommand",
        "ExampleShitCommand"
    ]);
    // 參數(shù)必須為 SymfonyComponentConsoleCommandCommand 的實例
    // 繼承自 IlluminateConsoleCommand 的類實例也可以
    $artisan->add($command);
});

Laravel 5.2:

Event::listen("IlluminateConsoleEventsArtisanStarting", function ($event) {
    // 其他用法同上
    $event->artisan->resolve("ExampleBarCommand");
});

Laravel 5.1:

Event::listen("artisan.start", function ($event) {
    // 其他用法同上
    $event->artisan->resolve("ExampleWtfCommand");
});

接下來就是我摸索時嘗試的步驟,寫下來權(quán)當(dāng)記錄水博文,發(fā)了發(fā)牢騷,有興趣的就繼續(xù)看下去吧。

0x01 初步嘗試

既然 Laravel 最常見的注冊 Artisan 命令的方式是修改 APPConsoleKernel 類中的 $commands,那么一般正常人都會從這邊開始下手。可以看到,這個類是繼承自 IlluminateFoundationConsoleKernel 類并覆寫了 $commands 屬性。讓我們稍微看一下這個 $commands 屬性用在哪了:

/**
 * Get the Artisan application instance.
 *
 * @return IlluminateConsoleApplication
 */
protected function getArtisan()
{
    if (is_null($this->artisan)) {
        return $this->artisan = (new Artisan($this->app, $this->events, $this->app->version()))
                            ->resolveCommands($this->commands);
    }

    return $this->artisan;
}

可以看到,這個方法用單例模式實例化了一個 Artisan(ArtisanIlluminateConsoleApplication 的別名),其中最重要的是調(diào)用了 IlluminateConsoleApplication::resolveCommands 這個方法,并且將那個注冊了自定義 Artisan 命令的屬性給傳了進(jìn)去。我們跳轉(zhuǎn)到那個 resolveCommands 方法看一看……

/**
 * Add a command, resolving through the application.
 *
 * @param  string  $command
 * @return SymfonyComponentConsoleCommandCommand
 */
public function resolve($command)
{
    return $this->add($this->laravel->make($command));
}

/**
 * Resolve an array of commands through the application.
 *
 * @param  array|mixed  $commands
 * @return $this
 */
public function resolveCommands($commands)
{
    $commands = is_array($commands) ? $commands : func_get_args();

    foreach ($commands as $command) {
        $this->resolve($command);
    }

    return $this;
}

代碼條理很清晰,挨個兒把那些 $commands 中的元素給丟進(jìn) Laravel 服務(wù)容器里實例化之后,調(diào)用父類方法 SymfonyComponentConsoleApplication::add (是的,Laravel 用了很多很多 Symfony 的組件)添加到自身實例中,持引用以供之后的調(diào)用所需。

繼續(xù)翻看 IlluminateFoundationConsoleKernel 的源碼,可以看到 Laravel 貼心地開放了一個 registerCommand 方法:

/**
 * Register the given command with the console application.
 *
 * @param  SymfonyComponentConsoleCommandCommand  $command
 * @return void
 */
public function registerCommand($command)
{
    $this->getArtisan()->add($command);
}

那么我們要做的就是,在運行時中拿到 Kernel 的實例,并且通過調(diào)用 registerCommand 方法把我們的自定義 Artisan 命令也給加進(jìn)去。那么我們要怎樣才能拿到這個實例呢?

相信對 Laravel 有所了解的各位都會想到 —— 服務(wù)容器。

通過查閱 Laravel 命令行入口(根目錄下的 artisan 文件)源碼可以知道,Laravel 就是使用服務(wù)容器來實例化 Kernel 的:

$kernel = $app->make(IlluminateContractsConsoleKernel::class);

如果你有心的話,會發(fā)現(xiàn) Laravel 框架的 Web 入口文件(public/index.php)和命令行入口文件中實例化 Kernel 的語句都是一樣的,那么為什么通過 Web 訪問時解析出來的是 AppHttpKernel 的實例而通過命令行訪問時解析出來的就是 AppConsoleKernel 的實例了呢?

這里就涉及 Laravel 服務(wù)容器的一個強大的核心功能 —— 綁定接口至實現(xiàn)。因為這些實例都實現(xiàn)了相同的接口,所以我們可以使用相同的代碼并且很方便地更換接口后的具體實現(xiàn),這也是使用 IoC 容器的好處之一,有興趣的多去了解了解吧 :)

閑話休提,那么我們只要通過服務(wù)容器就可以拿到 Kernel 實例了(當(dāng)然,如果你愿意,你也可以直接通過 $GLOBAL["kernel"] 來訪問全局作用域下定義的那個 $kernel 變量,效果都是一樣的,但是太 tmd lowb 了,所以我不愿意用),看起來已經(jīng)離成功了一大半呢!

$kernel = app("IlluminateContractsConsoleKernel");
// 因為 registerCommand 方法只接受 SymfonyComponentConsoleCommandCommand 的實例作為參數(shù)
$kernel->registerCommand(app("ExampleFooCommand"));

然后我們執(zhí)行一下 php artisan list,就能看到我們的命令已經(jīng)出現(xiàn)啦:

Laravel Framework version 5.2.45

Usage:
  command [options] [arguments]

Available commands:
  help           Displays help for a command
  list           Lists commands
  foo            Example command

但是等等……Laravel 自帶的那些 makemigrate 等命令哪里去了?我最開始出現(xiàn)這個問題的時候還以為是我太早把 Kernel 解析出來了,后來直接使用 $GLOBALS["kernel"] 也是一樣的問題時才認(rèn)識到問題另有原因。仔細(xì)閱讀源碼后發(fā)現(xiàn) Artisan 命令行在調(diào)用(handlecall 等方法)之前都會調(diào)用這樣一個方法:

$this->bootstrap();

通過閱讀源碼可以知道這個 bootstrap 方法就是用來加載 Laravel 框架的基本組件的,包括 IlluminateFoundationProvidersArtisanServiceProvider 這個服務(wù)提供者中提供的所有框架內(nèi)置 Artisan 命令。好在這個方法是 public 的,所以我們只要在 registerCommand 之前調(diào)用一下這個方法就可以啦:

$kernel = app("IlluminateContractsConsoleKernel");
$kernel->bootstrap();
$kernel->registerCommand(app("ExampleFooCommand"));

如果你愿意,你甚至還可以直接使用 Artisan 這個 Facade,因為它就是指向 IlluminateContractsConsoleKernel 的:

Artisan::bootstrap();
Artisan::registerCommand(app("InsaneProfileCacheCommandsClean"));

結(jié)果如下:

0x02 繼續(xù)嘗試

雖然這樣確實能夠?qū)崿F(xiàn)我們的需求,但是我覺得這樣不行(話說我都不曉得嘻哈梗怎么突然就流行起來了,雖然確實蠻有意思的啦)。

又要自己取出 Kernel 實例,又要自己調(diào)用 bootstrap 方法,調(diào)用 registerCommand 方法之前還有自己先把 Command 實例化……這么繁瑣,肯定不是運行時添加 Artisan 命令的最佳實踐,所以我決定繼續(xù)尋找更優(yōu)解。

雖然我們上面用的方法是取出 Kernel 實例并進(jìn)行操作的,但是其實該方法里的操作也是基于 getArtisan 所獲取的 IlluminateConsoleApplication (?這玩意在 Laravel 源碼里經(jīng)常被 as 為 Artisan)實例進(jìn)行的。可惜的是這個方法是 protected 的,我們無法直接調(diào)用它,所以我們還是先去看這個類的源碼吧:

/**
 * Create a new Artisan console application.
 *
 * @param  IlluminateContractsContainerContainer  $laravel
 * @param  IlluminateContractsEventsDispatcher  $events
 * @param  string  $version
 * @return void
 */
public function __construct(Container $laravel, Dispatcher $events, $version)
{
    parent::__construct("Laravel Framework", $version);

    $this->laravel = $laravel;
    $this->setAutoExit(false);
    $this->setCatchExceptions(false);

    $events->fire(new EventsArtisanStarting($this));
}

瞧我發(fā)現(xiàn)了什么?Artisan 在實例化之后會觸發(fā)一個 IlluminateConsoleEventsArtisanStarting 事件,并且把自身實例給傳遞過去。那么我們要做的就很簡單了:監(jiān)聽該事件,拿到 Artisan 實例,調(diào)用 resolveresolveCommands 方法來注冊我們的 Artisan 命令即可。

具體的方法在最上面給出了,我這里就不多說了。另外需要注意的是,Laravel 5.1 版本并沒有 ArtisanStarting 這個事件,而是 artisan.start,不過原理都是一樣的:

$events->fire("artisan.start", [$this]);

另外,在 Laravel 5.3 及以上版本中,Artisan 還貼心地提供了 Artisan::starting 這個方法,和監(jiān)聽事件的效果差不多,不過是直接修改實例的 $bootstrappers 屬性的,傳遞一個閉包進(jìn)去即可,示例代碼見最上方。

0x03 一些牢騷

雖然只要看源碼就能知道,Laravel 框架很多地方都預(yù)留了非常多的接口,讓我們可以方便優(yōu)雅地實現(xiàn)很多自定義功能,這也是我為什么喜歡這個框架的原因之一。

但是……但是,你的文檔就不能寫好一點嗎!哪怕提一下這些 API 也好啊!

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/23337.html

相關(guān)文章

  • Laravel 5 程序優(yōu)化技巧

    摘要:使用即時編譯器和都能輕輕松松的讓你的應(yīng)用程序在不用做任何修改的情況下,直接提高或者更高的性能,之前做個一個實驗,具體請見使用提升程序性能。 本文經(jīng)授權(quán)轉(zhuǎn)自 PHPHub 社區(qū) 說明 性能一直是 Laravel 框架為人詬病的一個點,所以調(diào)優(yōu) Laravel 程序算是一個必學(xué)的技能。 接下來分享一些開發(fā)的最佳實踐,還有調(diào)優(yōu)技巧,大家有別的建議也歡迎留言討論。 這里是簡單的列表: 配置信...

    habren 評論0 收藏0
  • 分享一些簡單 Laravel 編碼實踐

    摘要:關(guān)于,它使用起來簡單且舒適適用于編寫產(chǎn)品代碼,并能極大的推動開發(fā)過程。這里有一些在開發(fā)中值得記住的簡單建議最大限度的使用你的文件不要破壞框架核心,不要編輯文件夾中的文件,你可以選擇繼承相關(guān)函數(shù)來實現(xiàn)。 showImg(https://segmentfault.com/img/remote/1460000018416776?w=808&h=449); 將任何 PHP 框架稱為最好的框架都...

    cyixlq 評論0 收藏0
  • Laravel 編碼實踐分享

    摘要:關(guān)于,它使用起來簡單且舒適適用于編寫產(chǎn)品代碼,并能極大的推動開發(fā)過程。中我最喜歡的一點是它是使用當(dāng)下編程中的最佳實踐所構(gòu)建的。的工作原理是這樣的,對于一個命名為的表,希望該表的模型被命名為。盡量為每一個請求創(chuàng)建。 showImg(https://segmentfault.com/img/remote/1460000018303541?w=808&h=449); 將任何 PHP 框架稱為...

    wean 評論0 收藏0
  • Laravel入門及實踐,快速上手ThinkSNS+二次開發(fā)

    摘要:在中,提示符可能是。框架使用來執(zhí)行安裝及管理依賴。為了能訪問網(wǎng)頁,要啟動程序服務(wù)器。在大多數(shù)類系統(tǒng)中,包括,命令行提示符是符號。這兩個操作分別對應(yīng)于的和,即創(chuàng)建和讀取。首個表單要在模板中編寫表單,可以使用表單構(gòu)造器。 【摘要】自從ThinkSNS+不使用ThinkPHP框架而使用Laravel框架之后,很多人都說技術(shù)門檻抬高了,其實你與TS+的距離僅僅只是學(xué)習(xí)一個新框架而已,所以,我們...

    glumes 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<