Passed
Push — 17667-sqlite-create-index-with... ( 21cdd8...b9d5ca )
by Alexander
84:30 queued 44:33
created

Table   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 361
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 172
dl 0
loc 361
rs 8.96
c 1
b 0
f 0
wmc 43

11 Methods

Rating   Name   Duplication   Size   Complexity  
A setHeaders() 0 4 1
A getScreenWidth() 0 9 3
A setScreenWidth() 0 4 1
B calculateRowsSize() 0 42 9
C renderRow() 0 46 12
A setChars() 0 4 1
A renderSeparator() 0 11 3
A setListPrefix() 0 4 1
A run() 0 44 5
A calculateRowHeight() 0 24 4
A setRows() 0 8 3

How to fix   Complexity   

Complex Class

Complex classes like Table often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Table, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\console\widgets;
9
10
use Yii;
11
use yii\base\Widget;
12
use yii\helpers\ArrayHelper;
13
use yii\helpers\Console;
14
15
/**
16
 * Table class displays a table in console.
17
 *
18
 * For example,
19
 *
20
 * ```php
21
 * $table = new Table();
22
 *
23
 * echo $table
24
 *     ->setHeaders(['test1', 'test2', 'test3'])
25
 *     ->setRows([
26
 *         ['col1', 'col2', 'col3'],
27
 *         ['col1', 'col2', ['col3-0', 'col3-1', 'col3-2']],
28
 *     ])
29
 *     ->run();
30
 * ```
31
 *
32
 * or
33
 *
34
 * ```php
35
 * echo Table::widget([
36
 *     'headers' => ['test1', 'test2', 'test3'],
37
 *     'rows' => [
38
 *         ['col1', 'col2', 'col3'],
39
 *         ['col1', 'col2', ['col3-0', 'col3-1', 'col3-2']],
40
 *     ],
41
 * ]);
42
 *
43
 * @author Daniel Gomez Pan <[email protected]>
44
 * @since 2.0.13
45
 */
