浅谈Laravel核心解读之Console内核


Posted in PHP onDecember 02, 2018

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

PHP 相关文章推荐
开发大型PHP项目的方法
Oct 09 PHP
php判断输入不超过mysql的varchar字段的长度范围
Jun 24 PHP
php在程序中将网页生成word文档并提供下载的代码
Oct 09 PHP
js和php邮箱地址验证的实现方法
Jan 09 PHP
ThinkPHP使用心得分享-ThinkPHP + Ajax 实现2级联动下拉菜单
May 15 PHP
Laravel 5 学习笔记
Mar 06 PHP
轻松掌握php设计模式之访问者模式
Sep 23 PHP
浅谈PHP的exec()函数无返回值排查方法(必看)
Mar 31 PHP
详解php框架Yaf路由重写
Jun 20 PHP
thinkPHP框架通过Redis实现增删改查操作的方法详解
May 13 PHP
php实现的支付宝网页支付功能示例【基于TP5框架】
Sep 16 PHP
PHP7 整型处理机制修改
Mar 09 PHP
Laravel使用scout集成elasticsearch做全文搜索的实现方法
Nov 30 #PHP
用Laravel Sms实现laravel短信验证码的发送的实现
Nov 29 #PHP
php实现每日签到功能
Nov 29 #PHP
PHP序列化的四种实现方法与横向对比
Nov 29 #PHP
PHP中如何使用Redis接管文件存储Session详解
Nov 28 #PHP
php基于Redis消息队列实现的消息推送的方法
Nov 28 #PHP
php获取用户真实IP和防刷机制的实例代码
Nov 28 #PHP
You might like
php addslashes 函数详细分析说明
2009/06/23 PHP
PHP 采集程序原理分析篇
2010/03/05 PHP
php 无法加载mysql的module的时候的配置的解决方案引发的思考
2012/01/27 PHP
php如何获取文件的扩展名
2015/10/28 PHP
Laravel SQL语句记录方式(推荐)
2016/05/26 PHP
php实现跨域提交form表单的方法【2种方法】
2016/10/17 PHP
PHP中$GLOBALS['HTTP_RAW_POST_DATA']和$_POST的区别分析
2017/07/03 PHP
Javascript动态绑定事件的简单实现代码
2010/12/25 Javascript
EasyUI 中 MenuButton 的使用方法
2012/07/14 Javascript
javascript生成json数据简单示例分享
2014/02/14 Javascript
JQuery设置获取下拉菜单某个选项的值(比较全)
2014/08/05 Javascript
一个字符串中出现次数最多的字符 统计这个次数【实现代码】
2016/04/29 Javascript
Javascript 基础---Ajax入门必看
2016/07/06 Javascript
浅谈JavaScript 数据属性和访问器属性
2016/09/01 Javascript
AngularJS验证信息框架的封装插件用法【w5cValidator扩展插件】
2016/11/03 Javascript
canvas绘制的直线动画
2017/01/23 Javascript
nodejs中安装ghost出错的原因及解决方法
2017/10/23 NodeJs
微信小程序 swiper 组件遇到的问题及解决方法
2019/05/26 Javascript
Node.js使用MongoDB的ObjectId作为查询条件的方法
2019/09/10 Javascript
mpvue实现左侧导航与右侧内容的联动
2019/10/21 Javascript
JS数组降维的实现Array.prototype.concat.apply([], arr)
2020/04/28 Javascript
python下调用pytesseract识别某网站验证码的实现方法
2016/06/06 Python
python中的随机函数小结
2018/01/27 Python
Python爬虫框架Scrapy常用命令总结
2018/07/26 Python
对python中大文件的导入与导出方法详解
2018/12/28 Python
使用python PIL库实现简单验证码的去噪方法步骤
2019/05/10 Python
HTML5 Canvas图像模糊完美解决办法
2018/02/06 HTML / CSS
移动端html5模拟长按事件的实现方法
2018/09/30 HTML / CSS
澳洲网红粉泥面膜:Sand & Sky
2019/08/13 全球购物
酒店保安领班职务说明书
2014/03/04 职场文书
银行授权委托书范本
2014/10/04 职场文书
2015高考寄语集锦
2015/02/27 职场文书
介绍信范文大全
2015/05/07 职场文书
红色革命电影观后感
2015/06/18 职场文书
聘任合同书
2015/09/21 职场文书
2016年校园重阳节广播稿
2015/12/18 职场文书