Completed
Push — master ( a84d2e...be2e06 )
by Craig
190:01 queued 155:02
created

Table::splitRows()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 10
cts 10
cp 1
rs 9.2568
c 0
b 0
f 0
cc 5
nc 7
nop 1
crap 5
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 28
    protected $border;
54
55 28
    /**
56 28
     * The array of rows that will ultimately be returned
57
     *
58
     * @var array $rows
59
     */
60
    protected $rows           = [];
61
62
    /**
63 28
     * @var string $pregix A string to be output before each row is output.
64
     */
65 28
    private $prefix = "";
66 28
67 28
68
    public function __construct(array $data, $prefix = "")
69 28
    {
70
        $this->data = $this->getData($data);
71 28
        $this->prefix = $prefix;
72 28
    }
73 28
74 28
75
    /**
76 28
     * @param array $input
77
     *
78
     * @return array
79
     */
80
    private function getData(array $input)
81
    {
82
        $output = [];
83
84 28
        foreach ($input as $item) {
85
            if (is_object($item)) {
86 28
                $item = get_object_vars($item);
87 28
            }
88
89 28
            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 28
96
        return $this->splitRows($output);
97 28
    }
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 28
     *
105
     * @return array
106 28
     */
107
    private function splitRows($data)
108 28
    {
109 24
        foreach ($data as $row_key => $row) {
110 24
            $height = 1;
111 24
            $lines = [];
112 24
            foreach ($row as $key => $column) {
113 4
                $lines[$key] = preg_split('/(\r\n|\r|\n)/u', $column);
114
                $height = max($height, count($lines[$key]));
115 28
            }
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 28
            }
125
            $data[$row_key] = $new_rows;
126 28
        }
127
        return $data;
128 28
    }
129 28
130 28
131
    /**
132 28
     * Return the built rows
133
     *
134 28
     * @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 28
            foreach ($row_columns as $columns) {
146
                $this->addLine($this->buildRow($columns));
147 28
            }
148
            $this->addLine($this->border);
149
        }
150
151
        return $this->rows;
152
    }
153
154
    /**
155 28
     * Append a line to the output.
156
     *
157 28
     * @param string $line The line to output
158
     *
159 28
     * @return void
160 4
     */
161 4
    private function addLine($line)
162
    {
163 28
        $this->rows[] = $this->prefix . $line;
164 28
    }
165
166
167 28
    /**
168 24
     * Determine the width of the table
169
     *
170
     * @return integer
171 4
     */
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 28
    }
180
181 28
    /**
182
     * Get the border for each row based on the table width
183 28
     */
184 4
    protected function getBorder()
185 4
    {
186
        return (new Border())->length($this->table_width)->result();
187
    }
188 28
189
    /**
190 28
     * Check for a header row (if it's an array of associative arrays or objects),
191 28
     * if there is one, tack it onto the front of the rows array
192 28
     */
193 28
    protected function buildHeaderRow()
194 28
    {
195
        $this->addLine($this->border);
196 28
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 28
     *
207
     * @param  mixed  $columns
208 28
     *
209
     * @return string
210 28
     */
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 28
        return trim($this->column_divider . $row . $this->column_divider);
222
    }
223 28
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