# [教程] 掌握命令行的表格输出 > ThinkPHP`V5.1.24`引入了一个新的`Table`对象,用于在命令行下面动态输出表格,刚好最新的`route:list`指令中包含了大部分表格功能的实现,所以是学习Table对象的最好示例,本篇就通过分析该指令的代码来掌握`Table`对象的用法。 [TOC=2,2] ## 初见端倪 `route:list`指令是一个用于查看路由定义的指令,我们先运行下看下在命令行下输出的表格效果,有个感性的认识。 随便在你的路由定义文件中定义一些路由规则,例如下面定义了一个闭包路由、一个资源路由和一个普通路由。 ~~~ Route::get('think', function () { return 'hello,ThinkPHP5!'; }); Route::resource('blog', 'Blog'); Route::get('hello/:name', 'index/hello'); ~~~ 在命令行下面(切换到项目根目录)执行: ~~~ php think route:list ~~~ 你会看到下面的表格输出: ![](https://box.kancloud.cn/3239aeaf0578b814c4df45a095d008c2_541x234.png) > 通过这个指令你可以清晰看到每个资源路由实际上会注册7个路由规则。 是不是觉得很酷😂 当然了这个指令的功能远不止这么简单,我们后面会陆续讲解。 ## 指令寻址 为了分析这个指令,我们先找到`route:list`指令对应的类文件(指令寻址),打开`\think\Console`类,找到`defaultCommands`属性 ![](https://box.kancloud.cn/7e7549a22f5e05dc0310ed2e5b21a293_758x386.png) 这个属性定义了核心内置的所有指令,数组的索引就是指令名,键值就代表了该指令对应的完整类名。可以看到`route:list`指令对应的就是`think\console\command\RouteList`类,接下来就来给大家分析下这个指令的代码实现。 ## 指令定义 任何一个合法的指令类都必须继承系统的`think\console\Command`类,`RouteList`类一共130行左右代码,所以基本上很容易看明白,首先我们看第一个`configure`方法,该方法的作用就是配置当前指令的用法。 ![](https://box.kancloud.cn/5636d6d1f8ace1be12a8e34531bfe78b_861x180.png) `setName`方法就是指定当前类的指令名(注意指令名尽量使用小写,并且不要包含除`:`、`_`之外的特殊字符)。 `addArgument`方法是添加指令参数,这里添加的style参数表示表格输出的样式。该方法的参数可以在`think\console\input\Argument`类的构造方法中查看。 ![](https://box.kancloud.cn/b297863562bf5288d9e42645b5646d1d_848x473.png) 第二个参数表示参数的类型必须或者可选,对于`route:list`指令而言,这个参数是可选的,因此传入`Argument::OPTIONAL`,并且设置了该参赛的默认值是`default`。 `addOption`方法是添加指令选项,这里添加了`sort`和`more`两个选项,并且分别设置了简写。 具体用法则可以在`think\console\input\Option`类的构造方法中看到。 ![](https://box.kancloud.cn/7e5e02b824e8fc572b5a0bb0c040fba2_1089x520.png) `setDescription`方法用于指定当前指令的描述信息。 我们可以用`-h`选项来查看下指令配置的最终效果(可以对任何指令使用`-h`或者`--help`选项来查看指令帮助) ~~~ php think route:list -h ~~~ ![](https://box.kancloud.cn/adbc7ff60c5bcf81de2eabc2545fae24_1000x288.png) ## 拨云见日 第二个方法是`execute`方法,该方法会在指令在执行的时候自动调用。 ![](https://box.kancloud.cn/f422d46bad72e2094d7f57b9a3ef3730_748x229.png) 可以看到`route:list`指令的执行操作很简单,获取路由定义列表的内容(这个时候会同时在命令行输出)后就写入一个`route_list.php`文件中。所有的真相都在`getRouteList()`这个方法里面了,这也是本文的关键所在。 再来深入分析`getRouteList()`方法。 ![](https://box.kancloud.cn/fbad1631b598e633e6dab688395859ac_445x37.png) 这行代码可能很多人不明白,我先解释下。由于ThinkPHP`5.1`的路由采用延时解析机制,如果不开启测试模式的话,可能没有办法获取到所有的路由规则的,尤其是当使用了路由分组和资源路由的时候,实际上都是在路由匹配后才会真正解析分组或者资源路由下面的实际路由规则。而`setTestMode(true)`的作用就是让路由规则实时解析,这样我们才能获取到所有的路由规则。 ![](https://box.kancloud.cn/01e4fed9f0b2a0ea3347684308a7b15f_536x325.png) 这段代码的作用则是获取所有的路由定义文件,如果采用了配置数组方式定义的话也会批量导入。 >[danger] 配置数组方式定义路由规则在`5.1`版本中已经不再建议,在下一个大版本中就会废除,所以尽量不要使用数组方式定义,而改为方法定义 ![](https://box.kancloud.cn/9e1b2886e93684fe37d9c502137ee3af_1103x128.png) 这段代码的作用是在开启注解路由的情况下进行注解路由的解析,有兴趣的朋友可以通过跟踪`think\Build`类的`buildRoute`方法的实现深入了解下注解路由是如何实现的,由于不再文本的讲解范畴,所以暂且不表。 ## 表格输出 前面铺垫了那么多,接下来就要进入正题了(原来讲了这么多废话😄) ![](https://box.kancloud.cn/526e408ca39f80c514a4d3f748784e4c_804x200.png) 这段代码首先实例化了一个`think\console\Table`类,并通过`setHeader`方法设置输出的表头信息。 `setHeader`方法的参数可以参考`Table`类的代码 ![](https://box.kancloud.cn/37418c61a0bcfd7e876f7e453d2cb27a_796x306.png) 第一个参数是表头信息(数组),其实还有第二个参数可以控制表头信息的对齐方式,默认是左对齐,如果要让表头信息居中对齐或者居右,可以使用下面的方式 把 ~~~ $table->setHeader($header); ~~~ 改为居中对齐 ~~~ // 表头居中对齐 $table->setHeader($header, Table::ALIGN_CENTER); ~~~ 再次运行指令后,输出的效果变为: ![](https://box.kancloud.cn/d70b1c8ce79e3edb4ebe30004947316e_533x231.png) 或者居右对齐 ~~~ // 表头居右对齐 $table->setHeader($header, Table::ALIGN_RIGHT); ~~~ ![](https://box.kancloud.cn/e125e6e11e4282b1277b01de1ce0ab80_532x234.png) 这里判断是否传入了`more`选项(参考前面讲到的指令配置部分),如果有则附加额外的表头信息。也就是说,如果使用了`--more`或者`-m`你就能看到关于路由的详细信息(增加了路由参数和变量规则)。 ![](https://box.kancloud.cn/b5c399da60be44317a6623c2cf995cf3_815x232.png) ![](https://box.kancloud.cn/4d1adf6193369c31876defb08dd88ec9_890x358.png) 这段代码的作用是获取路由定义列表数据,事实上,`think\Route`类已经封装好了一个获取已经定义的路由规则列表方法`getRuleList()`,所以在指令中我们不需要自己实现。有兴趣的朋友,可以参考下`think\route\RuleName`类的`setRule()`方法和`getRuleList()`两个方法的代码实现,在此略过不提。 由于`getRouteList()`方法获取的数据是一个三维数组,我们需要重新进行封装为一个Table类可以输出的二维数组格式。同样在封装单元格数据的时候也判断是否使用了`--more`选项而使用不同的数据。最终封装到的二维数组保存到`rows`数组变量中。 ![](https://box.kancloud.cn/e0111f50cfd72167e26b1bcef61ca4fa_526x305.png) 这段代码的作用如果传入了`--sort`选项,则对二维数组进行指定排序排序。 排序的指定方式有两种:一种是直接从0开始的序号,0表示对第一列排序,1表示对第二列排序,以此类推,另外一种方式是对指定的列名进行排序。 例如我们要对第三列(请求类型)进行排序,可以用 ![](https://box.kancloud.cn/39bdebbf243350658a74d47c4014f992_528x230.png) 由于这样不够直观,系统还提供了第二种方式 ![](https://box.kancloud.cn/d0f83a39b2e121c3cf8fc2275eaad33b_526x232.png) ![](https://box.kancloud.cn/604a2d1b868648359614bbf659cecfa4_387x45.png) 这段代码的作用就是给`Table`类实例对象设置表格数据,和`setHeader()`方法一样,`setRows()`方法也支持指定对齐方式。 ![](https://box.kancloud.cn/375bc545ef4a60bb73c94e2e3f4c67b1_787x333.png) 如果我们把 ~~~ $table->setRows($rows); ~~~ 改成: ~~~ // 表格数据居右对齐 $table->setRows($rows, Table::ALIGN_RIGHT); ~~~ 再次运行指令后的输出效果如图: ![](https://box.kancloud.cn/1031bb8fed73ffb21fa584b5737e513c_528x230.png) ![](https://box.kancloud.cn/e86646c52e07fed240f129dd6ede7cd4_471x105.png) 这段代码的作用是使用`setStyle()`方法指定表格输出的样式,我们之前看到的都是默认的样式输出。 现在我们来看下可以支持哪些样式, ![](https://box.kancloud.cn/cf0da2c5a73e913a8c2fdce03a957f88_521x225.png) ![](https://box.kancloud.cn/e7cabd9326d6a01974400b16033ff335_522x227.png) ![](https://box.kancloud.cn/b05eab975397f68939ac4f6cc0ed315d_521x219.png) 通过查看Table类的源码,我们可以看到支持的样式可以包括: 样式名|描述 ---|--- default|默认样式 compact|没有任何表格边框输出 markdown|MARKDOWN格式输出表格 borderless|没有左右边框的表格输出 box|闭合边框输出表格 box-double|双线闭合边框输出表格 > 如果没有指定表格样式或者指定了一个不存在的样式,则默认采用`default`样式风格。 > 最后一步是渲染输出表格,这是调用的是`Command`类封装好的`table`方法,当然你也可以自己参考该方法实现。 ![](https://box.kancloud.cn/4fad3369607acacfeb116f822fceef49_298x52.png) 下面的代码是`Command`类的`table`方法的实现代码。 ![](https://box.kancloud.cn/008c3ec9e5699f3aa2847898be7b3798_402x245.png) 该方法在命令行输出表格之后返回了渲染的表格内容,是为了便于后面写入路由定义列表的内容到`route_list.php`文件,可以随时查看。 有时候需要实现一个跨行跨列的输出,例如实现一个跨列的单元格,我们在 ~~~ $table->setRows($rows); ~~~ 后面添加如下代码: ~~~ // 跨列数据直接传入字符串 $table->addRow('Extra Route List'); $table->addRow(['test/:name', 'index/test', '*', 'test', '']); ~~~ 现在运行指令后输出效果如图: ![](https://box.kancloud.cn/d97bab849e799a5a9130ef2d62056628_526x296.png) > 目前Table尚不支持跨部分列的表格输出 ## 总结 总结下来,`Table`类的用法其实很简单 ~~~ // 实例化一个Table对象 $table = new Table(); // 设置表头(可选) $table->setHeader(); // 设置表格数据 $table->setRows(); // 添加单行数据(可选) $table->addRow(); // 设置表格样式(可选) $table->setStyle(); // 渲染表格输出 $content = $table->render(); ~~~ 其实不仅仅是用于命令行输出,你还可以进行文件写入的时候生成表格数据内容。