Skip to content

Commit e26f604

Browse files
committed
[Console] deprecated TableHelper in favor of Table
1 parent 091a779 commit e26f604

File tree

5 files changed

+998
-276
lines changed

5 files changed

+998
-276
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
2.5.0
55
-----
66

7+
* deprecated TableHelper in favor of Table
78
* deprecated ProgressHelper in favor of ProgressBar
89
* added a way to set a default command instead of `ListCommand`
910
* added a way to set the process name of a command

Helper/Table.php

Lines changed: 391 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,391 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Console\Helper;
13+
14+
use Symfony\Component\Console\Output\OutputInterface;
15+
16+
/**
17+
* Provides helpers to display a table.
18+
*
19+
* @author Fabien Potencier <[email protected]>
20+
* @author Саша Стаменковић <[email protected]>
21+
*/
22+
class Table
23+
{
24+
/**
25+
* Table headers.
26+
*
27+
* @var array
28+
*/
29+
private $headers = array();
30+
31+
/**
32+
* Table rows.
33+
*
34+
* @var array
35+
*/
36+
private $rows = array();
37+
38+
/**
39+
* Column widths cache.
40+
*
41+
* @var array
42+
*/
43+
private $columnWidths = array();
44+
45+
/**
46+
* Number of columns cache.
47+
*
48+
* @var array
49+
*/
50+
private $numberOfColumns;
51+
52+
/**
53+
* @var OutputInterface
54+
*/
55+
private $output;
56+
57+
/**
58+
* @var TableStyle
59+
*/
60+
private $style;
61+
62+
private static $styles;
63+
64+
public function __construct(OutputInterface $output)
65+
{
66+
$this->output = $output;
67+
68+
if (!self::$styles) {
69+
self::$styles = self::initStyles();
70+
}
71+
72+
$this->setStyle('default');
73+
}
74+
75+
/**
76+
* Sets a style definition.
77+
*
78+
* @param string $name The style name
79+
* @param TableStyle $style A TableStyle instance
80+
*/
81+
public static function setStyleDefinition($name, TableStyle $style)
82+
{
83+
if (!self::$styles) {
84+
self::$styles = self::initStyles();
85+
}
86+
87+
self::$styles[$name] = $style;
88+
}
89+
90+
/**
91+
* Gets a style definition by name.
92+
*
93+
* @param string $name The style name
94+
*
95+
* @return TableStyle A TableStyle instance
96+
*/
97+
public static function getStyleDefinition($name)
98+
{
99+
if (!self::$styles) {
100+
self::$styles = self::initStyles();
101+
}
102+
103+
if (!self::$styles[$name]) {
104+
throw new \InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
105+
}
106+
107+
return self::$styles[$name];
108+
}
109+
110+
/**
111+
* Sets table style.
112+
*
113+
* @param string $name The style name
114+
*
115+
* @return Table
116+
*/
117+
public function setStyle($name)
118+
{
119+
if (isset(self::$styles[$name])) {
120+
$this->style = self::$styles[$name];
121+
122+
return $this;
123+
}
124+
125+
throw new \InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
126+
}
127+
128+
/**
129+
* Gets the current table style.
130+
*
131+
* @return TableStyle
132+
*/
133+
public function getStyle()
134+
{
135+
return $this->style;
136+
}
137+
138+
public function setHeaders(array $headers)
139+
{
140+
$this->headers = array_values($headers);
141+
142+
return $this;
143+
}
144+
145+
public function setRows(array $rows)
146+
{
147+
$this->rows = array();
148+
149+
return $this->addRows($rows);
150+
}
151+
152+
public function addRows(array $rows)
153+
{
154+
foreach ($rows as $row) {
155+
$this->addRow($row);
156+
}
157+
158+
return $this;
159+
}
160+
161+
public function addRow(array $row)
162+
{
163+
$this->rows[] = array_values($row);
164+
165+
$keys = array_keys($this->rows);
166+
$rowKey = array_pop($keys);
167+
168+
foreach ($row as $key => $cellValue) {
169+
if (!strstr($cellValue, "\n")) {
170+
continue;
171+
}
172+
173+
$lines = explode("\n", $cellValue);
174+
$this->rows[$rowKey][$key] = $lines[0];
175+
unset($lines[0]);
176+
177+
foreach ($lines as $lineKey => $line) {
178+
$nextRowKey = $rowKey + $lineKey + 1;
179+
180+
if (isset($this->rows[$nextRowKey])) {
181+
$this->rows[$nextRowKey][$key] = $line;
182+
} else {
183+
$this->rows[$nextRowKey] = array($key => $line);
184+
}
185+
}
186+
}
187+
188+
return $this;
189+
}
190+
191+
public function setRow($column, array $row)
192+
{
193+
$this->rows[$column] = $row;
194+
195+
return $this;
196+
}
197+
198+
/**
199+
* Renders table to output.
200+
*
201+
* Example:
202+
* +---------------+-----------------------+------------------+
203+
* | ISBN | Title | Author |
204+
* +---------------+-----------------------+------------------+
205+
* | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
206+
* | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
207+
* | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
208+
* +---------------+-----------------------+------------------+
209+
*
210+
* @param OutputInterface $output
211+
*/
212+
public function render()
213+
{
214+
$this->renderRowSeparator();
215+
$this->renderRow($this->headers, $this->style->getCellHeaderFormat());
216+
if (!empty($this->headers)) {
217+
$this->renderRowSeparator();
218+
}
219+
foreach ($this->rows as $row) {
220+
$this->renderRow($row, $this->style->getCellRowFormat());
221+
}
222+
if (!empty($this->rows)) {
223+
$this->renderRowSeparator();
224+
}
225+
226+
$this->cleanup();
227+
}
228+
229+
/**
230+
* Renders horizontal header separator.
231+
*
232+
* Example: +-----+-----------+-------+
233+
*/
234+
private function renderRowSeparator()
235+
{
236+
if (0 === $count = $this->getNumberOfColumns()) {
237+
return;
238+
}
239+
240+
if (!$this->style->getHorizontalBorderChar() && !$this->style->getCrossingChar()) {
241+
return;
242+
}
243+
244+
$markup = $this->style->getCrossingChar();
245+
for ($column = 0; $column < $count; $column++) {
246+
$markup .= str_repeat($this->style->getHorizontalBorderChar(), $this->getColumnWidth($column)).$this->style->getCrossingChar();
247+
}
248+
249+
$this->output->writeln(sprintf($this->style->getBorderFormat(), $markup));
250+
}
251+
252+
/**
253+
* Renders vertical column separator.
254+
*/
255+
private function renderColumnSeparator()
256+
{
257+
$this->output->write(sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar()));
258+
}
259+
260+
/**
261+
* Renders table row.
262+
*
263+
* Example: | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
264+
*
265+
* @param array $row
266+
* @param string $cellFormat
267+
*/
268+
private function renderRow(array $row, $cellFormat)
269+
{
270+
if (empty($row)) {
271+
return;
272+
}
273+
274+
$this->renderColumnSeparator();
275+
for ($column = 0, $count = $this->getNumberOfColumns(); $column < $count; $column++) {
276+
$this->renderCell($row, $column, $cellFormat);
277+
$this->renderColumnSeparator();
278+
}
279+
$this->output->writeln('');
280+
}
281+
282+
/**
283+
* Renders table cell with padding.
284+
*
285+
* @param array $row
286+
* @param integer $column
287+
* @param string $cellFormat
288+
*/
289+
private function renderCell(array $row, $column, $cellFormat)
290+
{
291+
$cell = isset($row[$column]) ? $row[$column] : '';
292+
$width = $this->getColumnWidth($column);
293+
294+
// str_pad won't work properly with multi-byte strings, we need to fix the padding
295+
if (function_exists('mb_strlen') && false !== $encoding = mb_detect_encoding($cell)) {
296+
$width += strlen($cell) - mb_strlen($cell, $encoding);
297+
}
298+
299+
$width += Helper::strlen($cell) - Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell);
300+
301+
$content = sprintf($this->style->getCellRowContentFormat(), $cell);
302+
303+
$this->output->write(sprintf($cellFormat, str_pad($content, $width, $this->style->getPaddingChar(), $this->style->getPadType())));
304+
}
305+
306+
/**
307+
* Gets number of columns for this table.
308+
*
309+
* @return integer
310+
*/
311+
private function getNumberOfColumns()
312+
{
313+
if (null !== $this->numberOfColumns) {
314+
return $this->numberOfColumns;
315+
}
316+
317+
$columns = array(count($this->headers));
318+
foreach ($this->rows as $row) {
319+
$columns[] = count($row);
320+
}
321+
322+
return $this->numberOfColumns = max($columns);
323+
}
324+
325+
/**
326+
* Gets column width.
327+
*
328+
* @param integer $column
329+
*
330+
* @return integer
331+
*/
332+
private function getColumnWidth($column)
333+
{
334+
if (isset($this->columnWidths[$column])) {
335+
return $this->columnWidths[$column];
336+
}
337+
338+
$lengths = array($this->getCellWidth($this->headers, $column));
339+
foreach ($this->rows as $row) {
340+
$lengths[] = $this->getCellWidth($row, $column);
341+
}
342+
343+
return $this->columnWidths[$column] = max($lengths) + strlen($this->style->getCellRowContentFormat()) - 2;
344+
}
345+
346+
/**
347+
* Gets cell width.
348+
*
349+
* @param array $row
350+
* @param integer $column
351+
*
352+
* @return integer
353+
*/
354+
private function getCellWidth(array $row, $column)
355+
{
356+
return isset($row[$column]) ? Helper::strlenWithoutDecoration($this->output->getFormatter(), $row[$column]) : 0;
357+
}
358+
359+
/**
360+
* Called after rendering to cleanup cache data.
361+
*/
362+
private function cleanup()
363+
{
364+
$this->columnWidths = array();
365+
$this->numberOfColumns = null;
366+
}
367+
368+
private static function initStyles()
369+
{
370+
$borderless = new TableStyle();
371+
$borderless
372+
->setHorizontalBorderChar('=')
373+
->setVerticalBorderChar(' ')
374+
->setCrossingChar(' ')
375+
;
376+
377+
$compact = new TableStyle();
378+
$compact
379+
->setHorizontalBorderChar('')
380+
->setVerticalBorderChar(' ')
381+
->setCrossingChar('')
382+
->setCellRowContentFormat('%s')
383+
;
384+
385+
return array(
386+
'default' => new TableStyle(),
387+
'borderless' => $borderless,
388+
'compact' => $compact,
389+
);
390+
}
391+
}

0 commit comments

Comments
 (0)