3
* @link https://www.yiiframework.com/
4
* @copyright Copyright (c) 2008 Yii Software LLC
5
* @license https://www.yiiframework.com/license/
8
namespace yii\console\widgets;
12
use yii\helpers\ArrayHelper;
13
use yii\helpers\Console;
16
* Table class displays a table in console.
21
* $table = new Table();
24
* ->setHeaders(['test1', 'test2', 'test3'])
26
* ['col1', 'col2', 'col3'],
27
* ['col1', 'col2', ['col3-0', 'col3-1', 'col3-2']],
35
* echo Table::widget([
36
* 'headers' => ['test1', 'test2', 'test3'],
38
* ['col1', 'col2', 'col3'],
39
* ['col1', 'col2', ['col3-0', 'col3-1', 'col3-2']],
43
* @property-write string $listPrefix List prefix.
44
* @property-write int $screenWidth Screen width.
46
* @author Daniel Gomez Pan <pana_1990@hotmail.com>
49
class Table extends Widget
51
const DEFAULT_CONSOLE_SCREEN_WIDTH = 120;
52
const CONSOLE_SCROLLBAR_OFFSET = 3;
53
const CHAR_TOP = 'top';
54
const CHAR_TOP_MID = 'top-mid';
55
const CHAR_TOP_LEFT = 'top-left';
56
const CHAR_TOP_RIGHT = 'top-right';
57
const CHAR_BOTTOM = 'bottom';
58
const CHAR_BOTTOM_MID = 'bottom-mid';
59
const CHAR_BOTTOM_LEFT = 'bottom-left';
60
const CHAR_BOTTOM_RIGHT = 'bottom-right';
61
const CHAR_LEFT = 'left';
62
const CHAR_LEFT_MID = 'left-mid';
63
const CHAR_MID = 'mid';
64
const CHAR_MID_MID = 'mid-mid';
65
const CHAR_RIGHT = 'right';
66
const CHAR_RIGHT_MID = 'right-mid';
67
const CHAR_MIDDLE = 'middle';
70
* @var array table headers
73
protected $headers = [];
75
* @var array table rows
80
* @var array table chars
84
self::CHAR_TOP => '═',
85
self::CHAR_TOP_MID => '╤',
86
self::CHAR_TOP_LEFT => '╔',
87
self::CHAR_TOP_RIGHT => '╗',
88
self::CHAR_BOTTOM => '═',
89
self::CHAR_BOTTOM_MID => '╧',
90
self::CHAR_BOTTOM_LEFT => '╚',
91
self::CHAR_BOTTOM_RIGHT => '╝',
92
self::CHAR_LEFT => '║',
93
self::CHAR_LEFT_MID => '╟',
94
self::CHAR_MID => '─',
95
self::CHAR_MID_MID => '┼',
96
self::CHAR_RIGHT => '║',
97
self::CHAR_RIGHT_MID => '╢',
98
self::CHAR_MIDDLE => '│',
101
* @var array table column widths
104
protected $columnWidths = [];
106
* @var int screen width
109
protected $screenWidth;
111
* @var string list prefix
114
protected $listPrefix = '• ';
120
* @param array $headers table headers
123
public function setHeaders(array $headers)
125
$this->headers = array_values($headers);
132
* @param array $rows table rows
135
public function setRows(array $rows)
137
$this->rows = array_map(function($row) {
138
return array_map(function($value) {
139
return empty($value) && !is_numeric($value)
142
? array_values($value)
144
}, array_values($row));
152
* @param array $chars table chars
155
public function setChars(array $chars)
157
$this->chars = $chars;
164
* @param int $width screen width
167
public function setScreenWidth($width)
169
$this->screenWidth = $width;
176
* @param string $listPrefix list prefix
179
public function setListPrefix($listPrefix)
181
$this->listPrefix = $listPrefix;
186
* @return string the rendered table
188
public function run()
190
$this->calculateRowsSize();
191
$headerCount = count($this->headers);
193
$buffer = $this->renderSeparator(
194
$this->chars[self::CHAR_TOP_LEFT],
195
$this->chars[self::CHAR_TOP_MID],
196
$this->chars[self::CHAR_TOP],
197
$this->chars[self::CHAR_TOP_RIGHT]
200
if ($headerCount > 0) {
201
$buffer .= $this->renderRow($this->headers,
202
$this->chars[self::CHAR_LEFT],
203
$this->chars[self::CHAR_MIDDLE],
204
$this->chars[self::CHAR_RIGHT]
209
foreach ($this->rows as $i => $row) {
210
if ($i > 0 || $headerCount > 0) {
211
$buffer .= $this->renderSeparator(
212
$this->chars[self::CHAR_LEFT_MID],
213
$this->chars[self::CHAR_MID_MID],
214
$this->chars[self::CHAR_MID],
215
$this->chars[self::CHAR_RIGHT_MID]
218
$buffer .= $this->renderRow($row,
219
$this->chars[self::CHAR_LEFT],
220
$this->chars[self::CHAR_MIDDLE],
221
$this->chars[self::CHAR_RIGHT]);
224
$buffer .= $this->renderSeparator(
225
$this->chars[self::CHAR_BOTTOM_LEFT],
226
$this->chars[self::CHAR_BOTTOM_MID],
227
$this->chars[self::CHAR_BOTTOM],
228
$this->chars[self::CHAR_BOTTOM_RIGHT]
235
* Renders a row of data into a string.
237
* @param array $row row of data
238
* @param string $spanLeft character for left border
239
* @param string $spanMiddle character for middle border
240
* @param string $spanRight character for right border
242
* @see \yii\console\widgets\Table::render()
244
protected function renderRow(array $row, $spanLeft, $spanMiddle, $spanRight)
246
$size = $this->columnWidths;
250
$renderedChunkTexts = [];
251
for ($i = 0, ($max = $this->calculateRowHeight($row)) ?: $max = 1; $i < $max; $i++) {
252
$buffer .= $spanLeft . ' ';
253
foreach ($size as $index => $cellSize) {
254
$cell = isset($row[$index]) ? $row[$index] : null;
257
$buffer .= $spanMiddle . ' ';
260
$arrayFromMultilineString = false;
261
if (is_string($cell)) {
262
$cellLines = explode(PHP_EOL, $cell);
263
if (count($cellLines) > 1) {
265
$arrayFromMultilineString = true;
269
if (is_array($cell)) {
270
if (empty($renderedChunkTexts[$index])) {
271
$renderedChunkTexts[$index] = '';
273
$prefix = $arrayFromMultilineString ? '' : $this->listPrefix;
274
if (!isset($arrayPointer[$index])) {
275
$arrayPointer[$index] = 0;
278
$start = mb_strwidth($renderedChunkTexts[$index], Yii::$app->charset);
280
$chunk = Console::ansiColorizedSubstr(
281
$cell[$arrayPointer[$index]],
283
$cellSize - 2 - Console::ansiStrwidth($prefix)
285
$renderedChunkTexts[$index] .= Console::stripAnsiFormat($chunk);
286
$fullChunkText = Console::stripAnsiFormat($cell[$arrayPointer[$index]]);
287
if (isset($cell[$arrayPointer[$index] + 1]) && $renderedChunkTexts[$index] === $fullChunkText) {
288
$arrayPointer[$index]++;
289
$renderedChunkTexts[$index] = '';
292
$chunk = Console::ansiColorizedSubstr($cell, ($cellSize * $i) - ($i * 2), $cellSize - 2);
294
$chunk = $prefix . $chunk;
295
$repeat = $cellSize - Console::ansiStrwidth($chunk) - 1;
298
$buffer .= str_repeat(' ', $repeat);
301
$buffer .= "$spanRight\n";
310
* @param string $spanLeft character for left border
311
* @param string $spanMid character for middle border
312
* @param string $spanMidMid character for middle-middle border
313
* @param string $spanRight character for right border
314
* @return string the generated separator row
315
* @see \yii\console\widgets\Table::render()
317
protected function renderSeparator($spanLeft, $spanMid, $spanMidMid, $spanRight)
319
$separator = $spanLeft;
320
foreach ($this->columnWidths as $index => $rowSize) {
322
$separator .= $spanMid;
324
$separator .= str_repeat($spanMidMid, $rowSize);
326
$separator .= $spanRight . "\n";
331
* Calculate the size of rows to draw anchor of columns in console.
333
* @see \yii\console\widgets\Table::render()
335
protected function calculateRowsSize()
337
$this->columnWidths = $columns = [];
339
$screenWidth = $this->getScreenWidth() - self::CONSOLE_SCROLLBAR_OFFSET;
341
$headerCount = count($this->headers);
342
if (empty($this->rows)) {
345
$rowColCount = max(array_map('count', $this->rows));
347
$count = max($headerCount, $rowColCount);
348
for ($i = 0; $i < $count; $i++) {
349
$columns[] = ArrayHelper::getColumn($this->rows, $i);
350
if ($i < $headerCount) {
351
$columns[$i][] = $this->headers[$i];
355
foreach ($columns as $column) {
356
$columnWidth = max(array_map(function ($val) {
357
if (is_array($val)) {
358
return max(array_map('yii\helpers\Console::ansiStrwidth', $val)) + Console::ansiStrwidth($this->listPrefix);
360
if (is_string($val)) {
361
return max(array_map('yii\helpers\Console::ansiStrwidth', explode(PHP_EOL, $val)));
363
return Console::ansiStrwidth($val);
365
$this->columnWidths[] = $columnWidth;
366
$totalWidth += $columnWidth;
369
if ($totalWidth > $screenWidth) {
372
$relativeWidth = $screenWidth / $totalWidth;
373
foreach ($this->columnWidths as $j => $width) {
374
$scaledWidth = (int) ($width * $relativeWidth);
375
if ($scaledWidth < $minWidth) {
380
$totalFixWidth = array_sum($fixWidths);
381
$relativeWidth = ($screenWidth - $totalFixWidth) / ($totalWidth - $totalFixWidth);
382
foreach ($this->columnWidths as $j => $width) {
383
if (!array_key_exists($j, $fixWidths)) {
384
$this->columnWidths[$j] = (int) ($width * $relativeWidth);
391
* Calculate the height of a row.
394
* @return int maximum row per cell
395
* @see \yii\console\widgets\Table::render()
397
protected function calculateRowHeight($row)
399
$rowsPerCell = array_map(function ($size, $columnWidth) {
400
if (is_array($columnWidth)) {
402
foreach ($columnWidth as $width) {
403
$rows += $size == 2 ? 0 : ceil($width / ($size - 2));
407
return $size == 2 || $columnWidth == 0 ? 0 : ceil($columnWidth / ($size - 2));
408
}, $this->columnWidths, array_map(function ($val) {
409
if (is_array($val)) {
410
return array_map('yii\helpers\Console::ansiStrwidth', $val);
412
if (is_string($val)) {
413
return array_map('yii\helpers\Console::ansiStrwidth', explode(PHP_EOL, $val));
415
return Console::ansiStrwidth($val);
417
return max($rowsPerCell);
421
* Getting screen width.
422
* If it is not able to determine screen width, default value `123` will be set.
424
* @return int screen width
426
protected function getScreenWidth()
428
if (!$this->screenWidth) {
429
$size = Console::getScreenSize();
430
$this->screenWidth = isset($size[0])
432
: self::DEFAULT_CONSOLE_SCREEN_WIDTH + self::CONSOLE_SCROLLBAR_OFFSET;
434
return $this->screenWidth;