浅谈Laravel核心解读之Console内核

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

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

(0)

相关推荐

  • 浅谈Laravel核心解读之Console内核

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

  • 浅谈laravel中间件的创建思路

    Laravel 中间件提供了一种机制在不修改逻辑代码的情况下,中断原本程序流程,通过中间件来处理一些事件,或者扩展一些功能.比如日志中间件可以方便的记录请求和响应日志,而不需要去更改逻辑代码. 那么我们简化一下软件执行过程,现在有一个核心类kernel,下面是它的laravel代码 #捕获请求 $request = Illuminate\Http\Request::capture() #处理请求 $response = $kernel->handle($request); 代码的作用是 捕获一个

  • 浅谈Laravel模板实体转义带来的坑

    问题 最近在Laravel项目中用到了百度编辑器,插入到数据库我保存的是原始的html标签代码,没有进行实体转义.然后在修改的时候,需要读取到数据库中的数据,进行回显,这时候竟然在编辑器里面显示html标签代码<p>123</p>,这让我很尴尬,因为以前在tp框架中也是这样写的,但是没有问题. 搜索之路 在知道问题之后,我就开始找百度了,因为一开始的时候我并不知道是框架的原因,我以为是百度编辑器版本的原因,然后收到了许多答案,都是围绕着htmlentities和html_entit

  • 浅谈Laravel POST,PUT,PATCH 路由的区别

    经常会混淆HTTP的POST/PUT方法,因为这两个方法似乎都可以用来创建或更新一个资源. 区别是细微但清楚的: POST方法用来创建一个子资源,如 /api/users,会在users下面创建一个user,如users/1 POST方法不是幂等的,多次执行,将导致多条相同的用户被创建(users/1,users/2 -而这些用户除了自增长id外有着相同的数据,除非你的系统实现了额外的数据唯一性检查) 而PUT方法用来创建一个URI已知的资源,或对已知资源进行完全替换,比如users/1, 因此

  • 浅谈laravel数据库查询返回的数据形式

    版本:laravel5.4+ 问题描述:laravel数据库查询返回的数据不是单纯的数组形式,而是数组与类似stdClass Object这种对象的结合体,即使在查询构造器中调用了toArray(),也无法转换成单纯的数组形式. 问题解析: (以上图片来源于laravel学院5.3版本到5.4版本的升级手册) 如上图所示:Laravel不再支持在配置文件中定制PDO的"fetch mode",取而代之,总是使用PDO::FETCH_OBJ,如果你仍然想要为应用定制fetch模式,需要监

  • 浅谈laravel框架sql中groupBy之后排序的问题

    最近在用框架给公司App写接口时,碰到了一个棘手的问题: 对查询结果进行排序并进行分页(进行了简略修改),下面是最终结果代码: $example = Example::select(DB::raw('max(id) as some_id,this_id')) ->where('id', $id) ->groupBy('this_id') ->orderBy('some_id', 'desc') ->skip($offset) ->take($limit) ->get()

  • 浅谈laravel框架与thinkPHP框架的区别

    主要区别:(thinkPHP更适合国人的编码习惯) 1.渲染模版方式的不同: 在Laravel框架里,使用return view()来渲染模版; 而ThinkPHP里则使用了$this->display()的方式渲染模版; 2.在Laravel框架里,由于其考虑到了跨站请求伪造, 所以如果使用form表单以post方式进行传值时,如果不再form表单中加入{{csrf_field()}}则会报出TokenMethodnotfound的语法错误; 而TP框架则需要自己手动完成防止跨站攻击的代码;

  • 浅谈Pycharm中的Python Console与Terminal

    Pycharm的下方工具栏中有两个窗口:Python Console和Terminal(如下图) 其中,Python Console叫做Python控制台,即Python交互模式:Terminal叫做终端,即命令行模式. Python交互模式主要有两种:CPython用>>>作为提示符,而IPython用In [序号]:作为提示符. Python交互式模式可以直接输入代码,然后执行,并立刻得到结果,因此Python交互模式主要是为了调试Python代码用的. 命令行模式与系统的CMD(命

  • 浅谈laravel中的关联查询with的问题

    表结构 主表结构: Create Table CREATE TABLE `user` ( `uid` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `email` varbinary(255) NOT NULL, PRIMARY KEY (`uid`) ) ENGINE=InnoDB AUTO_INCREMENT=114001 DEFA

  • 浅谈laravel 5.6 安装 windows上使用composer的安装过程

    在介绍下面的时候,先看一下 laravel 5.6 的环境要求 所以大家的php版本一定不要小于 7.1.3 ,我本地使用的是wamp 3.1.0 64位, php可以选择 7.1.9 一.下载compser 由于一些电脑直接下载composer.exe安装时会有很多问题,所以建议使用命令行安装,我在 E盘 下面建了个composer文件夹 ,打开cmd,进入 这个composer文件夹 1.执行以下命令: php -r "copy('https://getcomposer.org/instal

随机推荐