Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
22 / 22
90.62% covered (success)
90.62%
29 / 32
4.62% covered (danger)
4.62%
6 / 130
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
Table
100.00% covered (success)
100.00%
22 / 22
90.62% covered (success)
90.62%
29 / 32
4.62% covered (danger)
4.62%
6 / 130
100.00% covered (success)
100.00%
4 / 4
116.01
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 row
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 rule
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 render
100.00% covered (success)
100.00%
17 / 17
88.89% covered (warning)
88.89%
24 / 27
1.59% covered (danger)
1.59%
2 / 126
100.00% covered (success)
100.00%
1 / 1
53.70
1<?php
2
3declare(strict_types=1);
4
5namespace Celema\Console;
6
7use ValueError;
8
9/**
10 * Renders rows of columns aligned on their visible width — markup
11 * tags and multibyte characters don't count, so cells may carry
12 * markup: `<green>1,075</green>`.
13 *
14 * Columns size to their widest cell and are separated by two spaces;
15 * `rule()` inserts a separator line spanning the table. Cells don't
16 * wrap: a table wider than the terminal simply overflows. Render the
17 * result through the Io echo methods:
18 *
19 *     $table = new Table(align: [Align::Left, Align::Right]);
20 *     $table->row(['<strong>Language</strong>', '<strong>Lines</strong>']);
21 *     $table->rule();
22 *     $table->row(['PHP', '1,075']);
23 *     $io->echo($table->render());
24 *
25 * @api
26 */
27final class Table
28{
29    private readonly Markup $markup;
30
31    /** @var list<list<string>|string> Cell rows, or a rule's char. */
32    private array $rows = [];
33
34    /** @param list<Align> $align Per column; further columns align left. */
35    public function __construct(
36        private readonly array $align = [],
37    ) {
38        $this->markup = new Markup();
39    }
40
41    /** @param list<string> $cells A short row leaves the rest empty. */
42    public function row(array $cells): void
43    {
44        $this->rows[] = $cells;
45    }
46
47    public function rule(string $char = '─'): void
48    {
49        if ($this->markup->width($char) < 1) {
50            throw new ValueError("Rule char '{$char}' has no visible width");
51        }
52
53        $this->rows[] = $char;
54    }
55
56    /**
57     * The rendered table, lines separated and terminated by `\n`.
58     */
59    public function render(): string
60    {
61        $widths = [];
62
63        foreach ($this->rows as $row) {
64            if (is_string($row)) {
65                continue;
66            }
67
68            foreach ($row as $i => $cell) {
69                $widths[$i] = max($widths[$i] ?? 0, $this->markup->width($cell));
70            }
71        }
72
73        $total = (int) array_sum($widths) + (2 * max(0, count($widths) - 1));
74        $out = '';
75
76        foreach ($this->rows as $row) {
77            if (is_string($row)) {
78                $out .= str_repeat($row, intdiv($total, $this->markup->width($row)));
79            } else {
80                $cells = [];
81
82                foreach ($widths as $i => $width) {
83                    $cells[] = $this->markup->pad($row[$i] ?? '', $width, $this->align[$i] ?? Align::Left);
84                }
85
86                $out .= rtrim(implode('  ', $cells));
87            }
88
89            $out .= "\n";
90        }
91
92        return $out;
93    }
94}

Branches

Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once. Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement always has an else as part of its logical flow even if you didn't write one.

Table->__construct
36        private readonly array $align = [],
37    ) {
38        $this->markup = new Markup();
39    }
Table->render
61        $widths = [];
62
63        foreach ($this->rows as $row) {
63        foreach ($this->rows as $row) {
64            if (is_string($row)) {
65                continue;
68            foreach ($row as $i => $cell) {
68            foreach ($row as $i => $cell) {
68            foreach ($row as $i => $cell) {
69                $widths[$i] = max($widths[$i] ?? 0, $this->markup->width($cell));
69                $widths[$i] = max($widths[$i] ?? 0, $this->markup->width($cell));
69                $widths[$i] = max($widths[$i] ?? 0, $this->markup->width($cell));
68            foreach ($row as $i => $cell) {
69                $widths[$i] = max($widths[$i] ?? 0, $this->markup->width($cell));
63        foreach ($this->rows as $row) {
64            if (is_string($row)) {
65                continue;
66            }
67
68            foreach ($row as $i => $cell) {
63        foreach ($this->rows as $row) {
64            if (is_string($row)) {
65                continue;
66            }
67
68            foreach ($row as $i => $cell) {
69                $widths[$i] = max($widths[$i] ?? 0, $this->markup->width($cell));
70            }
71        }
72
73        $total = (int) array_sum($widths) + (2 * max(0, count($widths) - 1));
73        $total = (int) array_sum($widths) + (2 * max(0, count($widths) - 1));
73        $total = (int) array_sum($widths) + (2 * max(0, count($widths) - 1));
73        $total = (int) array_sum($widths) + (2 * max(0, count($widths) - 1));
74        $out = '';
75
76        foreach ($this->rows as $row) {
76        foreach ($this->rows as $row) {
77            if (is_string($row)) {
77            if (is_string($row)) {
78                $out .= str_repeat($row, intdiv($total, $this->markup->width($row)));
80                $cells = [];
81
82                foreach ($widths as $i => $width) {
82                foreach ($widths as $i => $width) {
82                foreach ($widths as $i => $width) {
82                foreach ($widths as $i => $width) {
83                    $cells[] = $this->markup->pad($row[$i] ?? '', $width, $this->align[$i] ?? Align::Left);
84                }
85
86                $out .= rtrim(implode('  ', $cells));
86                $out .= rtrim(implode('  ', $cells));
86                $out .= rtrim(implode('  ', $cells));
86                $out .= rtrim(implode('  ', $cells));
87            }
88
89            $out .= "\n";
76        foreach ($this->rows as $row) {
77            if (is_string($row)) {
78                $out .= str_repeat($row, intdiv($total, $this->markup->width($row)));
79            } else {
80                $cells = [];
81
82                foreach ($widths as $i => $width) {
83                    $cells[] = $this->markup->pad($row[$i] ?? '', $width, $this->align[$i] ?? Align::Left);
84                }
85
86                $out .= rtrim(implode('  ', $cells));
87            }
88
89            $out .= "\n";
76        foreach ($this->rows as $row) {
77            if (is_string($row)) {
78                $out .= str_repeat($row, intdiv($total, $this->markup->width($row)));
79            } else {
80                $cells = [];
81
82                foreach ($widths as $i => $width) {
83                    $cells[] = $this->markup->pad($row[$i] ?? '', $width, $this->align[$i] ?? Align::Left);
84                }
85
86                $out .= rtrim(implode('  ', $cells));
87            }
88
89            $out .= "\n";
90        }
91
92        return $out;
93    }
Table->row
42    public function row(array $cells): void
43    {
44        $this->rows[] = $cells;
45    }
Table->rule
47    public function rule(string $char = '─'): void
48    {
49        if ($this->markup->width($char) < 1) {
50            throw new ValueError("Rule char '{$char}' has no visible width");
53        $this->rows[] = $char;
54    }