1 | <?php |
||
7 | class CliTable |
||
8 | { |
||
9 | private array |
||
|
|||
10 | $headers, |
||
11 | $rows, |
||
12 | $columnsSize; |
||
13 | private int |
||
14 | $nbColumns; |
||
15 | private ?\Closure |
||
16 | $valueRenderFunction; |
||
17 | private bool |
||
18 | $enableFormattingTags, |
||
19 | $displayKeys; |
||
20 | |||
21 | 12 | public function __construct(array $values) |
|
22 | { |
||
23 | 12 | $this->rows = $values; |
|
24 | |||
25 | 12 | $this->headers = []; |
|
26 | 12 | $this->nbColumns = 0; |
|
27 | |||
28 | 12 | $this->valueRenderFunction = null; |
|
29 | 12 | $this->enableFormattingTags = false; |
|
30 | 12 | $this->displayKeys = false; |
|
31 | 12 | } |
|
32 | |||
33 | 2 | public function setValueRenderingFunction(\Closure $function): self |
|
34 | { |
||
35 | 2 | $this->valueRenderFunction = $function; |
|
36 | |||
37 | 2 | return $this; |
|
38 | } |
||
39 | |||
40 | 3 | public function enableFormattingTags(bool $value = true): self |
|
41 | { |
||
42 | 3 | $this->enableFormattingTags = (bool) $value; |
|
43 | |||
44 | 3 | return $this; |
|
45 | } |
||
46 | |||
47 | 8 | public function setHeaders(array $headers): self |
|
48 | { |
||
49 | 8 | $this->headers = $headers; |
|
50 | |||
51 | 8 | return $this; |
|
52 | } |
||
53 | |||
54 | 3 | public function displayKeys(bool $value = true): self |
|
55 | { |
||
56 | 3 | $this->displayKeys = (bool) $value; |
|
57 | |||
58 | 3 | return $this; |
|
59 | } |
||
60 | |||
61 | 12 | public function render(): string |
|
62 | { |
||
63 | 12 | $this->manageKeys(); |
|
64 | 12 | $this->computeColumnsSize(); |
|
65 | |||
66 | 8 | $nbSeparators = $this->nbColumns + 1; |
|
67 | 8 | $paddingSize = 2 * $this->nbColumns; |
|
68 | 8 | $totalSize = array_sum($this->columnsSize) + $nbSeparators + $paddingSize; |
|
69 | |||
70 | 8 | $separatorRow = str_pad('|', $totalSize - 1, '-') . '|'; |
|
71 | |||
72 | 8 | $lines = array(); |
|
73 | 8 | $lines[] = $separatorRow; |
|
74 | |||
75 | 8 | if(! empty($this->headers)) |
|
76 | { |
||
77 | 8 | $lines[] = $this->renderLine($this->headers); |
|
78 | 8 | $lines[] = $separatorRow; |
|
79 | } |
||
80 | |||
81 | 8 | foreach($this->rows as $row) |
|
82 | { |
||
83 | 8 | $lines[] = $this->renderLine($row); |
|
84 | } |
||
85 | |||
86 | 8 | $lines[] = $separatorRow; |
|
87 | |||
88 | 8 | return implode(PHP_EOL, $lines); |
|
89 | } |
||
90 | |||
91 | 12 | private function manageKeys(): void |
|
92 | { |
||
93 | 12 | if($this->displayKeys !== true) |
|
94 | { |
||
95 | 10 | return; |
|
96 | } |
||
97 | |||
98 | 2 | if(! empty($this->headers)) |
|
99 | { |
||
100 | 2 | array_unshift($this->headers, ''); |
|
101 | } |
||
102 | |||
103 | 2 | foreach($this->rows as $key => $row) |
|
104 | { |
||
105 | 2 | array_unshift($row, $key); |
|
106 | 2 | $this->rows[$key] = $row; |
|
107 | } |
||
108 | 2 | } |
|
109 | |||
110 | 12 | private function computeColumnsSize(): void |
|
111 | { |
||
112 | 12 | $this->computeNbColumns(); |
|
113 | |||
114 | 12 | $this->columnsSize = array_pad(array(), $this->nbColumns, -1); |
|
115 | |||
116 | 12 | if(! empty($this->headers)) |
|
117 | { |
||
118 | 8 | $this->updateColumnsSize(array($this->headers)); |
|
119 | } |
||
120 | |||
121 | 12 | $this->updateColumnsSize($this->rows); |
|
122 | 8 | } |
|
123 | |||
124 | 12 | private function computeNbColumns(): void |
|
125 | { |
||
126 | 12 | $this->nbColumns = 0; |
|
127 | |||
128 | 12 | foreach($this->rows as $row) |
|
129 | { |
||
130 | 12 | if(is_array($row) ||$row instanceof \Countable) |
|
131 | { |
||
132 | 10 | $this->nbColumns = max($this->nbColumns, count($row)); |
|
133 | } |
||
134 | } |
||
135 | |||
136 | 12 | $this->nbColumns = max($this->nbColumns, count($this->headers)); |
|
137 | 12 | } |
|
138 | |||
139 | 12 | private function updateColumnsSize(array $newValues): void |
|
140 | { |
||
141 | 12 | foreach($newValues as $row) |
|
142 | { |
||
143 | 12 | if(! is_array($row)) |
|
144 | { |
||
145 | 2 | throw new \InvalidArgumentException('Rows must be arrays'); |
|
146 | } |
||
147 | |||
148 | 10 | if(count($row) !== $this->nbColumns) |
|
149 | { |
||
150 | 2 | throw new \InvalidArgumentException('Rows must all have the same number of columns'); |
|
151 | } |
||
152 | |||
153 | 9 | for($i = 0; $i < $this->nbColumns; $i++) |
|
154 | { |
||
155 | 9 | $value = $this->renderValueAsString($row[$i]); |
|
156 | |||
157 | 9 | if($this->enableFormattingTags === true) |
|
158 | { |
||
159 | 3 | $value = $this->stripTags($value); |
|
160 | } |
||
161 | |||
162 | 9 | $this->columnsSize[$i] = max(strlen($value), $this->columnsSize[$i]); |
|
163 | } |
||
164 | } |
||
165 | 8 | } |
|
166 | |||
167 | 9 | private function renderValueAsString($value): string |
|
168 | { |
||
169 | 9 | if($value === false) |
|
170 | { |
||
171 | 1 | $value = 'false'; |
|
172 | } |
||
173 | 9 | elseif($value === true) |
|
174 | { |
||
175 | 3 | $value = 'true'; |
|
176 | } |
||
177 | 9 | elseif($value === null) |
|
178 | { |
||
179 | 4 | $value = 'NULL'; |
|
180 | } |
||
181 | |||
182 | 9 | if($this->valueRenderFunction instanceof \Closure) |
|
183 | { |
||
184 | 2 | $f = $this->valueRenderFunction; |
|
185 | 2 | $value = $f($value); |
|
186 | } |
||
187 | |||
188 | 9 | return (string) $value; |
|
189 | } |
||
190 | |||
191 | 3 | private function stripTags(string $value): string |
|
192 | { |
||
193 | 3 | return preg_replace ('/<[^>]*>/', '', $value); |
|
194 | } |
||
195 | |||
196 | 8 | private function renderLine(array $row): string |
|
197 | { |
||
198 | 8 | $columns = array(); |
|
199 | |||
200 | 8 | for($i = 0; $i < $this->nbColumns; $i++) |
|
201 | { |
||
202 | 8 | $value = $this->renderValueAsString($row[$i]); |
|
203 | |||
204 | 8 | $displayedString = str_pad( |
|
205 | 8 | $value, |
|
206 | 8 | $this->computeRealColumnSizeFor($value, $this->columnsSize[$i]), |
|
207 | 8 | ' ' |
|
208 | ); |
||
209 | |||
210 | 8 | $columns[] = sprintf('| %s ', $displayedString); |
|
211 | } |
||
212 | |||
213 | 8 | return implode('', $columns) . '|'; |
|
214 | } |
||
215 | |||
216 | 8 | private function computeRealColumnSizeFor(string $value, int $columnSize): int |
|
217 | { |
||
218 | 8 | $displayedValue = $value; |
|
219 | |||
220 | 8 | if($this->enableFormattingTags === true) |
|
221 | { |
||
222 | 3 | $displayedValue = $this->stripTags($displayedValue); |
|
223 | } |
||
224 | |||
225 | 8 | $paddingLength = $columnSize - strlen($displayedValue); |
|
226 | |||
227 | 8 | return strlen($value) + $paddingLength; |
|
230 |