Passed
Push — 2.1 ( 438dd8...4d644f )
by Greg
15:45 queued 07:28
created

StatisticsFormat   A

Complexity

Total Complexity 40

Size/Duplication

Total Lines 168
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 87
dl 0
loc 168
rs 9.2
c 1
b 0
f 0
wmc 40

9 Methods

Rating   Name   Duplication   Size   Complexity  
A missing() 0 3 1
A percentage() 0 3 2
A age() 0 14 3
D century() 0 52 23
A interpolateRgb() 0 24 3
A hexToRgb() 0 3 1
A hitCount() 0 3 1
A record() 0 11 3
A pieChart() 0 22 3

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace Fisharebest\Webtrees;
4
5
use function array_column;
6
use function array_map;
7
use function array_sum;
8
use function array_unshift;
9
use function e;
10
use function hexdec;
11
use function ltrim;
12
use function round;
13
use function sprintf;
14
use function sprintf as sprintf1;
15
use function sprintf as sprintf2;
16
use function str_split;
17
use function strip_tags;
18
use function view;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Fisharebest\Webtrees\view. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
19
20
class StatisticsFormat
21
{
22
    public function age(int $days): string
23
    {
24
        if ($days < 31) {
25
            return I18N::plural('%s day', '%s days', $days, I18N::number($days));
26
        }
27
28
        if ($days < 365) {
29
            $months = (int) ($days / 30.5);
30
            return I18N::plural('%s month', '%s months', $months, I18N::number($months));
31
        }
32
33
        $years = (int) ($days / 365.25);
34
35
        return I18N::plural('%s year', '%s years', $years, I18N::number($years));
36
    }
37
38
    /**
39
     * Century name, English => 21st, Polish => XXI, etc.
40
     */
41
    public function century(int $century): string
42
    {
43
        if ($century < 0) {
44
            return I18N::translate('%s BCE', $this->century(-$century));
45
        }
46
47
        // The current chart engine (Google charts) can't handle <sup></sup> markup
48
        switch ($century) {
49
            case 21:
50
                return strip_tags(I18N::translateContext('CENTURY', '21st'));
51
            case 20:
52
                return strip_tags(I18N::translateContext('CENTURY', '20th'));
53
            case 19:
54
                return strip_tags(I18N::translateContext('CENTURY', '19th'));
55
            case 18:
56
                return strip_tags(I18N::translateContext('CENTURY', '18th'));
57
            case 17:
58
                return strip_tags(I18N::translateContext('CENTURY', '17th'));
59
            case 16:
60
                return strip_tags(I18N::translateContext('CENTURY', '16th'));
61
            case 15:
62
                return strip_tags(I18N::translateContext('CENTURY', '15th'));
63
            case 14:
64
                return strip_tags(I18N::translateContext('CENTURY', '14th'));
65
            case 13:
66
                return strip_tags(I18N::translateContext('CENTURY', '13th'));
67
            case 12:
68
                return strip_tags(I18N::translateContext('CENTURY', '12th'));
69
            case 11:
70
                return strip_tags(I18N::translateContext('CENTURY', '11th'));
71
            case 10:
72
                return strip_tags(I18N::translateContext('CENTURY', '10th'));
73
            case 9:
74
                return strip_tags(I18N::translateContext('CENTURY', '9th'));
75
            case 8:
76
                return strip_tags(I18N::translateContext('CENTURY', '8th'));
77
            case 7:
78
                return strip_tags(I18N::translateContext('CENTURY', '7th'));
79
            case 6:
80
                return strip_tags(I18N::translateContext('CENTURY', '6th'));
81
            case 5:
82
                return strip_tags(I18N::translateContext('CENTURY', '5th'));
83
            case 4:
84
                return strip_tags(I18N::translateContext('CENTURY', '4th'));
85
            case 3:
86
                return strip_tags(I18N::translateContext('CENTURY', '3rd'));
87
            case 2:
88
                return strip_tags(I18N::translateContext('CENTURY', '2nd'));
89
            case 1:
90
                return strip_tags(I18N::translateContext('CENTURY', '1st'));
91
            default:
92
                return ($century - 1) . '01-' . $century . '00';
93
        }
94
    }
95
96
    public function hitCount(int $count): string
97
    {
98
        return view('statistics/hit-count', ['count' => $count]);
99
    }
100
101
    public function percentage(int $count, int $total, int $precision = 1): string
102
    {
103
        return $total !== 0 ? I18N::percentage($count / $total, $precision) : '';
104
    }
105
106
    /**
107
     * @return array<int,string>
108
     */
109
    public function interpolateRgb(string $start_color, string $end_color, int $steps): array
110
    {
111
        if ($steps === 0) {
112
            return [];
113
        }
114
115
        $s        = $this->hexToRgb($start_color);
116
        $e        = $this->hexToRgb($end_color);
117
        $colors   = [];
118
        $factor_r = ($e[0] - $s[0]) / $steps;
119
        $factor_g = ($e[1] - $s[1]) / $steps;
120
        $factor_b = ($e[2] - $s[2]) / $steps;
121
122
        for ($x = 1; $x < $steps; ++$x) {
123
            $red   = (int) round($s[0] + $factor_r * $x);
124
            $green = (int) round($s[1] + $factor_g * $x);
125
            $blue  = (int) round($s[2] + $factor_b * $x);
126
127
            $colors[] = sprintf2('#%02x%02x%02x', $red, $green, $blue);
128
        }
129
130
        $colors[] = sprintf1('#%02x%02x%02x', $e[0], $e[1], $e[2]);
131
132
        return $colors;
133
    }
134
135
    /**
136
     * @return array<int>
137
     */
138
    private function hexToRgb(string $hex): array
139
    {
140
        return array_map(static fn (string $hex): int => (int) hexdec($hex), str_split(ltrim($hex, '#'), 2));
0 ignored issues
show
Bug introduced by
It seems like str_split(ltrim($hex, '#'), 2) can also be of type true; however, parameter $array of array_map() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

140
        return array_map(static fn (string $hex): int => (int) hexdec($hex), /** @scrutinizer ignore-type */ str_split(ltrim($hex, '#'), 2));
Loading history...
141
    }
142
143
    public function missing(): string
144
    {
145
        return I18N::translate('This information is not available.');
146
    }
147
148
    /**
149
     * @param array<array{0:string,1:int}> $data
150
     * @param array<string>                $colors
151
     */
152
    public function pieChart(
153
        array $data,
154
        array $colors,
155
        string $title,
156
        string $category,
157
        string $quantity,
158
        bool $percentage = false
159
    ): string {
160
        // Cannot display a pie chart if there is no data.
161
        if (array_sum(array_column($data, 1)) === 0) {
162
            return $this->missing();
163
        }
164
165
        // Google Charts require a header row.
166
        array_unshift($data, [$category, $quantity]);
167
168
        return view('statistics/other/charts/pie', [
169
            'title'            => $title,
170
            'data'             => $data,
171
            'colors'           => $colors,
172
            'labeledValueText' => $percentage ? 'percentage' : 'value',
173
            'language'         => I18N::languageTag(),
174
        ]);
175
    }
176
177
    public function record(?GedcomRecord $record): string
178
    {
179
        if ($record === null) {
180
            return $this->missing();
181
        }
182
183
        if ($record->canShow()) {
184
            return '<a href="' . e($record->url()) . '">' . $record->fullName() . '</a>';
185
        }
186
187
        return $record->fullName();
188
    }
189
}
190