淺談Laravel核心解讀之Console內(nèi)核
Console內(nèi)核
上一篇文章我們介紹了Laravel的HTTP內(nèi)核,詳細(xì)概述了網(wǎng)絡(luò)請求從進(jìn)入應(yīng)用到應(yīng)用處理完請求返回HTTP響應(yīng)整個生命周期中HTTP內(nèi)核是如何調(diào)動Laravel各個核心組件來完成任務(wù)的。除了處理HTTP請求一個健壯的應(yīng)用經(jīng)常還會需要執(zhí)行計(jì)劃任務(wù)、異步隊(duì)列這些。Laravel為了能讓應(yīng)用滿足這些場景設(shè)計(jì)了artisan工具,通過artisan工具定義各種命令來滿足非HTTP請求的各種場景,artisan命令通過Laravel的Console內(nèi)核來完成對應(yīng)用核心組件的調(diào)度來完成任務(wù)。 今天我們就來學(xué)習(xí)一下Laravel Console內(nèi)核的核心代碼。
內(nèi)核綁定
跟HTTP內(nèi)核一樣,在應(yīng)用初始化階有一個內(nèi)核綁定的過程,將Console內(nèi)核注冊到應(yīng)用的服務(wù)容器里去,還是引用上一篇文章引用過的bootstrap/app.php里的代碼
<?php // 第一部分: 創(chuàng)建應(yīng)用實(shí)例 $app = new Illuminate\Foundation\Application( realpath(__DIR__.'/../') ); // 第二部分: 完成內(nèi)核綁定 $app->singleton( Illuminate\Contracts\Http\Kernel::class, App\Http\Kernel::class ); // console內(nèi)核綁定 $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內(nèi)核 \App\Console\Kernel繼承自Illuminate\Foundation\Console, 在Console內(nèi)核中我們可以注冊artisan命令和定義應(yīng)用里要執(zhí)行的計(jì)劃任務(wù)。
/**
* 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');
}
在實(shí)例化Console內(nèi)核的時候,內(nèi)核會定義應(yīng)用的命令計(jì)劃任務(wù)(shedule方法中定義的計(jì)劃任務(wù))
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();
});
}
應(yīng)用解析Console內(nèi)核
查看aritisan文件的源碼我們可以看到, 完成Console內(nèi)核綁定的綁定后,接下來就會通過服務(wù)容器解析出console內(nèi)核對象
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class); $status = $kernel->handle( $input = new Symfony\Component\Console\Input\ArgvInput, new Symfony\Component\Console\Output\ConsoleOutput );
執(zhí)行命令任務(wù)
解析出Console內(nèi)核對象后,接下來就要處理來自命令行的命令請求了, 我們都知道PHP是通過全局變量$_SERVER['argv']來接收所有的命令行輸入的, 和命令行里執(zhí)行shell腳本一樣(在shell腳本里可以通過$0獲取腳本文件名,$1 $2這些依次獲取后面?zhèn)鬟f給shell腳本的參數(shù)選項(xiàng))索引0對應(yīng)的是腳本文件名,接下來依次是命令行里傳遞給腳本的所有參數(shù)選項(xiàng),所以在命令行里通過artisan腳本執(zhí)行的命令,在artisan腳本中$_SERVER['argv']數(shù)組里索引0對應(yīng)的永遠(yuǎn)是artisan這個字符串,命令行里后面的參數(shù)會依次對應(yīng)到$_SERVER['argv']數(shù)組后續(xù)的元素里。
因?yàn)閍rtisan命令的語法中可以指定命令參數(shù)選項(xiàng)、有的選項(xiàng)還可以指定實(shí)參,為了減少命令行輸入?yún)?shù)解析的復(fù)雜度,Laravel使用了Symfony\Component\Console\Input對象來解析命令行里這些參數(shù)選項(xiàng)(shell腳本里其實(shí)也是一樣,會通過shell函數(shù)getopts來解析各種格式的命令行參數(shù)輸入),同樣地Laravel使用了Symfony\Component\Console\Output對象來抽象化命令行的標(biāo)準(zhǔn)輸出。
引導(dǎo)應(yīng)用
在Console內(nèi)核的handle方法里我們可以看到和HTTP內(nèi)核處理請求前使用bootstrapper程序引用應(yīng)用一樣在開始處理命令任務(wù)之前也會有引導(dǎo)應(yīng)用這一步操作
其父類 「IlluminateFoundationConsoleKernel」 內(nèi)部定義了屬性名為 「bootstrappers」 的 引導(dǎo)程序 數(shù)組:
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, ];
數(shù)組中包括的引導(dǎo)程序基本上和HTTP內(nèi)核中定義的引導(dǎo)程序一樣, 都是應(yīng)用在初始化階段要進(jìn)行的環(huán)境變量、配置文件加載、注冊異常處理器、設(shè)置Console請求、注冊應(yīng)用中的服務(wù)容器、Facade和啟動服務(wù)。其中設(shè)置Console請求是唯一區(qū)別于HTTP內(nèi)核的一個引導(dǎo)程序。
執(zhí)行命令
執(zhí)行命令是通過Console Application來執(zhí)行的,它繼承自Symfony框架的Symfony\Component\Console\Application類, 通過對應(yīng)的run方法來執(zhí)行命令。
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
{
//執(zhí)行命令
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);
//解析出入?yún)?
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);
}
......
//運(yùn)行命令類
$exitCode = $this->doRunCommand($command, $input, $output);
return $exitCode;
}
protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
{
......
//執(zhí)行命令類的run方法來處理任務(wù)
$exitCode = $command->run($input, $output);
......
return $exitcode;
}
}
執(zhí)行命令時主要有三步操作:
- 通過命令行輸入解析出命令名稱和參數(shù)選項(xiàng)。
- 通過命令名稱查找命令類的命名空間和類名。
- 執(zhí)行命令類的run方法來完成任務(wù)處理并返回狀態(tài)碼。
和命令行腳本的規(guī)范一樣,如果執(zhí)行命令任務(wù)程序成功會返回0, 拋出異常退出則返回1。
還有就是打開命令類后我們可以看到并沒有run方法,我們把處理邏輯都寫在了handle方法中,仔細(xì)查看代碼會發(fā)現(xiàn)run方法定義在父類中,在run方法會中會調(diào)用子類中定義的handle方法來完成任務(wù)處理。 嚴(yán)格遵循了面向?qū)ο蟪绦蛟O(shè)計(jì)的SOLID 原則。
結(jié)束應(yīng)用
執(zhí)行完命令程序返回狀態(tài)碼后, 在artisan中會直接通過exit($status)函數(shù)輸出狀態(tài)碼并結(jié)束PHP進(jìn)程,接下來shell進(jìn)程會根據(jù)返回的狀態(tài)碼是否為0來判斷腳本命令是否執(zhí)行成功。
到這里通過命令行開啟的程序進(jìn)程到這里就結(jié)束了,跟HTTP內(nèi)核一樣Console內(nèi)核在整個生命周期中也是負(fù)責(zé)調(diào)度,只不過Http內(nèi)核最終將請求落地到了Controller程序中而Console內(nèi)核則是將命令行請求落地到了Laravel中定義的各種命令類程序中,然后在命令類里面我們就可以寫其他程序一樣自由地使用Laravel中的各個組件和注冊到服務(wù)容器里的服務(wù)了。
本文已經(jīng)收錄在系列文章Laravel源碼學(xué)習(xí)里,歡迎訪問閱讀。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
ThinkPHP 連接Oracle數(shù)據(jù)庫的詳細(xì)教程[全]
最近收集了一些關(guān)于THinkPHP連接Oracle數(shù)據(jù)庫的問題,有很多朋友按照連接mysql的方法來操作,導(dǎo)致有一些方法在Oreale中無法正常使用2012-07-07
thinkPHP框架實(shí)現(xiàn)的簡單計(jì)算器示例
這篇文章主要介紹了thinkPHP框架實(shí)現(xiàn)的簡單計(jì)算器,結(jié)合實(shí)例形式分析了基于thinkPHP框架的用戶登陸、數(shù)值計(jì)算、數(shù)據(jù)庫讀寫、歷史記錄保存等功能相關(guān)實(shí)現(xiàn)技巧與操作注意事項(xiàng),需要的朋友可以參考下2018-12-12
thinkPHP3.1驗(yàn)證碼的簡單實(shí)現(xiàn)方法
這篇文章主要介紹了thinkPHP3.1驗(yàn)證碼的簡單實(shí)現(xiàn)方法,實(shí)例分析了thinkPHP3.1使用圖形類生成驗(yàn)證碼的相關(guān)技巧,需要的朋友可以參考下2016-04-04
PHP實(shí)現(xiàn)頁面靜態(tài)化深入講解
這篇文章主要介紹了PHP實(shí)現(xiàn)頁面靜態(tài)化深入講解,本文講解的很透徹,有感興趣的同學(xué)可以研究下2021-03-03

