CodestatService   A
last analyzed

Complexity

Total Complexity 42

Size/Duplication

Total Lines 327
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 185
dl 0
loc 327
rs 9.0399
c 0
b 0
f 0
wmc 42

12 Methods

Rating   Name   Duplication   Size   Complexity  
A classGenerator() 0 12 4
A makeStatistic() 0 14 4
A summaryStatistic() 0 19 5
A makeSummary() 0 28 3
A __construct() 0 4 1
A calcPercentMetric() 0 12 4
A reflectionGenerator() 0 10 4
B makeAdvancedStatistic() 0 24 8
A columnAvg() 0 3 2
A withErrorsCounter() 0 3 1
A errorList() 0 3 1
A makeCommonStatistic() 0 15 5

How to fix   Complexity   

Complex Class

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

1
<?php
2
/**
3
 * Created by solly [18.10.17 5:34]
4
 */
5
6
namespace insolita\codestat\lib;
7
8
use insolita\codestat\lib\collection\Group;
9
use insolita\codestat\lib\collection\GroupCollection;
10
use insolita\codestat\lib\contracts\ClassDetectorInterface;
11
use insolita\codestat\lib\contracts\CodestatServiceInterface;
12
use ReflectionClass;
13
use SebastianBergmann\PHPLOC\Analyser;
14
use yii\helpers\ArrayHelper;
15
use function count;
16
use function in_array;
17
18
class CodestatService implements CodestatServiceInterface
19
{
20
    protected $groups;
21
    
22
    private $nonClasses = 0;
23
24
    private $withErrors = [];
25
    
26
    /**
27
     * @var \insolita\codestat\lib\contracts\ClassDetectorInterface
28
     */
29
    private $classDetector;
30
31
    private static $percentMetrics = [
32
        'cloc'=>'loc',
33
        'ncloc'=>'loc',
34
        'lloc'=>'loc',
35
        'llocClasses'=>'lloc',
36
        'llocFunctions'=>'lloc',
37
        'llocGlobal'=>'lloc',
38
        'globalConstantAccesses'=>'globalAccesses',
39
        'globalVariableAccesses'=>'globalAccesses',
40
        'superGlobalVariableAccesses'=>'globalAccesses',
41
        'instanceAttributeAccesses'=>'attributeAccesses',
42
        'staticAttributeAccesses'=>'attributeAccesses',
43
        'instanceMethodCalls'=>'methodCalls',
44
        'staticMethodCalls'=>'methodCalls',
45
        'abstractClasses'=>'classes',
46
        'concreteClasses'=>'classes',
47
        'nonStaticMethods'=>'methods',
48
        'staticMethods'=>'methods',
49
        'publicMethods'=>'methods',
50
        'nonPublicMethods'=>'methods',
51
        'namedFunctions'=>'functions',
52
        'anonymousFunctions'=>'functions',
53
        'globalConstants'=>'constants',
54
        'classConstants'=>'constants',
55
56
    ];
57
    public static $metricNames = [
58
        'Common' => [
59
            'directories' => 'Directories',
60
            'files' => 'Files',
61
        ],
62
        'Size' => [
63
            'loc' => 'Lines of Code (loc)',
64
            'cloc' => 'Comment Lines of Code (cloc)',
65
            'ncloc' => 'Non-Comment Lines of Code (ncloc)',
66
            'lloc' => 'Logical Lines of Code (lloc)',
67
            'llocClasses' => 'Classes',
68
            'classLlocAvg' => 'Average Class Length',
69
            'classLlocMin' => 'Minimum Class Length',
70
            'classLlocMax' => 'Maximum Class Length',
71
            'methodLlocAvg' => 'Average Method Length',
72
            'methodLlocMin' => 'Minimum Method Length',
73
            'methodLlocMax' => 'Maximum Method Length',
74
            'llocFunctions' => 'Functions',
75
            'llocByNof' => 'Average Function Length',
76
            'llocGlobal' => 'Not in classes or functions',
77
        ],
78
        'Cyclomatic Complexity' => [
79
            'ccnByLloc' => 'Average Complexity per LLOC',
80
            'classCcnAvg' => 'Average Complexity per Class',
81
            'classCcnMin' => 'Minimum Class Complexity',
82
            'classCcnMax' => 'Maximum Class Complexity',
83
            'methodCcnAvg' => 'Average Complexity per Method',
84
            'methodCcnMin' => 'Minimum Method Complexity',
85
            'methodCcnMax' => 'Maximum Method Complexity',
86
        ],
87
        'Dependencies' => [
88
            'globalAccesses' => 'Global Accesses',
89
            'globalConstantAccesses' => 'Global Constants',
90
            'globalVariableAccesses' => 'Global Variables',
91
            'superGlobalVariableAccesses' => 'Super-Global Variables',
92
            'attributeAccesses' => 'Attribute Accesses',
93
            'instanceAttributeAccesses' => 'Non-Static Attribute Accesses',
94
            'staticAttributeAccesses' => 'Static Attribute Accesses',
95
            'methodCalls' => 'Method Calls',
96
            'instanceMethodCalls' => 'Non-Static Method Calls',
97
            'staticMethodCalls' => 'Static Method Calls',
98
        ],
99
        'Structure' => [
100
            'namespaces' => 'Namespaces',
101
            'interfaces' => 'Interfaces',
102
            'traits' => 'Traits',
103
            'classes' => 'Classes',
104
            'abstractClasses' => 'Abstract Classes',
105
            'concreteClasses' => 'Concrete Classes',
106
            'methods' => 'Methods',
107
            'nonStaticMethods' => 'Non-Static Methods',
108
            'staticMethods' => 'Static Methods',
109
            'publicMethods' => 'Public Methods',
110
            'nonPublicMethods' => 'Non-Public Methods',
111
            'functions' => 'Functions',
112
            'namedFunctions' => 'Named Functions',
113
            'anonymousFunctions' => 'Anonymous Functions',
114
            'constants' => 'Constants',
115
            'classConstants' => 'Class Constants',
116
            'globalConstants' => 'Global Constants',
117
            'testClasses' => 'Test Classes',
118
            'testMethods' => 'Test Methods',
119
        ],
120
    ];
121
    
122
    public function __construct(ClassDetectorInterface $classDetector, GroupCollection $groups)
123
    {
124
        $this->groups = $groups;
125
        $this->classDetector = $classDetector;
126
    }
127
    
128
    /**
129
     * Return summary from partial phploc statistic information per each defined group
130
     * @see \insolita\codestat\CodeStatModule::$groupRules
131
     * @param array $files
132
     * @param callable|null  $analyseCallback
133
     *
134
     * @return array
135
     */
136
    public function makeStatistic(array $files, $analyseCallback = null):array
137
    {
138
        foreach ($this->reflectionGenerator($this->classGenerator($files)) as $reflection) {
139
            $this->groups->fill($reflection);
140
        }
141
        $statistic = [];
142
        foreach ($this->groups as $group) {
143
            if (is_callable($analyseCallback)) {
144
                $statistic[$group->getName()] = call_user_func($analyseCallback, $group);
145
            } else {
146
                $statistic[$group->getName()] = $this->makeSummary($group);
147
            }
148
        }
149
        return $statistic;
150
    }
151
152
    /**
153
     * Return full phploc statistic per each defined group
154
     * @param array $files
155
     * @param array $metrics
156
     * @return array
157
     * @see \insolita\codestat\CodeStatModule::$groupRules
158
     */
159
    public function makeAdvancedStatistic(array $files, array $metrics = []):array
160
    {
161
        foreach ($this->reflectionGenerator($this->classGenerator($files)) as $reflection) {
162
            $this->groups->fill($reflection);
163
        }
164
        $statistic = [];
165
        foreach ($this->groups as $group) {
166
            if ($group->getNumberOfClasses() === 0) {
167
                continue;
168
            }
169
            $result = (new Analyser())->countFiles($group->getFiles(), true);
170
            foreach (static::$metricNames as $groupName => $data) {
171
                $statistic[$group->getName()][$groupName] = 'Group';
172
                foreach ($data as $key => $label) {
173
                    if (!empty($metrics) && !in_array($key, $metrics, true)) {
174
                        continue;
175
                    }
176
                    $value = $this->calcPercentMetric($result, $key);
177
                    $statistic[$group->getName()][$label] = $value;
178
                }
179
            }
180
            unset($result);
181
        }
182
        return $statistic;
183
    }
184
185
    /**
186
     * Return  phploc statistic for all files
187
     * @param array $files
188
     * @param array $metrics
189
     * @return array
190
     */
191
    public function makeCommonStatistic(array $files, array $metrics = []):array
192
    {
193
        $statistic = [];
194
        $result = (new Analyser())->countFiles($files, true);
195
        foreach (static::$metricNames as $groupName => $data) {
196
            $statistic[$groupName] = 'Group';
197
            foreach ($data as $key => $label) {
198
                if (!empty($metrics) && !in_array($key, $metrics, true)) {
199
                    continue;
200
                }
201
                $value = $this->calcPercentMetric($result, $key);
202
                $statistic[$label] = $value;
203
            }
204
        }
205
        return $statistic;
206
    }
207
    
208
    /**
209
     * @param array $statistic
210
     *
211
     * @return array|mixed
212
     */
213
    public function summaryStatistic(array $statistic)
214
    {
215
        $result = [];
216
        if (!empty($statistic)) {
217
            $firstRow = reset($statistic);
218
            if (count($statistic) === 1) {
219
                $result = $firstRow;
220
            } else {
221
                $firstKeys = array_keys($firstRow);
222
                foreach ($firstKeys as $key) {
223
                    if (mb_strpos($key, '/') !== false) {
224
                        $result[$key] = $this->columnAvg(array_column($statistic, $key));
225
                    } else {
226
                        $result[$key] = array_sum(array_column($statistic, $key));
227
                    }
228
                }
229
            }
230
        }
231
        return $result;
232
    }
233
    
234
    public function withErrorsCounter():int
235
    {
236
        return count($this->withErrors);
237
    }
238
239
    public function errorList():array
240
    {
241
        return $this->withErrors;
242
    }
243
    /**
244
     * @param array $files
245
     *
246
     * @return \Generator
247
     */
248
    public function classGenerator(array $files)
249
    {
250
        foreach ($files as $filePath) {
251
            try {
252
                $className = $this->classDetector->resolveClassName($filePath);
253
                if ($className === null) {
254
                    ++$this->nonClasses;
255
                } else {
256
                    yield $className;
257
                }
258
            } catch (\Throwable $e) {
259
                $this->withErrors[] = $e->getMessage();
260
            }
261
        }
262
    }
263
    
264
    /**
265
     * @param array $column
266
     *
267
     * @return float|int
268
     */
269
    protected function columnAvg(array $column)
270
    {
271
        return count($column) > 0 ? round(array_sum($column) / count($column), 2) : 0;
272
    }
273
    
274
    /**
275
     * @param \Generator $classGen
276
     *
277
     * @return \Generator|\ReflectionClass[]
278
     */
279
    protected function reflectionGenerator($classGen)
280
    {
281
        foreach ($classGen as $class) {
282
            try {
283
                $reflection = new ReflectionClass($class);
284
                if (!$reflection->isInternal()) {
285
                    yield $reflection;
286
                }
287
            } catch (\Throwable $e) {
288
                $this->withErrors[] = $e->getMessage();
289
            }
290
        }
291
    }
292
    
293
    /**
294
     * @param \insolita\codestat\lib\collection\Group $group
295
     *
296
     * @return array
297
     */
298
    protected function makeSummary(Group $group)
299
    {
300
        $summary = [];
301
        if ($group->getNumberOfClasses() > 0) {
302
            $groupMetrics = (new Analyser())->countFiles($group->getFiles(), false);
303
            $summary['Classes'] = $groupMetrics['classes'];
304
            $summary['Methods'] = $groupMetrics['methods'];
305
            $summary['Methods/Class'] = round($groupMetrics['methods'] / $groupMetrics['classes'], 2);
306
            $summary['Lines'] = $groupMetrics['loc'];
307
            $summary['LoC'] = $groupMetrics['lloc'];
308
            $summary['LoC/Method'] = $groupMetrics['methods'] > 0
309
                ? round($groupMetrics['lloc'] / $groupMetrics['methods'], 2)
310
                : 0;
311
            $summary['Complexity'] = $groupMetrics['ccn'];
312
            $summary['Class/Complexity avg'] = round($groupMetrics['classCcnAvg'], 2);
313
            return $summary;
314
        }
315
316
        return $summary + array_fill_keys([
317
                'Classes',
318
                'Methods/Class',
319
                'Methods',
320
                'Lines',
321
                'LoC',
322
                'LoC/Method',
323
                'Complexity',
324
                'Class/Complexity avg',
325
            ], 0);
326
    }
327
328
    /**
329
     * @param array $result
330
     * @param       $key
331
     * @return string
332
     */
333
    private function calcPercentMetric(array &$result, $key):string
334
    {
335
        $value = $result[$key];
336
        if (is_float($value)) {
337
            $value = round($value, 2);
338
        }
339
        if (isset(self::$percentMetrics[$key])) {
340
            $delimiter = ArrayHelper::getValue($result, self::$percentMetrics[$key], 0);
341
            $percent = $delimiter > 0 ? round(($result[$key] / $delimiter) * 100, 2) : 0;
342
            return $value . ' [' . $percent . '%]';
343
        }
344
        return $value;
345
    }
346
}
347