上一篇文章咱们介绍了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核心代码学习中,欢迎访问阅读,多多交流。