上一篇文章咱們介紹了Laravel的HTTP內核,詳細概述了網絡請求從進入應用到應用處理完請求返回HTTP響應整個生命週期中HTTP內核是如何調動Laravel各個核心組件來完成任務的。除了處理HTTP請求一個健壯的應用常常還會須要執行計劃任務、異步隊列這些。Laravel爲了能讓應用知足這些場景設計了artisan
工具,經過artisan
工具定義各類命令來知足非HTTP請求的各類場景,artisan
命令經過Laravel的Console內核來完成對應用核心組件的調度來完成任務。 今天咱們就來學習一下Laravel Console內核的核心代碼。php
跟HTTP內核同樣,在應用初始化階有一個內核綁定的過程,將Console內核註冊到應用的服務容器裏去,仍是引用上一篇文章引用過的bootstrap/app.php
裏的代碼git
<?php
// 第一部分: 建立應用實例
$app = new Illuminate\Foundation\Application(
realpath(__DIR__.'/../')
);
// 第二部分: 完成內核綁定
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
// console內核綁定
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
return $app;
複製代碼
Console內核 \App\Console\Kernel
繼承自Illuminate\Foundation\Console
, 在Console內核中咱們能夠註冊artisan
命令和定義應用裏要執行的計劃任務。github
/**
* Define the application's command schedule. * * @param \Illuminate\Console\Scheduling\Schedule $schedule * @return void */ protected function schedule(Schedule $schedule) { // $schedule->command('inspire') // ->hourly(); } /** * Register the commands for the application. * * @return void */ protected function commands() { $this->load(__DIR__.'/Commands'); require base_path('routes/console.php'); } 複製代碼
在實例化Console內核的時候,內核會定義應用的命令計劃任務(shedule方法中定義的計劃任務)shell
public function __construct(Application $app, Dispatcher $events)
{
if (! defined('ARTISAN_BINARY')) {
define('ARTISAN_BINARY', 'artisan');
}
$this->app = $app;
$this->events = $events;
$this->app->booted(function () {
$this->defineConsoleSchedule();
});
}
複製代碼
查看aritisan
文件的源碼咱們能夠看到, 完成Console內核綁定的綁定後,接下來就會經過服務容器解析出console內核對象bootstrap
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
$status = $kernel->handle(
$input = new Symfony\Component\Console\Input\ArgvInput,
new Symfony\Component\Console\Output\ConsoleOutput
);
複製代碼
解析出Console內核對象後,接下來就要處理來自命令行的命令請求了, 咱們都知道PHP是經過全局變量$_SERVER['argv']
來接收全部的命令行輸入的, 和命令行裏執行shell腳本同樣(在shell腳本里能夠經過$0
獲取腳本文件名,$1
$2
這些依次獲取後面傳遞給shell腳本的參數選項)索引0對應的是腳本文件名,接下來依次是命令行裏傳遞給腳本的全部參數選項,因此在命令行裏經過artisan
腳本執行的命令,在artisan
腳本中$_SERVER['argv']
數組裏索引0對應的永遠是artisan
這個字符串,命令行裏後面的參數會依次對應到$_SERVER['argv']
數組後續的元素裏。數組
由於artisan
命令的語法中能夠指定命令參數選項、有的選項還能夠指定實參,爲了減小命令行輸入參數解析的複雜度,Laravel使用了Symfony\Component\Console\Input
對象來解析命令行裏這些參數選項(shell腳本里其實也是同樣,會經過shell函數getopts來解析各類格式的命令行參數輸入),一樣地Laravel使用了Symfony\Component\Console\Output
對象來抽象化命令行的標準輸出。bash
在Console內核的handle
方法裏咱們能夠看到和HTTP內核處理請求前使用bootstrapper
程序引用應用同樣在開始處理命令任務以前也會有引導應用這一步操做網絡
其父類 「Illuminate\Foundation\Console\Kernel」 內部定義了屬性名爲 「bootstrappers」 的 引導程序 數組:app
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\SetRequestForConsole::class,
\Illuminate\Foundation\Bootstrap\RegisterProviders::class,
\Illuminate\Foundation\Bootstrap\BootProviders::class,
];
複製代碼
數組中包括的引導程序基本上和HTTP內核中定義的引導程序同樣, 都是應用在初始化階段要進行的環境變量、配置文件加載、註冊異常處理器、設置Console請求、註冊應用中的服務容器、Facade和啓動服務。其中設置Console請求是惟一區別於HTTP內核的一個引導程序。框架
執行命令是經過Console Application來執行的,它繼承自Symfony框架的Symfony\Component\Console\Application
類, 經過對應的run方法來執行命令。
name Illuminate\Foundation\Console;
class Kernel implements KernelContract
{
public function handle($input, $output = null)
{
try {
$this->bootstrap();
return $this->getArtisan()->run($input, $output);
} catch (Exception $e) {
$this->reportException($e);
$this->renderException($output, $e);
return 1;
} catch (Throwable $e) {
$e = new FatalThrowableError($e);
$this->reportException($e);
$this->renderException($output, $e);
return 1;
}
}
}
namespace Symfony\Component\Console;
class Application
{
//執行命令
public function run(InputInterface $input = null, OutputInterface $output = null)
{
......
try {
$exitCode = $this->doRun($input, $output);
} catch {
......
}
......
return $exitCode;
}
public function doRun(InputInterface $input, OutputInterface $output)
{
//解析出命令名稱
$name = $this->getCommandName($input);
//解析出入參
if (!$name) {
$name = $this->defaultCommand;
$definition = $this->getDefinition();
$definition->setArguments(array_merge(
$definition->getArguments(),
array(
'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name),
)
));
}
......
try {
//經過命令名稱查找出命令類(命名空間、類名等)
$command = $this->find($name);
}
......
//運行命令類
$exitCode = $this->doRunCommand($command, $input, $output);
return $exitCode;
}
protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
{
......
//執行命令類的run方法來處理任務
$exitCode = $command->run($input, $output);
......
return $exitcode;
}
}
複製代碼
執行命令時主要有三步操做:
經過命令行輸入解析出命令名稱和參數選項。
經過命令名稱查找命令類的命名空間和類名。
執行命令類的run
方法來完成任務處理並返回狀態碼。
和命令行腳本的規範同樣,若是執行命令任務程序成功會返回0, 拋出異常退出則返回1。
還有就是打開命令類後咱們能夠看到並無run方法,咱們把處理邏輯都寫在了handle
方法中,仔細查看代碼會發現run
方法定義在父類中,在run
方法會中會調用子類中定義的handle
方法來完成任務處理。 嚴格遵循了面向對象程序設計的**SOLID **原則。
執行完命令程序返回狀態碼後, 在artisan
中會直接經過exit($status)
函數輸出狀態碼並結束PHP進程,接下來shell進程會根據返回的狀態碼是否爲0來判斷腳本命令是否執行成功。
到這裏經過命令行開啓的程序進程到這裏就結束了,跟HTTP內核同樣Console內核在整個生命週期中也是負責調度,只不過Http內核最終將請求落地到了Controller
程序中而Console內核則是將命令行請求落地到了Laravel中定義的各類命令類程序中,而後在命令類裏面咱們就能夠寫其餘程序同樣自由地使用Laravel中的各個組件和註冊到服務容器裏的服務了。
本文已經整理髮布到系列文章Laravel核心代碼學習中,歡迎訪問閱讀,多多交流。