46
class Table extends Widget
47
{
48
    const DEFAULT_CONSOLE_SCREEN_WIDTH = 120;
49
    const CONSOLE_SCROLLBAR_OFFSET = 3;
50
    const CHAR_TOP = 'top';
51
    const CHAR_TOP_MID = 'top-mid';
52
    const CHAR_TOP_LEFT = 'top-left';
53
    const CHAR_TOP_RIGHT = 'top-right';
54
    const CHAR_BOTTOM = 'bottom';
55
    const CHAR_BOTTOM_MID = 'bottom-mid';
56
    const CHAR_BOTTOM_LEFT = 'bottom-left';
57
    const CHAR_BOTTOM_RIGHT = 'bottom-right';
58
    const CHAR_LEFT = 'left';
59
    const CHAR_LEFT_MID = 'left-mid';
60
    const CHAR_MID = 'mid';
61
    const CHAR_MID_MID = 'mid-mid';
62
    const CHAR_RIGHT = 'right';
63
    const CHAR_RIGHT_MID = 'right-mid';
64
    const CHAR_MIDDLE = 'middle';
65
66
    /**
67
     * @var array table headers
68
     * @since 2.0.19
69
     */
70
    protected $headers = [];
71
    /**
72
     * @var array table rows
73
     * @since 2.0.19
74
     */
75
    protected $rows = [];
76
    /**
77
     * @var array table chars
78
     * @since 2.0.19
79
     */
80
    protected $chars = [
81
        self::CHAR_TOP => '═',
82
        self::CHAR_TOP_MID => '╤',
83
        self::CHAR_TOP_LEFT => '╔',
84
        self::CHAR_TOP_RIGHT => '╗',
85
        self::CHAR_BOTTOM => '═',
86
        self::CHAR_BOTTOM_MID => '╧',
87
        self::CHAR_BOTTOM_LEFT => '╚',
88
        self::CHAR_BOTTOM_RIGHT => '╝',
89
        self::CHAR_LEFT => '║',
90
        self::CHAR_LEFT_MID => '╟',
91
        self::CHAR_MID => '─',
92
        self::CHAR_MID_MID => '┼',
93
        self::CHAR_RIGHT => '║',
94
        self::CHAR_RIGHT_MID => '╢',
95
        self::CHAR_MIDDLE => '│',
96
    ];
97
    /**
98
     * @var array table column widths
99
     * @since 2.0.19
100
     */
101
    protected $columnWidths = [];
102
    /**
103
     * @var int screen width
104
     * @since 2.0.19
105
     */
106
    protected $screenWidth;
107
    /**
108
     * @var string list prefix
109
     * @since 2.0.19
110
     */
111
    protected $listPrefix = '• ';
112
113
114
    /**
115
     * Set table headers.
116
     *
117
     * @param array $headers table headers
118
     * @return $this
119
     */
120
    public function setHeaders(array $headers)
121
    {
122
        $this->headers = array_values($headers);
123
        return $this;
124
    }
125
126
    /**
127
     * Set table rows.
128
     *
129
     * @param array $rows table rows
130
     * @return $this
131
     */
132
    public function setRows(array $rows)
133
    {
134
        $this->rows = array_map(function($row) {
135
            return array_map(function($value) {
136
                return empty($value) && !is_numeric($value) ? ' ' : $value;
137
            }, array_values($row));
138
        }, $rows);
139
        return $this;
140
    }
141
142
    /**
143
     * Set table chars.
144
     *
145
     * @param array $chars table chars
146
     * @return $this
147
     */
148
    public function setChars(array $chars)
149
    {
150
        $this->chars = $chars;
151
        return $this;
152
    }
153
154
    /**
155
     * Set screen width.
156
     *
157
     * @param int $width screen width
158
     * @return $this
159
     */
160
    public function setScreenWidth($width)
161
    {
162
        $this->screenWidth = $width;
163
        return $this;
164
    }
165
166
    /**
167
     * Set list prefix.
168
     *
169
     * @param string $listPrefix list prefix
170
     * @return $this
171
     */
172
    public function setListPrefix($listPrefix)
173
    {
174
        $this->listPrefix = $listPrefix;
175
        return $this;
176
    }
177
178
    /**
179
     * @return string the rendered table
180
     */
181
    public function run()
182
    {
183
        $this->calculateRowsSize();
184
        $headerCount = count($this->headers);
185
186
        $buffer = $this->renderSeparator(
187
            $this->chars[self::CHAR_TOP_LEFT],
188
            $this->chars[self::CHAR_TOP_MID],
189
            $this->chars[self::CHAR_TOP],
190
            $this->chars[self::CHAR_TOP_RIGHT]
191
        );
192
        // Header
193
        if ($headerCount > 0) {
194
            $buffer .= $this->renderRow($this->headers,
195
                $this->chars[self::CHAR_LEFT],
196
                $this->chars[self::CHAR_MIDDLE],
197
                $this->chars[self::CHAR_RIGHT]
198
            );
199
        }
200
201
        // Content
202
        foreach ($this->rows as $i => $row) {
203
            if ($i > 0 || $headerCount > 0) {
204
                $buffer .= $this->renderSeparator(
205
                    $this->chars[self::CHAR_LEFT_MID],
206
                    $this->chars[self::CHAR_MID_MID],
207
                    $this->chars[self::CHAR_MID],
208
                    $this->chars[self::CHAR_RIGHT_MID]
209
                );
210
            }
211
            $buffer .= $this->renderRow($row,
212
                $this->chars[self::CHAR_LEFT],
213
                $this->chars[self::CHAR_MIDDLE],
214
                $this->chars[self::CHAR_RIGHT]);
215
        }
216
217
        $buffer .= $this->renderSeparator(
218
            $this->chars[self::CHAR_BOTTOM_LEFT],
219
            $this->chars[self::CHAR_BOTTOM_MID],
220
            $this->chars[self::CHAR_BOTTOM],
221
            $this->chars[self::CHAR_BOTTOM_RIGHT]
222
        );
223
224
        return $buffer;
225
    }
226
227
    /**
228
     * Renders a row of data into a string.
229
     *
230
     * @param array $row row of data
231
     * @param string $spanLeft character for left border
232
     * @param string $spanMiddle character for middle border
233
     * @param string $spanRight character for right border
234
     * @return string
235
     * @see \yii\console\widgets\Table::render()
236
     */
237
    protected function renderRow(array $row, $spanLeft, $spanMiddle, $spanRight)
238
    {
239
        $size = $this->columnWidths;
240
241
        $buffer = '';
242
        $arrayPointer = [];
243
        $finalChunk = [];
244
        for ($i = 0, ($max = $this->calculateRowHeight($row)) ?: $max = 1; $i < $max; $i++) {
245
            $buffer .= $spanLeft . ' ';
246
            foreach ($size as $index => $cellSize) {
247
                $cell = isset($row[$index]) ? $row[$index] : null;
248
                $prefix = '';
249
                if ($index !== 0) {
250
                    $buffer .= $spanMiddle . ' ';
251
                }
252
                if (is_array($cell)) {
253
                    if (empty($finalChunk[$index])) {
254
                        $finalChunk[$index] = '';
255
                        $start = 0;
256
                        $prefix = $this->listPrefix;
257
                        if (!isset($arrayPointer[$index])) {
258
                            $arrayPointer[$index] = 0;
259
                        }
260
                    } else {
261
                        $start = mb_strwidth($finalChunk[$index], Yii::$app->charset);
262
                    }
263
                    $chunk = mb_substr($cell[$arrayPointer[$index]], $start, $cellSize - 4, Yii::$app->charset);
264
                    $finalChunk[$index] .= $chunk;
265
                    if (isset($cell[$arrayPointer[$index] + 1]) && $finalChunk[$index] === $cell[$arrayPointer[$index]]) {
266
                        $arrayPointer[$index]++;
267
                        $finalChunk[$index] = '';
268
                    }
269
                } else {
270
                    $chunk = mb_substr($cell, ($cellSize * $i) - ($i * 2), $cellSize - 2, Yii::$app->charset);
271
                }
272
                $chunk = $prefix . $chunk;
273
                $repeat = $cellSize - mb_strwidth($chunk, Yii::$app->charset) - 1;
274
                $buffer .= $chunk;
275
                if ($repeat >= 0) {
276
                    $buffer .= str_repeat(' ', $repeat);
277
                }
278
            }
279
            $buffer .= "$spanRight\n";
280
        }
281
282
        return $buffer;
283
    }
284
285
    /**
286
     * Renders separator.
287
     *
288
     * @param string $spanLeft character for left border
289
     * @param string $spanMid character for middle border
290
     * @param string $spanMidMid character for middle-middle border
291
     * @param string $spanRight character for right border
292
     * @return string the generated separator row
293
     * @see \yii\console\widgets\Table::render()
294
     */
295
    protected function renderSeparator($spanLeft, $spanMid, $spanMidMid, $spanRight)
296
    {
297
        $separator = $spanLeft;
298
        foreach ($this->columnWidths as $index => $rowSize) {
299
            if ($index !== 0) {
300
                $separator .= $spanMid;
301
            }
302
            $separator .= str_repeat($spanMidMid, $rowSize);
303
        }
304
        $separator .= $spanRight . "\n";
305
        return $separator;
306
    }
307
308
    /**
309
     * Calculate the size of rows to draw anchor of columns in console.
310
     *
311
     * @see \yii\console\widgets\Table::render()
312
     */
313
    protected function calculateRowsSize()
314
    {
315
        $this->columnWidths = $columns = [];
316
        $totalWidth = 0;
317
        $screenWidth = $this->getScreenWidth() - self::CONSOLE_SCROLLBAR_OFFSET;
318
319
        $headerCount = count($this->headers);
320
        if (empty($this->rows)) {
321
            $rowColCount = 0;
322
        } else {
323
            $rowColCount = max(array_map('count', $this->rows));
324
        }
325
        $count = max($headerCount, $rowColCount);
326
        for ($i = 0; $i < $count; $i++) {
327
            $columns[] = ArrayHelper::getColumn($this->rows, $i);
328
            if ($i < $headerCount) {
329
                $columns[$i][] = $this->headers[$i];
330
            }
331
        }
332
333
        foreach ($columns as $column) {
334
            $columnWidth = max(array_map(function ($val) {
335
                if (is_array($val)) {
336
                    $encodings = array_fill(0, count($val), Yii::$app->charset);
337
                    return max(array_map('mb_strwidth', $val, $encodings)) + mb_strwidth($this->listPrefix, Yii::$app->charset);
338
                }
339
340
                return mb_strwidth($val, Yii::$app->charset);
341
            }, $column)) + 2;
342
            $this->columnWidths[] = $columnWidth;
343
            $totalWidth += $columnWidth;
344
        }
345
346
        $relativeWidth = $screenWidth / $totalWidth;
347
348
        if ($totalWidth > $screenWidth) {
349
            foreach ($this->columnWidths as $j => $width) {
350
                $this->columnWidths[$j] = (int) ($width * $relativeWidth);
351
                if ($j === count($this->columnWidths)) {
352
                    $this->columnWidths = $totalWidth;
0 ignored issues
show
Documentation Bug introduced by
It seems like $totalWidth of type integer is incompatible with the declared type array of property $columnWidths.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
353
                }
354
                $totalWidth -= $this->columnWidths[$j];
355
            }
356
        }
357
    }
358
359
    /**
360
     * Calculate the height of a row.
361
     *
362
     * @param array $row
363
     * @return int maximum row per cell
364
     * @see \yii\console\widgets\Table::render()
365
     */
366
    protected function calculateRowHeight($row)
367
    {
368
        $rowsPerCell = array_map(function ($size, $columnWidth) {
369
            if (is_array($columnWidth)) {
370
                $rows = 0;
371
                foreach ($columnWidth as $width) {
372
                    $rows += ceil($width / ($size - 2));
373
                }
374
375
                return $rows;
376
            }
377
378
            return ceil($columnWidth / ($size - 2));
379
        }, $this->columnWidths, array_map(function ($val) {
380
            if (is_array($val)) {
381
                $encodings = array_fill(0, count($val), Yii::$app->charset);
382
                return array_map('mb_strwidth', $val, $encodings);
383
            }
384
385
            return mb_strwidth($val, Yii::$app->charset);
386
        }, $row)
387
        );
388
389
        return max($rowsPerCell);
390
    }
391
392
    /**
393
     * Getting screen width.
394
     * If it is not able to determine screen width, default value `123` will be set.
395
     *
396
     * @return int screen width
397
     */
398
    protected function getScreenWidth()
399
    {
400
        if (!$this->screenWidth) {
401
            $size = Console::getScreenSize();
402
            $this->screenWidth = isset($size[0])
403
                ? $size[0]
404
                : self::DEFAULT_CONSOLE_SCREEN_WIDTH + self::CONSOLE_SCROLLBAR_OFFSET;
405
        }
406
        return $this->screenWidth;
407
    }
408
}
409