Completed
Push — master ( a59c49...1dd4d3 )
by personal
26s queued 20s
created

Reporter   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 285
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
dl 0
loc 285
rs 10
c 0
b 0
f 0
wmc 28
lcom 1
cbo 4

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
F generate() 0 129 12
A renderPage() 0 17 1
C getTrend() 0 69 13
A isHomePage() 0 4 1
1
<?php
2
3
namespace Hal\Report\Html;
4
5
use Hal\Application\Config\Config;
6
use Hal\Component\Output\Output;
7
use Hal\Metric\Consolidated;
8
use Hal\Metric\Group\Group;
9
use Hal\Metric\Metrics;
10
11
class Reporter
12
{
13
    /**
14
     * @var Config
15
     */
16
    private $config;
17
18
    /**
19
     * @var Output
20
     */
21
    private $output;
22
23
    /**
24
     * @var string
25
     */
26
    protected $templateDir;
27
28
    /**
29
     * @var Consolidated[]
30
     */
31
    private $consolidatedByGroups;
32
33
    /**
34
     * @var Group[]
35
     */
36
    private $groups = [];
37
38
    /**
39
     * @var string
40
     */
41
    private $currentGroup;
42
43
    /**
44
     * @var string
45
     */
46
    private $assetPath = '';
47
48
    /**
49
     * @param Config $config
50
     * @param Output $output
51
     */
52
    public function __construct(Config $config, Output $output)
53
    {
54
        $this->config = $config;
55
        $this->output = $output;
56
        $this->templateDir = __DIR__ . '/../../../../templates';
57
    }
58
59
60
    public function generate(Metrics $metrics)
61
    {
62
        $logDir = $this->config->get('report-html');
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $logDir is correct as $this->config->get('report-html') (which targets Hal\Application\Config\Config::get()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
63
        if (!$logDir) {
64
            return;
65
        }
66
67
        // consolidate
68
69
        /** @var Group[] $groups */
70
        $groups = $this->config->get('groups');
71
        $this->groups = $groups;
72
        $consolidatedGroups = [];
73
        foreach ($groups as $group) {
74
            $reducedMetricsByGroup = $group->reduceMetrics($metrics);
75
            $consolidatedGroups[$group->getName()] = new Consolidated($reducedMetricsByGroup);
76
        }
77
78
        $consolidated = new Consolidated($metrics);
79
80
        // history of builds
81
        $today = (object)[
82
            'avg' => $consolidated->getAvg(),
83
            'sum' => $consolidated->getSum()
84
        ];
85
        $files = glob($logDir . '/js/history-*.json');
86
        $next = count($files) + 1;
87
        $history = [];
88
        natsort($files);
89
        foreach ($files as $filename) {
90
            array_push($history, json_decode(file_get_contents($filename)));
91
        }
92
93
        // copy sources
94
        if (!file_exists($logDir . '/js')) {
95
            mkdir($logDir . '/js', 0755, true);
96
        }
97
        if (!file_exists($logDir . '/css')) {
98
            mkdir($logDir . '/css', 0755, true);
99
        }
100
        if (!file_exists($logDir . '/images')) {
101
            mkdir($logDir . '/images', 0755, true);
102
        }
103
        if (!file_exists($logDir . '/fonts')) {
104
            mkdir($logDir . '/fonts', 0755, true);
105
        }
106
        recurse_copy($this->templateDir . '/html_report/js', $logDir . '/js');
107
        recurse_copy($this->templateDir . '/html_report/css', $logDir . '/css');
108
        recurse_copy($this->templateDir . '/html_report/images', $logDir . '/images');
109
        recurse_copy($this->templateDir . '/html_report/fonts', $logDir . '/fonts');
110
111
        // render dynamic pages
112
        $this->renderPage($this->templateDir . '/html_report/index.php', $logDir . '/index.html', $consolidated, $history);
113
        $this->renderPage($this->templateDir . '/html_report/loc.php', $logDir . '/loc.html', $consolidated, $history);
114
        $this->renderPage($this->templateDir . '/html_report/relations.php', $logDir . '/relations.html', $consolidated, $history);
115
        $this->renderPage($this->templateDir . '/html_report/coupling.php', $logDir . '/coupling.html', $consolidated, $history);
116
        $this->renderPage($this->templateDir . '/html_report/all.php', $logDir . '/all.html', $consolidated, $history);
117
        $this->renderPage($this->templateDir . '/html_report/oop.php', $logDir . '/oop.html', $consolidated, $history);
118
        $this->renderPage($this->templateDir . '/html_report/complexity.php', $logDir . '/complexity.html', $consolidated, $history);
119
        $this->renderPage($this->templateDir . '/html_report/panel.php', $logDir . '/panel.html', $consolidated, $history);
120
        $this->renderPage($this->templateDir . '/html_report/violations.php', $logDir . '/violations.html', $consolidated, $history);
121
        $this->renderPage($this->templateDir . '/html_report/packages.php', $logDir . '/packages.html', $consolidated, $history);
122
        $this->renderPage($this->templateDir . '/html_report/package_relations.php', $logDir . '/package_relations.html', $consolidated, $history);
123
        $this->renderPage($this->templateDir . '/html_report/composer.php', $logDir . '/composer.html', $consolidated, $history);
124
        if ($this->config->has('git')) {
125
            $this->renderPage($this->templateDir . '/html_report/git.php', $logDir . '/git.html', $consolidated, $consolidatedGroups, $history);
0 ignored issues
show
Unused Code introduced by
The call to Reporter::renderPage() has too many arguments starting with $history.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
126
        }
127
        $this->renderPage($this->templateDir . '/html_report/junit.php', $logDir . '/junit.html', $consolidated, $consolidatedGroups, $history);
0 ignored issues
show
Unused Code introduced by
The call to Reporter::renderPage() has too many arguments starting with $history.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
128
129
        // js data
130
        file_put_contents(
131
            sprintf('%s/js/history-%d.json', $logDir, $next),
132
            json_encode($today, JSON_PRETTY_PRINT)
133
        );
134
        file_put_contents(
135
            sprintf('%s/js/latest.json', $logDir),
136
            json_encode($today, JSON_PRETTY_PRINT)
137
        );
138
139
        // json data
140
        file_put_contents(
141
            $logDir . '/classes.js',
142
            'var classes = ' . json_encode($consolidated->getClasses(), JSON_PRETTY_PRINT)
143
        );
144
145
        // HTML files to generate
146
        $filesToGenerate = [
147
            'index',
148
            'loc',
149
            'relations',
150
            'coupling',
151
            'all',
152
            'oop',
153
            'complexity',
154
            'panel',
155
            'violation',
156
            'packages',
157
            'package_relations',
158
            'composer',
159
        ];
160
161
        // consolidated by groups
162
        foreach ($consolidatedGroups as $name => $consolidated) {
163
            $outDir = $logDir . DIRECTORY_SEPARATOR . $name;
164
            $this->currentGroup = $name;
0 ignored issues
show
Documentation Bug introduced by
It seems like $name can also be of type integer. However, the property $currentGroup is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
165
            $this->assetPath = '../';
166
167
            if (!file_exists($outDir)) {
168
                mkdir($outDir, 0755, true);
169
            }
170
171
            foreach ($filesToGenerate as $filename) {
172
                $this->renderPage(
173
                    sprintf('%s/html_report/%s.php', $this->templateDir, $filename),
174
                    sprintf('%s/%s.html', $outDir, $filename),
175
                    $consolidated,
176
                    $history
177
                );
178
            }
179
        }
180
181
        // json data
182
        file_put_contents(
183
            $outDir . '/classes.js',
0 ignored issues
show
Bug introduced by
The variable $outDir does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
184
            'var classes = ' . json_encode($consolidated->getClasses(), JSON_PRETTY_PRINT)
185
        );
186
187
        $this->output->writeln(sprintf('HTML report generated in "%s" directory', $logDir));
188
    }
189
190
    /**
191
     * @param $source
192
     * @param $destination
193
     * @return $this
194
     */
195
    public function renderPage($source, $destination, Consolidated $consolidated, $history)
196
    {
197
        $this->sum = $sum = $consolidated->getSum();
0 ignored issues
show
Bug introduced by
The property sum does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
198
        $this->avg = $avg = $consolidated->getAvg();
0 ignored issues
show
Bug introduced by
The property avg does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
199
        $this->classes = $classes = $consolidated->getClasses();
0 ignored issues
show
Bug introduced by
The property classes does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
200
        $this->files = $files = $consolidated->getFiles();
0 ignored issues
show
Bug introduced by
The property files does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
201
        $this->project = $project = $consolidated->getProject();
0 ignored issues
show
Bug introduced by
The property project does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
202
        $this->packages = $packages = $consolidated->getPackages();
0 ignored issues
show
Bug introduced by
The property packages does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
203
        $config = $this->config;
204
        $this->history = $history;
0 ignored issues
show
Bug introduced by
The property history does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
205
206
        ob_start();
207
        require $source;
208
        $content = ob_get_clean();
209
        file_put_contents($destination, $content);
210
        return $this;
211
    }
212
213
    /**
214
     * @param $type
215
     * @param $key
216
     * @return string
217
     */
218
    protected function getTrend($type, $key, $lowIsBetter = false, $highIsBetter = false)
219
    {
220
        if (!$this->isHomePage()) {
221
            return '';
222
        }
223
224
        $svg = [];
225
        $svg['gt'] = '<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
226
    <path d="M16 6l2.29 2.29-4.88 4.88-4-4L2 16.59 3.41 18l6-6 4 4 6.3-6.29L22 12V6z"/>
227
    <path d="M0 0h24v24H0z" fill="none"/>
228
</svg>';
229
        $svg['eq'] = '<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
230
    <path d="M22 12l-4-4v3H3v2h15v3z"/>
231
    <path d="M0 0h24v24H0z" fill="none"/>
232
</svg>';
233
        $svg['lt'] = '<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
234
    <path d="M16 18l2.29-2.29-4.88-4.88-4 4L2 7.41 3.41 6l6 6 4-4 6.3 6.29L22 12v6z"/>
235
    <path d="M0 0h24v24H0z" fill="none"/>
236
</svg>';
237
238
        $last = end($this->history);
239
        if (!isset($last->$type->$key)) {
240
            return '';
241
        }
242
243
        $oldValue = $last->$type->$key;
244
        $newValue = isset($this->$type->$key) ? $this->$type->$key : 0;
245
        if ($newValue > $oldValue) {
246
            $r = 'gt';
247
        } elseif ($newValue < $oldValue) {
248
            $r = 'lt';
249
        } else {
250
            $r = 'eq';
251
        }
252
253
        $diff = $newValue - $oldValue;
254
        if ($diff > 0) {
255
            $diff = '+' . $diff;
256
        }
257
258
        $goodOrBad = 'neutral';
259
        if ($lowIsBetter) {
260
            if ($newValue > $oldValue) {
261
                $goodOrBad = 'bad';
262
            } else {
263
                if ($newValue < $oldValue) {
264
                    $goodOrBad = 'good';
265
                }
266
            }
267
        }
268
        if ($highIsBetter) {
269
            if ($newValue > $oldValue) {
270
                $goodOrBad = 'good';
271
            } else {
272
                if ($newValue < $oldValue) {
273
                    $goodOrBad = 'bad';
274
                }
275
            }
276
        }
277
278
        return sprintf(
279
            '<span title="Last value: %s" class="progress progress-%s progress-%s">%s %s</span>',
280
            $oldValue,
281
            $goodOrBad,
282
            $r,
283
            $diff,
284
            $svg[$r]
285
        );
286
    }
287
288
    /**
289
     * @return bool
290
     */
291
    private function isHomePage()
292
    {
293
        return null === $this->currentGroup;
294
    }
295
}
296