setHeaders(['test1', 'test2', 'test3']) * ->setRows([ * ['col1', 'col2', 'col3'], * ['col1', 'col2', ['col3-0', 'col3-1', 'col3-2']], * ]) * ->run(); * ``` * * or * * ```php * echo Table::widget([ * 'headers' => ['test1', 'test2', 'test3'], * 'rows' => [ * ['col1', 'col2', 'col3'], * ['col1', 'col2', ['col3-0', 'col3-1', 'col3-2']], * ], * ]); * * @author Daniel Gomez Pan * @since 2.0.13 */ class Table extends Widget { const DEFAULT_CONSOLE_SCREEN_WIDTH = 120; const CONSOLE_SCROLLBAR_OFFSET = 3; const CHAR_TOP = 'top'; const CHAR_TOP_MID = 'top-mid'; const CHAR_TOP_LEFT = 'top-left'; const CHAR_TOP_RIGHT = 'top-right'; const CHAR_BOTTOM = 'bottom'; const CHAR_BOTTOM_MID = 'bottom-mid'; const CHAR_BOTTOM_LEFT = 'bottom-left'; const CHAR_BOTTOM_RIGHT = 'bottom-right'; const CHAR_LEFT = 'left'; const CHAR_LEFT_MID = 'left-mid'; const CHAR_MID = 'mid'; const CHAR_MID_MID = 'mid-mid'; const CHAR_RIGHT = 'right'; const CHAR_RIGHT_MID = 'right-mid'; const CHAR_MIDDLE = 'middle'; /** * @var array table headers * @since 2.0.19 */ protected $headers = []; /** * @var array table rows * @since 2.0.19 */ protected $rows = []; /** * @var array table chars * @since 2.0.19 */ protected $chars = [ self::CHAR_TOP => '═', self::CHAR_TOP_MID => '╤', self::CHAR_TOP_LEFT => '╔', self::CHAR_TOP_RIGHT => '╗', self::CHAR_BOTTOM => '═', self::CHAR_BOTTOM_MID => '╧', self::CHAR_BOTTOM_LEFT => '╚', self::CHAR_BOTTOM_RIGHT => '╝', self::CHAR_LEFT => '║', self::CHAR_LEFT_MID => '╟', self::CHAR_MID => '─', self::CHAR_MID_MID => '┼', self::CHAR_RIGHT => '║', self::CHAR_RIGHT_MID => '╢', self::CHAR_MIDDLE => '│', ]; /** * @var array table column widths * @since 2.0.19 */ protected $columnWidths = []; /** * @var int screen width * @since 2.0.19 */ protected $screenWidth; /** * @var string list prefix * @since 2.0.19 */ protected $listPrefix = '• '; /** * Set table headers. * * @param array $headers table headers * @return $this */ public function setHeaders(array $headers) { $this->headers = array_values($headers); return $this; } /** * Set table rows. * * @param array $rows table rows * @return $this */ public function setRows(array $rows) { $this->rows = array_map('array_values', $rows); return $this; } /** * Set table chars. * * @param array $chars table chars * @return $this */ public function setChars(array $chars) { $this->chars = $chars; return $this; } /** * Set screen width. * * @param int $width screen width * @return $this */ public function setScreenWidth($width) { $this->screenWidth = $width; return $this; } /** * Set list prefix. * * @param string $listPrefix list prefix * @return $this */ public function setListPrefix($listPrefix) { $this->listPrefix = $listPrefix; return $this; } /** * @return string the rendered table */ public function run() { $this->calculateRowsSize(); $headerCount = count($this->headers); $buffer = $this->renderSeparator( $this->chars[self::CHAR_TOP_LEFT], $this->chars[self::CHAR_TOP_MID], $this->chars[self::CHAR_TOP], $this->chars[self::CHAR_TOP_RIGHT] ); // Header if ($headerCount > 0) { $buffer .= $this->renderRow($this->headers, $this->chars[self::CHAR_LEFT], $this->chars[self::CHAR_MIDDLE], $this->chars[self::CHAR_RIGHT] ); } // Content foreach ($this->rows as $i => $row) { if ($i > 0 || $headerCount > 0) { $buffer .= $this->renderSeparator( $this->chars[self::CHAR_LEFT_MID], $this->chars[self::CHAR_MID_MID], $this->chars[self::CHAR_MID], $this->chars[self::CHAR_RIGHT_MID] ); } $buffer .= $this->renderRow($row, $this->chars[self::CHAR_LEFT], $this->chars[self::CHAR_MIDDLE], $this->chars[self::CHAR_RIGHT]); } $buffer .= $this->renderSeparator( $this->chars[self::CHAR_BOTTOM_LEFT], $this->chars[self::CHAR_BOTTOM_MID], $this->chars[self::CHAR_BOTTOM], $this->chars[self::CHAR_BOTTOM_RIGHT] ); return $buffer; } /** * Renders a row of data into a string. * * @param array $row row of data * @param string $spanLeft character for left border * @param string $spanMiddle character for middle border * @param string $spanRight character for right border * @return string * @see \yii\console\widgets\Table::render() */ protected function renderRow(array $row, $spanLeft, $spanMiddle, $spanRight) { $size = $this->columnWidths; $buffer = ''; $arrayPointer = []; $finalChunk = []; for ($i = 0, ($max = $this->calculateRowHeight($row)) ?: $max = 1; $i < $max; $i++) { $buffer .= $spanLeft . ' '; foreach ($size as $index => $cellSize) { $cell = isset($row[$index]) ? $row[$index] : null; $prefix = ''; if ($index !== 0) { $buffer .= $spanMiddle . ' '; } if (is_array($cell)) { if (empty($finalChunk[$index])) { $finalChunk[$index] = ''; $start = 0; $prefix = $this->listPrefix; if (!isset($arrayPointer[$index])) { $arrayPointer[$index] = 0; } } else { $start = mb_strwidth($finalChunk[$index], Yii::$app->charset); } $chunk = mb_substr($cell[$arrayPointer[$index]], $start, $cellSize - 4, Yii::$app->charset); $finalChunk[$index] .= $chunk; if (isset($cell[$arrayPointer[$index] + 1]) && $finalChunk[$index] === $cell[$arrayPointer[$index]]) { $arrayPointer[$index]++; $finalChunk[$index] = ''; } } else { $chunk = mb_substr($cell, ($cellSize * $i) - ($i * 2), $cellSize - 2, Yii::$app->charset); } $chunk = $prefix . $chunk; $repeat = $cellSize - mb_strwidth($chunk, Yii::$app->charset) - 1; $buffer .= $chunk; if ($repeat >= 0) { $buffer .= str_repeat(' ', $repeat); } } $buffer .= "$spanRight\n"; } return $buffer; } /** * Renders separator. * * @param string $spanLeft character for left border * @param string $spanMid character for middle border * @param string $spanMidMid character for middle-middle border * @param string $spanRight character for right border * @return string the generated separator row * @see \yii\console\widgets\Table::render() */ protected function renderSeparator($spanLeft, $spanMid, $spanMidMid, $spanRight) { $separator = $spanLeft; foreach ($this->columnWidths as $index => $rowSize) { if ($index !== 0) { $separator .= $spanMid; } $separator .= str_repeat($spanMidMid, $rowSize); } $separator .= $spanRight . "\n"; return $separator; } /** * Calculate the size of rows to draw anchor of columns in console. * * @see \yii\console\widgets\Table::render() */ protected function calculateRowsSize() { $this->columnWidths = $columns = []; $totalWidth = 0; $screenWidth = $this->getScreenWidth() - self::CONSOLE_SCROLLBAR_OFFSET; $headerCount = count($this->headers); if (empty($this->rows)) { $rowColCount = 0; } else { $rowColCount = max(array_map('count', $this->rows)); } $count = max($headerCount, $rowColCount); for ($i = 0; $i < $count; $i++) { $columns[] = ArrayHelper::getColumn($this->rows, $i); if ($i < $headerCount) { $columns[$i][] = $this->headers[$i]; } } foreach ($columns as $column) { $columnWidth = max(array_map(function ($val) { if (is_array($val)) { $encodings = array_fill(0, count($val), Yii::$app->charset); return max(array_map('mb_strwidth', $val, $encodings)) + mb_strwidth($this->listPrefix, Yii::$app->charset); } return mb_strwidth($val, Yii::$app->charset); }, $column)) + 2; $this->columnWidths[] = $columnWidth; $totalWidth += $columnWidth; } $relativeWidth = $screenWidth / $totalWidth; if ($totalWidth > $screenWidth) { foreach ($this->columnWidths as $j => $width) { $this->columnWidths[$j] = (int) ($width * $relativeWidth); if ($j === count($this->columnWidths)) { $this->columnWidths = $totalWidth; } $totalWidth -= $this->columnWidths[$j]; } } } /** * Calculate the height of a row. * * @param array $row * @return int maximum row per cell * @see \yii\console\widgets\Table::render() */ protected function calculateRowHeight($row) { $rowsPerCell = array_map(function ($size, $columnWidth) { if (is_array($columnWidth)) { $rows = 0; foreach ($columnWidth as $width) { $rows += ceil($width / ($size - 2)); } return $rows; } return ceil($columnWidth / ($size - 2)); }, $this->columnWidths, array_map(function ($val) { if (is_array($val)) { $encodings = array_fill(0, count($val), Yii::$app->charset); return array_map('mb_strwidth', $val, $encodings); } return mb_strwidth($val, Yii::$app->charset); }, $row) ); return max($rowsPerCell); } /** * Getting screen width. * If it is not able to determine screen width, default value `123` will be set. * * @return int screen width */ protected function getScreenWidth() { if (!$this->screenWidth) { $size = Console::getScreenSize(); $this->screenWidth = isset($size[0]) ? $size[0] : self::DEFAULT_CONSOLE_SCREEN_WIDTH + self::CONSOLE_SCROLLBAR_OFFSET; } return $this->screenWidth; } }