Completed
Push — table-fix ( 69d400...76937b )
by Craig
156:14 queued 121:12
created

Table::getDefaultColumnWidths()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
namespace League\CLImate\TerminalObject\Basic;
4
5
use League\CLImate\Exceptions\InvalidArgumentException;
6
use League\CLImate\TerminalObject\Helper\StringLength;
7
8
use function array_keys;
9
use function count;
10
use function get_object_vars;
11
use function is_array;
12
use function is_object;
13
use function max;
14
use function preg_split;
15
16
class Table extends BasicTerminalObject
17
{
18
    use StringLength;
19
20
    /**
21
     * The data for the table, an array of (arrays|objects)
22
     *
23
     * @var array $data
24
     */
25
    protected $data           = [];
26
27
    /**
28
     * An array of the widths of each column in the table
29
     *
30
     * @var array $column_widths
31
     */
32
    protected $column_widths  = [];
33
34
    /**
35
     * The width of the table
36
     *
37
     * @var integer $table_width
38
     */
39
    protected $table_width    = 0;
40
41
    /**
42
     * The divider between table cells
43
     *
44
     * @var string $column_divider
45
     */
46
    protected $column_divider = ' | ';
47
48
    /**
49
     * The border to divide each row of the table
50
     *
51
     * @var string $border
52
     */
53
    protected $border;
54
55
    /**
56
     * The array of rows that will ultimately be returned
57
     *
58
     * @var array $rows
59
     */
60
    protected $rows           = [];
61
62
    /**
63
     * @var string $pregix A string to be output before each row is output.
64
     */
65
    private $prefix = "";
66
67
68
    public function __construct(array $data, $prefix = "")
69
    {
70
        $this->data = $this->getData($data);
71
        $this->prefix = $prefix;
72
    }
73
74
75
    /**
76
     * @param array $input
77
     *
78
     * @return array
79
     */
80
    private function getData(array $input)
81
    {
82
        $output = [];
83
84
        foreach ($input as $item) {
85
            if (is_object($item)) {
86
                $item = get_object_vars($item);
87
            }
88
89
            if (!is_array($item)) {
90
                throw new InvalidArgumentException("Invalid table data, you must pass an array of arrays or objects");
91
            }
92
93
            $output[] = $item;
94
        }
95
96
        return $this->splitRows($output);
97
    }
98
99
    /**
100
     * Split each row in $data into an array of arrays
101
     * Where each value represents a line in a cell
102
     *
103
     * @param array $data
104
     *
105
     * @return array
106
     */
107
    private function splitRows($data)
108
    {
109
        foreach ($data as $row_key => $row) {
110
            $height = 1;
111
            $lines = [];
112
            foreach ($row as $key => $column) {
113
                $lines[$key] = preg_split('/(\r\n|\r|\n)/u', $column);
114
                $height = max($height, count($lines[$key]));
115
            }
116
            $keys = array_keys($row);
117
            $new_rows = [];
118
            for ($i = 0; $i < $height; $i++) {
119
                $new_row = [];
120
                foreach ($keys as $key) {
121
                    $new_row[$key] = $lines[$key][$i] ?? '';
122
                }
123
                $new_rows[] = $new_row;
124
            }
125
            $data[$row_key] = $new_rows;
126
        }
127
        return $data;
128
    }
129
130
131
    /**
132
     * Return the built rows
133
     *
134
     * @return array
135
     */
136
    public function result()
137
    {
138
        $this->column_widths = $this->getColumnWidths();
139
        $this->table_width   = $this->getWidth();
140
        $this->border        = $this->getBorder();
141
142
        $this->buildHeaderRow();
143
144
        foreach ($this->data as $row_columns) {
145
            foreach ($row_columns as $columns) {
146
                $this->addLine($this->buildRow($columns));
147
            }
148
            $this->addLine($this->border);
149
        }
150
151
        return $this->rows;
152
    }
153
154
    /**
155
     * Append a line to the output.
156
     *
157
     * @param string $line The line to output
158
     *
159
     * @return void
160
     */
161
    private function addLine($line)
162
    {
163
        $this->rows[] = $this->prefix . $line;
164
    }
165
166
167
    /**
168
     * Determine the width of the table
169
     *
170
     * @return integer
171
     */
172
    protected function getWidth()
173
    {
174
        $first_row = reset($this->data);
175
        $first_row = reset($first_row);
176
        $first_row = $this->buildRow($first_row);
177
178
        return $this->lengthWithoutTags($first_row);
179
    }
180
181
    /**
182
     * Get the border for each row based on the table width
183
     */
184
    protected function getBorder()
185
    {
186
        return (new Border())->length($this->table_width)->result();
187
    }
188
189
    /**
190
     * Check for a header row (if it's an array of associative arrays or objects),
191
     * if there is one, tack it onto the front of the rows array
192
     */
193
    protected function buildHeaderRow()
194
    {
195
        $this->addLine($this->border);
196
197
        $header_row = $this->getHeaderRow();
198
        if ($header_row) {
199
            $this->addLine($this->buildRow($header_row));
200
            $this->addLine((new Border)->char('=')->length($this->table_width)->result());
201
        }
202
    }
203
204
    /**
205
     * Get table row
206
     *
207
     * @param  mixed  $columns
208
     *
209
     * @return string
210
     */
211
    protected function buildRow($columns)
212
    {
213
        $row = [];
214
215
        foreach ($columns as $key => $column) {
216
            $row[] = $this->buildCell($key, $column);
217
        }
218
219
        $row = implode($this->column_divider, $row);
220
221
        return trim($this->column_divider . $row . $this->column_divider);
222
    }
223
224
    /**
225
     * Build the string for this particular table cell
226
     *
227
     * @param  mixed  $key
228
     * @param  string $column
229
     *
230
     * @return string
231
     */
232
    protected function buildCell($key, $column)
233
    {
234
        return  $this->pad($column, $this->column_widths[$key]);
235
    }
236
237
    /**
238
     * Get the header row for the table if it's an associative array or object
239
     *
240
     * @return mixed
241
     */
242
    protected function getHeaderRow()
243
    {
244
        $first_item = reset($this->data);
245
        $first_item = reset($first_item);
246
247
        $keys       = array_keys($first_item);
248
        $first_key  = reset($keys);
249
250
        // We have an associative array (probably), let's have a header row
251
        if (!is_int($first_key)) {
252
            return array_combine($keys, $keys);
253
        }
254
255
        return false;
256
    }
257
258
    /**
259
     * Determine the width of each column
260
     *
261
     * @return array
262
     */
263
    protected function getColumnWidths()
264
    {
265
        $first_row = reset($this->data);
266
        $first_row = reset($first_row);
267
268
        // Create an array with the columns as keys and values of zero
269
        $column_widths = $this->getDefaultColumnWidths($first_row);
270
271
        foreach ($this->data as $row_columns) {
272
            foreach ($row_columns as $columns) {
273
                foreach ($columns as $key => $column) {
274
                    $column_widths[$key] = $this->getCellWidth($column_widths[$key], $column);
275
                }
276
            }
277
        }
278
279
        return $column_widths;
280
    }
281
282
    /**
283
     * Set up an array of default column widths
284
     *
285
     * @param array $columns
286
     *
287
     * @return array
288
     */
289
    protected function getDefaultColumnWidths(array $columns)
290
    {
291
        $widths = $this->arrayOfStrLens(array_keys($columns));
292
293
        return array_combine(array_keys($columns), $widths);
294
    }
295
296
    /**
297
     * Determine the width of the columns without tags
298
     *
299
     * @param array  $current_width
300
     * @param string $str
301
     *
302
     * @return integer
303
     */
304
    protected function getCellWidth($current_width, $str)
305
    {
306
        return max($current_width, $this->lengthWithoutTags($str));
307
    }
308
}
309