Completed
Pull Request — develop (#184)
by Neil
25:03
created

BaseGraph::parseRRDCsv()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 14
nc 4
nop 2
dl 0
loc 21
rs 9.0534
c 0
b 0
f 0
1
<?php
2
/**
3
 * BaseGraph.php
4
 *
5
 * This program is free software: you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation, either version 3 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 *
18
 * @package    LibreNMS
19
 * @link       http://librenms.org
20
 * @copyright  2016 Neil Lathwood
21
 * @author     Neil Lathwood <[email protected]>
22
 */
23
24
namespace App\Graphs;
25
26
use Carbon\Carbon;
27
use Illuminate\Http\Request;
28
use Symfony\Component\Process\Process;
29
use Symfony\Component\Process\ProcessBuilder;
30
use Symfony\Component\Process\Exception\ProcessFailedException;
31
use App\Models\Device;
32
use Settings;
33
34
class BaseGraph
35
{
36
37
    private $type;
38
    private $input;
39
    private $device;
40
41
    /**
42
     * @param $value
43
     * @return $this
44
     */
45
    public function setType($value)
46
    {
47
        $this->type = $value;
48
        return $this;
49
    }
50
51
    /**
52
     * @param $input
53
     * @return $this
54
     */
55
    public function setInput($input)
56
    {
57
        $this->input = $input;
58
        return $this;
59
    }
60
61
    /**
62
     * @param $device
63
     * @return $this
64
     */
65
    public function setDevice($input)
66
    {
67
        $this->device = Device::find($input->device_id);
68
        return $this;
69
    }
70
71
    /**
72
     * @param $device
73
     * @return $this
74
     */
75
    public function getDevice()
76
    {
77
        return $this->device;
78
    }
79
80
    /**
81
     * Get json output.
82
     *
83
     * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
84
     */
85
    public function json(Request $request)
86
    {
87
        $input = $this->input;
88
        if ($request->{'source'} === 'rrd')
89
        {
90
            $build = $this->buildRRDJson($input, $this->buildRRDXport($input));
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class App\Graphs\BaseGraph as the method buildRRDXport() does only exist in the following sub-classes of App\Graphs\BaseGraph: App\Graphs\Device_bits, App\Graphs\Device_processor, App\Graphs\Device_storage, App\Graphs\Device_ucd_memory. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
91
        }
92
        $response = $this->runRRDXport($build['cmd']);
0 ignored issues
show
Bug introduced by
The variable $build 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...
93
        return $this->parseRRDJson($response, $build['headers']);
0 ignored issues
show
Unused Code introduced by
The call to BaseGraph::parseRRDJson() has too many arguments starting with $build['headers'].

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...
94
    }
95
96
    /**
97
     * Get csv output.
98
     *
99
     * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
100
     */
101
    public function csv(Request $request)
102
    {
103
        $input = $this->input;
104
        if ($request->{'source'} === 'rrd') {
105
            $build = $this->buildRRDJson($input, $this->buildRRDXport($input));
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class App\Graphs\BaseGraph as the method buildRRDXport() does only exist in the following sub-classes of App\Graphs\BaseGraph: App\Graphs\Device_bits, App\Graphs\Device_processor, App\Graphs\Device_storage, App\Graphs\Device_ucd_memory. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
106
            $response = $this->runRRDXport($build['cmd']);
107
            return $this->parseRRDCsv($response, $build['headers']);
108
        }
109
    }
110
111
    /**
112
     * Get png output.
113
     *
114
     * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
115
     */
116
    public function png(Request $request)
117
    {
118
        $input = $this->input;
119
        if ($request->{'source'} === 'rrd') {
120
            $build = $this->buildRRDGraph($input, $this->buildRRDGraphParams($input));
0 ignored issues
show
Bug introduced by
The method buildRRDGraphParams() does not exist on App\Graphs\BaseGraph. Did you maybe mean buildRRDGraph()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
121
            $response = $this->runRRDGraph($build['cmd']);
122
            return base64_encode($response);
123
        }
124
    }
125
126
    /**
127
     * Build the RRD Xport query
128
     *
129
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be array?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
130
     */
131
    public function buildRRDJson($input, $setup) {
132
        $rrd_defs = $setup['defs'];
133
        $headers  = $setup['headers'];
134
        $cmd = build_rrdtool(' xport --json -s ' . $input->{'start'} . ' -e ' . $input->{'end'} . ' ' . $rrd_defs);
135
136
        return [
137
            'headers' => $headers,
138
            'cmd'     => $cmd,
139
        ];
140
    }
141
142
    /**
143
     * Build the RRD Graph command
144
     *
145
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be array<string,array|string>?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
146
     */
147
    public function buildRRDGraph($input, $setup) {
148
        $rrd_defs = $setup['defs'];
149
        $headers = [];
150
        $cmd = build_rrdtool(' graph - -s ' . $input->{'start'} . ' -e ' . $input->{'end'} . ' ' .
151
            " --width " . $input->{'width'} . " --height " . $input->{'height'} . " --alt-autoscale-max --rigid -E -c BACK#EEEEEE00 -c SHADEA#EEEEEE00 -c SHADEB#EEEEEE00 -c \
152
                FONT#000000 -c CANVAS#FFFFFF00 -c GRID#a5a5a5 -c MGRID#FF9999 -c FRAME#5e5e5e -c ARROW#5e5e5e \
153
                -R normal --font LEGEND:8:DejaVuSansMono --font AXIS:7:DejaVuSansMono --font-render-mode normal " .
154
            $rrd_defs);
155
        return [
156
            'headers' => $headers,
157
            'cmd'     => $cmd,
158
        ];
159
    }
160
161
    /**
162
     * @param $cmd
163
     * @return mixed|string
164
     */
165
    public function runRRDGraph($cmd)
166
    {
167
        $process = new Process($cmd);
168
        $process->run();
169
        if (!$process->isSuccessful()) {
170
            throw new ProcessFailedException($process);
171
        }
172
        $output = $process->getOutput();
173
        return $output;
174
    }
175
176
    /**
177
     * Run the RRD Xport query
178
     *
179
     * @return string
180
     */
181
    public function runRRDXport($query)
182
    {
183
        $process = new Process($query);
184
        $process->run();
185
        if (!$process->isSuccessful()) {
186
            throw new ProcessFailedException($process);
187
        }
188
        $output = $process->getOutput();
189
        $output = preg_replace('/\'/', '"', $output);
190
        $output = preg_replace('/about\:/', '"meta":', $output);
191
        $output = preg_replace('/meta\:/', '"meta":', $output);
192
        $output = json_decode($output);
193
        return $output;
194
    }
195
196
    /**
197
     * Run the RRD Xport query
198
     *
199
     * @return string
200
     */
201
    public function parseRRDJson($response)
202
    {
203
        $step = $response->{'meta'}->{'step'};
204
        $start = $response->{'meta'}->{'start'};
205
        $end = $response->{'meta'}->{'end'};
0 ignored issues
show
Unused Code introduced by
$end is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
206
        $cur_time = $start;
207
        $z = 0;
208
        $tmp_data = [];
209
210
        foreach ($response->{'data'} as $data)
211
        {
212
            $tmp_data['data'][$z][] = $cur_time + $step;
213
            foreach ($data as $key => $value)
214
            {
215
                $tmp_data['data'][$z][] = (is_null($value)) ? 0 : (int) $value;
216
            }
217
            //$tmp_data[] = $data;
0 ignored issues
show
Unused Code Comprehensibility introduced by
63% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
218
            $z++;
219
        }
220
        $tmp_data['labels'] = ['x', 'A', 'B', 'C', 'D'];
221
        return json_encode($tmp_data);
222
    }
223
224
    /**
225
     * Parse RRD output to csv
226
     *
227
     * @return string
228
     */
229
    public function parseRRDCsv($response, $headers)
230
    {
231
        $step = $response->{'meta'}->{'step'};
232
        $start = $response->{'meta'}->{'start'};
233
        $end = $response->{'meta'}->{'end'};
0 ignored issues
show
Unused Code introduced by
$end is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
234
        $cur_time = $start;
235
        $output = 'Date, ' . implode(',', $headers) . PHP_EOL;
236
237
        foreach ($response->{'data'} as $data)
238
        {
239
            $output .=  Carbon::createFromTimestamp($cur_time) . ',';
240
            $tmp_data = [];
241
            foreach ($data as $key => $value)
242
            {
243
                $tmp_data[] = (is_null($value)) ? 0 : (int) $value;
244
            }
245
            $output .= implode(',', $tmp_data) . PHP_EOL;
246
            $cur_time = $cur_time + $step;
247
        }
248
        return $output;
249
    }
250
}
251