Console内核

上一篇文章我们介绍了Laravel的HTTP内核,详细概述了网络请求从进入应用到应用处理完请求返回HTTP响应整个生命周期中HTTP内核是如何调动Laravel各个核心组件来完成任务的。除了处理HTTP请求一个健壮的应用经常还会需要执行计划任务、异步队列这些。Laravel为了能让应用满足这些场景设计了artisan工具,通过artisan工具定义各种命令来满足非HTTP请求的各种场景,artisan命令通过Laravel的Console内核来完成对应用核心组件的调度来完成任务。 今天我们就来学习一下Laravel Console内核的核心代码。

内核绑定

跟HTTP内核一样,在应用初始化阶有一个内核绑定的过程,将Console内核注册到应用的服务容器里去,还是引用上一篇文章引用过的bootstrap/app.php里的代码


<?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命令和定义应用里要执行的计划任务。


/**
* 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方法中定义的计划任务)


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();
});
}

应用解析Console内核

查看aritisan文件的源码我们可以看到, 完成Console内核绑定的绑定后,接下来就会通过服务容器解析出console内核对象


$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对象来抽象化命令行的标准输出。

引导应用

在Console内核的handle方法里我们可以看到和HTTP内核处理请求前使用bootstrapper程序引用应用一样在开始处理命令任务之前也会有引导应用这一步操作

其父类 「IlluminateFoundationConsoleKernel」 内部定义了属性名为 「bootstrappers」 的 引导程序 数组:


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源码学习里,欢迎访问阅读。

原文地址:https://segmentfault.com/a/1190000017218756

Laravel核心解读--Console内核的更多相关文章

  1. Laravel核心解读--HTTP内核

    Http Kernel Http Kernel是Laravel中用来串联框架的各个核心组件来网络请求的,简单的说只要是通过public/index.php来启动框架的都会用到Http Kernel,而 ...

  2. Laravel核心解读--ENV的加载和读取

    Laravel在启动时会加载项目中的.env文件.对于应用程序运行的环境来说,不同的环境有不同的配置通常是很有用的. 例如,你可能希望在本地使用测试的Mysql数据库而在上线后希望项目能够自动切换到生 ...

  3. Laravel核心解读--Contracts契约

    Contracts Laravel 的契约是一组定义框架提供的核心服务的接口, 例如我们在介绍用户认证的章节中到的用户看守器契约IllumninateContractsAuthGuard 和用户提供器 ...

  4. Laravel核心解读--异常处理

    异常处理是编程中十分重要但也最容易被人忽视的语言特性,它为开发者提供了处理程序运行时错误的机制,对于程序设计来说正确的异常处理能够防止泄露程序自身细节给用户,给开发者提供完整的错误回溯堆栈,同时也能提 ...

  5. Laravel核心解读 -- 扩展用户认证系统

    扩展用户认证系统 上一节我们介绍了Laravel Auth系统实现的一些细节知道了Laravel是如何应用看守器和用户提供器来进行用户认证的,但是针对我们自己开发的项目或多或少地我们都会需要在自带的看 ...

  6. Laravel 核心--Facades 门面

    Laravel 核心--Facades 门面 伊Summer 关注  0.1 2017.08.12 19:07* 字数 2017 阅读 1089评论 0喜欢 5 介绍 Facades 为应用的 IoC ...

  7. Laravel 核心概念

    工欲善其事,必先利其器.在开发Xblog的过程中,稍微领悟了一点Laravel的思想.确实如此,这篇文章读完你可能并不能从无到有写出一个博客,但知道Laravel的核心概念之后,当你再次写起Larav ...

  8. Redis核心解读:集群管理工具(Redis-sentinel)

    Redis核心解读:集群管理工具(Redis-sentinel) - Redis - TechTarget数据库 Redis核心解读:集群管理工具(Redis-sentinel)

  9. Redis核心解读(转)

    原文:Redis核心解读 Redis是知名的键值数据库,它广泛用于缓存系统.关于Redis的信息已经不用我多介绍了.这个系统的Redis文章主要从另外一个角度关注,Redis作为一个开源项目,短短2W ...

随机推荐

  1. 【SPOJ】Substrings

    出现次数很好处理,就是 \(right/endpos\) 集合的大小 那么,直接构建 \(SAM\) 求出每个位置的\(right\)集合大小 直接更新每个节点的\(longest\)就行了 最后短的 ...

  2. HDU-1281-棋盘游戏(最大匹配,枚举)

    链接:https://vjudge.net/problem/HDU-1281 题目: 小希和Gardon在玩一个游戏:对一个N*M的棋盘,在格子里放尽量多的一些国际象棋里面的“车”,并且使得他们不能互 ...

  3. UVaLive6435(dp)

    要点 题意:两个数据传输数列,每个数列里有若干个数据包并给出发出时间\(t\),每个数据包到达的时间\(T\)是\(t <= T < t + D\),问有多少种到达序列. 将题目转化为第二 ...

  4. Spring注入bean和aop的注意事项

    spring注入类没有构造函数,注入成功抽象类,注入失败不写bean注入的名字,默认是bean第一个字母小写的名字,但是bean名字开头是两个大写,则默认是bean的名字前面所有大写都变小写@Auto ...

  5. 关于小米手机USB传输稍大点的文件老中断的问题解决方法!

    关于小米手机USB传输稍大点的文件老中断的问题解决方法! 这是一个很痛苦的事情,当你传输大文件的时候,传输到一半就会莫名其妙的中断,拔插数据线很多次以后,好不容易没准可以成功传输一次. 后来使用了36 ...

  6. 《四 spring源码》手写springmvc

    手写SpringMVC思路 1.web.xml加载  为了读取web.xml中的配置,我们用到ServletConfig这个类,它代表当前Servlet在web.xml中的配置信息.通过web.xml ...

  7. IDEA2017 配置Maven

    配置本地仓库位置 配置一下Maven的本地仓库路径,首先找到解压Maven的目录,找到conf -> settings.xml这个配置文件打开. 打开settings.xml 配置文件,选一个本 ...

  8. Android 下的 SQLite 操作封装 —— DatabaseUtil

    看到别人写的代码不错,对自己目前的开发很有用,所以转载一下,希望也能帮助到其他人: 1.DatabaseUtil.java(封装的类) package com.dbexample; import an ...

  9. ios获取数据之encodeURI 和 decodeURI

    在APP开发过程中,免不了要进行ios的数据处理,在ios传递数据的过程中,会出现JSON数据获取不到的情况,这时候就轮到encodeURI 和 decodeURI出马了. 1.encodeURI,d ...

  10. JavaScript笔记4-数组

    一.概述: 1.数组是无类型的:同一数组的各元素可以是任意类型,也可以是数组或对象; 2.索引从0开始,最大到2^32-2=4294967294;最多容纳4294967295个元素; 3.数组是动态的 ...