Passed
Push — master ( 247be1...a91530 )
by Insolita
01:41
created

CodestatService::calcPercentMetric()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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