Completed
Push — master ( ae7c83...8c021d )
by ignace nyamagana
04:31
created

GanttChartConfig   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 484
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 48
eloc 124
c 1
b 0
f 0
dl 0
loc 484
ccs 138
cts 138
cp 1
rs 8.5599

29 Methods

Rating   Name   Duplication   Size   Complexity  
A withOutput() 0 6 1
A leftMarginSize() 0 3 1
A withEndIncluded() 0 11 2
A width() 0 3 1
A filterUnicodeCharacter() 0 10 2
A colors() 0 3 1
A space() 0 3 1
A withWidth() 0 14 3
A __construct() 0 3 1
A createFromRandom() 0 8 1
A withColors() 0 19 3
A withStartExcluded() 0 11 2
A withGapSize() 0 14 3
A withLeftMarginSize() 0 14 3
A startIncluded() 0 3 1
A endExcluded() 0 3 1
A gapSize() 0 3 1
A output() 0 3 1
A withSpace() 0 11 2
A labelAlign() 0 3 1
A withEndExcluded() 0 11 2
A endIncluded() 0 3 1
A withStartIncluded() 0 11 2
A body() 0 3 1
A withLabelAlign() 0 14 3
A createFromRainbow() 0 6 1
A withBody() 0 11 2
A filterPattern() 0 11 3
A startExcluded() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like GanttChartConfig 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 GanttChartConfig, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * League.Period (https://period.thephpleague.com)
5
 *
6
 * (c) Ignace Nyamagana Butera <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace League\Period\Chart;
15
16
use function array_filter;
17
use function array_map;
18
use function mb_convert_encoding;
19
use function mb_strlen;
20
use function preg_match;
21
use function preg_replace;
22
use function sprintf;
23
use const STDOUT;
24
use const STR_PAD_BOTH;
25
use const STR_PAD_LEFT;
26
use const STR_PAD_RIGHT;
27
28
/**
29
 * A class to configure the console output settings.
30
 */
31
final class GanttChartConfig
32
{
33
    private const REGEXP_UNICODE = '/\\\\u(?<unicode>[0-9A-F]{1,4})/i';
34
35
    public const ALIGN_LEFT = STR_PAD_RIGHT;
36
37
    public const ALIGN_RIGHT = STR_PAD_LEFT;
38
39
    public const ALIGN_CENTER = STR_PAD_BOTH;
40
41
    /**
42
     * @var Output
43
     */
44
    private $output;
45
46
    /**
47
     * @var string[]
48
     */
49
    private $colors = [Output::COLOR_DEFAULT];
50
51
    /**
52
     * @var int
53
     */
54
    private $width = 60;
55
56
    /**
57
     * @var string
58
     */
59
    private $endExcludedChar = ')';
60
61
    /**
62
     * @var string
63
     */
64
    private $endIncludedChar = ']';
65
66
    /**
67
     * @var string
68
     */
69
    private $startExcludedChar = '(';
70
71
    /**
72
     * @var string
73
     */
74
    private $startIncludedChar = '[';
75
76
    /**
77
     * @var string
78
     */
79
    private $body = '-';
80
81
    /**
82
     * @var string
83
     */
84
    private $space = ' ';
85
86
    /**
87
     * @var int
88
     */
89
    private $leftMarginSize = 1;
90
91
    /**
92
     * @var int
93
     */
94
    private $gapSize = 1;
95
96
    /**
97
     * @var int
98
     */
99
    private $alignLabel = self::ALIGN_LEFT;
100
101
    /**
102
     * New instance.
103
     *
104
     * @param ?Output $output
105
     */
106 297
    public function __construct(?Output $output = null)
107
    {
108 297
        $this->output = $output ?? new ConsoleOutput(STDOUT);
109 297
    }
110
111
    /**
112
     * Create a Cli Renderer to Display the millipede in Rainbow.
113
     *
114
     * @param ?Output $output
115
     */
116 3
    public static function createFromRandom(?Output $output = null): self
117
    {
118 3
        $index = array_rand(Output::COLORS);
119
120 3
        $config = new self($output);
121 3
        $config->colors = [Output::COLORS[$index]];
122
123 3
        return $config;
124
    }
125
126
    /**
127
     * Create a Cli Renderer to Display the millipede in Rainbow.
128
     *
129
     * @param ?Output $output
130
     */
131 3
    public static function createFromRainbow(?Output $output = null): self
132
    {
133 3
        $config = new self($output);
134 3
        $config->colors = Output::COLORS;
135
136 3
        return $config;
137
    }
138
139
    /**
140
     * Returns the Output class.
141
     */
142 3
    public function output(): Output
143
    {
144 3
        return $this->output;
145
    }
146
147
    /**
148
     * Retrieves the start excluded block character.
149
     */
150 42
    public function startExcluded(): string
151
    {
152 42
        return $this->startExcludedChar;
153
    }
154
    /**
155
     * Retrieves the start included block character.
156
     */
157 42
    public function startIncluded(): string
158
    {
159 42
        return $this->startIncludedChar;
160
    }
161
162
    /**
163
     * Retrieves the excluded end block character.
164
     */
165 42
    public function endExcluded(): string
166
    {
167 42
        return $this->endExcludedChar;
168
    }
169
170
    /**
171
     * Retrieves the excluded end block character.
172
     */
173 42
    public function endIncluded(): string
174
    {
175 42
        return $this->endIncludedChar;
176
    }
177
178
    /**
179
     * Retrieves the row width.
180
     */
181 15
    public function width(): int
182
    {
183 15
        return $this->width;
184
    }
185
186
    /**
187
     * Retrieves the body block character.
188
     */
189 42
    public function body(): string
190
    {
191 42
        return $this->body;
192
    }
193
194
    /**
195
     * Retrieves the row space character.
196
     */
197 42
    public function space(): string
198
    {
199 42
        return $this->space;
200
    }
201
202
    /**
203
     * The selected colors for each rows.
204
     *
205
     * @return string[]
206
     */
207 12
    public function colors(): array
208
    {
209 12
        return $this->colors;
210
    }
211
212
    /**
213
     * Retrieves the left margin size before the label name.
214
     */
215 9
    public function leftMarginSize(): int
216
    {
217 9
        return $this->leftMarginSize;
218
    }
219
220
    /**
221
     * Retrieves the gap size between the label and the line.
222
     */
223 12
    public function gapSize(): int
224
    {
225 12
        return $this->gapSize;
226
    }
227
228
    /**
229
     * Returns how label should be aligned.
230
     */
231 12
    public function labelAlign(): int
232
    {
233 12
        return $this->alignLabel;
234
    }
235
236
    /**
237
     * Return an instance with the start excluded pattern.
238
     *
239
     * This method MUST retain the state of the current instance, and return
240
     * an instance that contains the specified start excluded character.
241
     */
242 39
    public function withStartExcluded(string $startExcludedChar): self
243
    {
244 39
        $startExcludedChar = $this->filterPattern($startExcludedChar, 'startExcluded');
245 39
        if ($startExcludedChar === $this->startExcludedChar) {
246 3
            return $this;
247
        }
248
249 36
        $clone = clone $this;
250 36
        $clone->startExcludedChar = $startExcludedChar;
251
252 36
        return $clone;
253
    }
254
255
    /**
256
     * Filter the submitted string.
257
     *
258
     * @throws \InvalidArgumentException if the pattern is invalid
259
     */
260 240
    private function filterPattern(string $str, string $part): string
261
    {
262 240
        if (1 === mb_strlen($str)) {
263 216
            return $str;
264
        }
265
266 24
        if (1 === preg_match(self::REGEXP_UNICODE, $str)) {
267 21
            return $this->filterUnicodeCharacter($str);
268
        }
269
270 3
        throw new \InvalidArgumentException(sprintf('The %s pattern must be a single character', $part));
271
    }
272
273
    /**
274
     * Decode unicode characters.
275
     *
276
     * @see http://stackoverflow.com/a/37415135/2316257
277
     *
278
     * @throws \InvalidArgumentException if the character is not valid.
279
     */
280 21
    private function filterUnicodeCharacter(string $str): string
281
    {
282 21
        $replaced = (string) preg_replace(self::REGEXP_UNICODE, '&#x$1;', $str);
283 21
        $result = mb_convert_encoding($replaced, 'UTF-16', 'HTML-ENTITIES');
284 21
        $result = mb_convert_encoding($result, 'UTF-8', 'UTF-16');
285 21
        if (1 === mb_strlen($result)) {
286 18
            return $result;
287
        }
288
289 3
        throw new \InvalidArgumentException(sprintf('The given string `%s` is not a valid unicode string', $str));
290
    }
291
292
    /**
293
     * Return an instance with a new output object.
294
     *
295
     * This method MUST retain the state of the current instance, and return
296
     * an instance that contains the specified output class.
297
     */
298 3
    public function withOutput(Output $output): self
299
    {
300 3
        $clone = clone $this;
301 3
        $clone->output = $output;
302
303 3
        return $clone;
304
    }
305
306
    /**
307
     * Return an instance with the start included pattern.
308
     *
309
     * This method MUST retain the state of the current instance, and return
310
     * an instance that contains the specified start included character.
311
     */
312 39
    public function withStartIncluded(string $startIncludedChar): self
313
    {
314 39
        $startIncludedChar = $this->filterPattern($startIncludedChar, 'startIncluded');
315 39
        if ($startIncludedChar === $this->startIncludedChar) {
316 3
            return $this;
317
        }
318
319 36
        $clone = clone $this;
320 36
        $clone->startIncludedChar = $startIncludedChar;
321
322 36
        return $clone;
323
    }
324
325
    /**
326
     * Return an instance with the end excluded pattern.
327
     *
328
     * This method MUST retain the state of the current instance, and return
329
     * an instance that contains the specified end excluded character.
330
     */
331 39
    public function withEndExcluded(string $endExcludedChar): self
332
    {
333 39
        $endExcludedChar = $this->filterPattern($endExcludedChar, 'endExcluded');
334 39
        if ($endExcludedChar === $this->endExcludedChar) {
335 3
            return $this;
336
        }
337
338 36
        $clone = clone $this;
339 36
        $clone->endExcludedChar = $endExcludedChar;
340
341 36
        return $clone;
342
    }
343
344
    /**
345
     * Return an instance with the end included pattern.
346
     *
347
     * This method MUST retain the state of the current instance, and return
348
     * an instance that contains the specified end included character.
349
     */
350 39
    public function withEndIncluded(string $endIncludedChar): self
351
    {
352 39
        $endIncludedChar = $this->filterPattern($endIncludedChar, 'endIncluded');
353 39
        if ($endIncludedChar === $this->endIncludedChar) {
354 3
            return $this;
355
        }
356
357 36
        $clone = clone $this;
358 36
        $clone->endIncludedChar = $endIncludedChar;
359
360 36
        return $clone;
361
    }
362
363
    /**
364
     * Return an instance with the specified row width.
365
     *
366
     * This method MUST retain the state of the current instance, and return
367
     * an instance that contains the specified width.
368
     */
369 12
    public function withWidth(int $width): self
370
    {
371 12
        if ($width < 10) {
372 6
            $width = 10;
373
        }
374
375 12
        if ($width === $this->width) {
376 3
            return $this;
377
        }
378
379 9
        $clone = clone $this;
380 9
        $clone->width = $width;
381
382 9
        return $clone;
383
    }
384
385
    /**
386
     * Return an instance with the specified body block.
387
     *
388
     * This method MUST retain the state of the current instance, and return
389
     * an instance that contains the specified body pattern.
390
     */
391 45
    public function withBody(string $bodyChar): self
392
    {
393 45
        $bodyChar = $this->filterPattern($bodyChar, 'body');
394 39
        if ($bodyChar === $this->body) {
395 3
            return $this;
396
        }
397
398 36
        $clone = clone $this;
399 36
        $clone->body = $bodyChar;
400
401 36
        return $clone;
402
    }
403
404
    /**
405
     * Return an instance with the space pattern.
406
     *
407
     * This method MUST retain the state of the current instance, and return
408
     * an instance that contains the specified space character.
409
     */
410 39
    public function withSpace(string $spaceChar): self
411
    {
412 39
        $spaceChar = $this->filterPattern($spaceChar, 'space');
413 39
        if ($spaceChar === $this->space) {
414 6
            return $this;
415
        }
416
417 33
        $clone = clone $this;
418 33
        $clone->space = $spaceChar;
419
420 33
        return $clone;
421
    }
422
423
    /**
424
     * Return an instance with a new color palette.
425
     *
426
     * This method MUST retain the state of the current instance, and return
427
     * an instance that contains the specified color palette.
428
     *
429
     * @param string... $colors
0 ignored issues
show
Documentation Bug introduced by
The doc comment string... at position 0 could not be parsed: Unknown type name 'string...' at position 0 in string....
Loading history...
430
     */
431 9
    public function withColors(string ...$colors): self
432
    {
433
        $filter = static function ($value): bool {
434 6
            return in_array($value, Output::COLORS, true);
435 9
        };
436
437 9
        $colors = array_filter(array_map('strtolower', $colors), $filter);
438 9
        if ([] === $colors) {
439 6
            $colors = [Output::COLOR_DEFAULT];
440
        }
441
442 9
        if ($colors === $this->colors) {
443 6
            return $this;
444
        }
445
446 3
        $clone = clone $this;
447 3
        $clone->colors = $colors;
448
449 3
        return $clone;
450
    }
451
452
    /**
453
     * Return an instance with a new left margin size.
454
     *
455
     * This method MUST retain the state of the current instance, and return
456
     * an instance that contains the specified left margin size.
457
     */
458 9
    public function withLeftMarginSize(int $leftMarginSize): self
459
    {
460 9
        if ($leftMarginSize  === $this->leftMarginSize) {
461 3
            return $this;
462
        }
463
464 6
        if ($leftMarginSize < 0) {
465 3
            $leftMarginSize = 1;
466
        }
467
468 6
        $clone = clone $this;
469 6
        $clone->leftMarginSize = $leftMarginSize;
470
471 6
        return $clone;
472
    }
473
    /**
474
     * Return an instance with a new gap size.
475
     *
476
     * This method MUST retain the state of the current instance, and return
477
     * an instance that contains the specified gap size.
478
     */
479 9
    public function withGapSize(int $gapSize): self
480
    {
481 9
        if ($gapSize === $this->gapSize) {
482 3
            return $this;
483
        }
484
485 6
        if ($gapSize < 0) {
486 3
            $gapSize = 1;
487
        }
488
489 6
        $clone = clone $this;
490 6
        $clone->gapSize = $gapSize;
491
492 6
        return $clone;
493
    }
494
495
    /**
496
     * Return an instance with a left padding.
497
     *
498
     * This method MUST retain the state of the current instance, and return
499
     * an instance that set a left padding to the line label.
500
     */
501 9
    public function withLabelAlign(int $align): self
502
    {
503 9
        if (!in_array($align, [STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH], true)) {
504 3
            $align = STR_PAD_RIGHT;
505
        }
506
507 9
        if ($this->alignLabel === $align) {
508 6
            return $this;
509
        }
510
511 3
        $clone = clone $this;
512 3
        $clone->alignLabel = $align;
513
514 3
        return $clone;
515
    }
516
}
517