lime_coverage::process()   C
last analyzed

Complexity

Conditions 11
Paths 24

Size

Total Lines 62
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 132

Importance

Changes 0
Metric Value
cc 11
eloc 32
nc 24
nop 1
dl 0
loc 62
rs 6.1722
c 0
b 0
f 0
ccs 0
cts 48
cp 0
crap 132

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * lime_coverage class.
5
 *
6
 * @extends lime_registration
7
 * @package lime
8
 */
9
class lime_coverage extends lime_registration
10
{
11
    public $files = array();
12
    public $extension = '.php';
13
    public $base_dir = '';
14
    public $harness = null;
15
    public $verbose = false;
16
    protected $coverage = array();
17
18
    /**
19
     * @param $harness
20
     * @throws Exception
21
     */
22
    public function __construct($harness)
23
    {
24
        $this->harness = $harness;
25
26
        if (!function_exists('xdebug_start_code_coverage')) {
27
            throw new Exception('You must install and enable xdebug before using lime coverage.');
28
        }
29
30
        if (!ini_get('xdebug.extended_info')) {
31
            throw new Exception('You must set xdebug.extended_info to 1 in your php.ini to use lime coverage.');
32
        }
33
    }
34
35
    /**
36
     * @throws Exception
37
     */
38
    public function run()
39
    {
40
        if (!count($this->harness->files)) {
41
            throw new Exception('You must register some test files before running coverage!');
42
        }
43
44
        if (!count($this->files)) {
45
            throw new Exception('You must register some files to cover!');
46
        }
47
48
        $this->coverage = array();
49
50
        $this->process($this->harness->files);
51
52
        $this->output($this->files);
53
    }
54
55
    /**
56
     * @param $files
57
     * @throws Exception
58
     */
59
    public function process($files)
60
    {
61
        if (!is_array($files)) {
62
            $files = array($files);
63
        }
64
65
        $tmp_file = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'test.php';
66
        foreach ($files as $file) {
67
            $tmp = <<<EOF
68
<?php
69
xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);
70
include('$file');
71
echo '<PHP_SER>'.serialize(xdebug_get_code_coverage()).'</PHP_SER>';
72
EOF;
73
            file_put_contents($tmp_file, $tmp);
74
            ob_start();
75
            // see http://trac.symfony-project.org/ticket/5437 for the explanation on the weird "cd" thing
76
            passthru(sprintf('cd & %s %s 2>&1', escapeshellarg($this->harness->php_cli), escapeshellarg($tmp_file)),
77
                $return);
78
            $retval = ob_get_clean();
79
80
            if (0 != $return) // test exited without success
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $return of type integer|null to 0; this is ambiguous as not only 0 == 0 is true, but null == 0 is true, too. Consider using a strict comparison ===.
Loading history...
81
            {
82
                // something may have gone wrong, we should warn the user so they know
83
                // it's a bug in their code and not symfony's
84
85
                $this->harness->output->echoln(sprintf('Warning: %s returned status %d, results may be inaccurate',
86
                    $file, $return), 'ERROR');
87
            }
88
89
            if (false === $cov = @unserialize(substr($retval, strpos($retval, '<PHP_SER>') + 9,
90
                    strpos($retval, '</PHP_SER>') - 9))
91
            ) {
92
                if (0 == $return) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $return of type integer|null to 0; this is ambiguous as not only 0 == 0 is true, but null == 0 is true, too. Consider using a strict comparison ===.
Loading history...
93
                    // failed to serialize, but PHP said it should of worked.
94
                    // something is seriously wrong, so abort with exception
95
                    throw new Exception(sprintf('Unable to unserialize coverage for file "%s"', $file));
96
                } else {
97
                    // failed to serialize, but PHP warned us that this might have happened.
98
                    // so we should ignore and move on
99
                    continue; // continue foreach loop through $this->harness->files
100
                }
101
            }
102
103
            foreach ($cov as $file => $lines) {
104
                if (!isset($this->coverage[$file])) {
105
                    $this->coverage[$file] = $lines;
106
                    continue;
107
                }
108
109
                foreach ($lines as $line => $flag) {
110
                    if ($flag == 1) {
111
                        $this->coverage[$file][$line] = 1;
112
                    }
113
                }
114
            }
115
        }
116
117
        if (file_exists($tmp_file)) {
118
            unlink($tmp_file);
119
        }
120
    }
121
122
    /**
123
     * @param $files
124
     */
125
    public function output($files)
126
    {
127
        ksort($this->coverage);
128
        $total_php_lines = 0;
129
        $total_covered_lines = 0;
130
        foreach ($files as $file) {
131
            $file = realpath($file);
132
            $is_covered = isset($this->coverage[$file]);
133
            $cov = isset($this->coverage[$file]) ? $this->coverage[$file] : array();
134
            $covered_lines = array();
135
            $missing_lines = array();
136
            $output = null;
0 ignored issues
show
Unused Code introduced by
$output 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...
137
138
            foreach ($cov as $line => $flag) {
139
                switch ($flag) {
140
                    case 1:
141
                        $covered_lines[] = $line;
142
                        break;
143
                    case -1:
144
                        $missing_lines[] = $line;
145
                        break;
146
                }
147
            }
148
149
            $total_lines = count($covered_lines) + count($missing_lines);
150
            if (!$total_lines) {
151
                // probably means that the file is not covered at all!
152
                $total_lines = count($this->get_php_lines(file_get_contents($file)));
153
            }
154
155
            $output = $this->harness->output;
156
            $percent = $total_lines ? count($covered_lines) * 100 / $total_lines : 0;
157
158
            $total_php_lines += $total_lines;
159
            $total_covered_lines += count($covered_lines);
160
161
            $relative_file = $this->get_relative_file($file);
162
            $output->echoln(sprintf("%-70s %3.0f%%", substr($relative_file, -min(70, strlen($relative_file))),
163
                $percent), $percent == 100 ? 'INFO' : ($percent > 90 ? 'PARAMETER' : ($percent < 20 ? 'ERROR' : '')));
164
            if ($this->verbose && $is_covered && $percent != 100) {
165
                $output->comment(sprintf("missing: %s", $this->format_range($missing_lines)));
166
            }
167
        }
168
169
        $output->echoln(sprintf("TOTAL COVERAGE: %3.0f%%",
0 ignored issues
show
Bug introduced by
The variable $output 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...
170
            $total_php_lines ? $total_covered_lines * 100 / $total_php_lines : 0));
171
    }
172
173
    /**
174
     * @param $content
175
     * @return array
176
     */
177
    public static function get_php_lines($content)
178
    {
179
        if (is_readable($content)) {
180
            $content = file_get_contents($content);
181
        }
182
183
        $tokens = token_get_all($content);
184
        $php_lines = array();
185
        $current_line = 1;
186
        $in_class = false;
187
        $in_function = false;
188
        $in_function_declaration = false;
189
        $end_of_current_expr = true;
190
        $open_braces = 0;
191
        foreach ($tokens as $token) {
192
            if (is_string($token)) {
193
                switch ($token) {
194
                    case '=':
195
                        if (false === $in_class || (false !== $in_function && !$in_function_declaration)) {
196
                            $php_lines[$current_line] = true;
197
                        }
198
                        break;
199
                    case '{':
200
                        ++$open_braces;
201
                        $in_function_declaration = false;
202
                        break;
203
                    case ';':
204
                        $in_function_declaration = false;
205
                        $end_of_current_expr = true;
206
                        break;
207
                    case '}':
208
                        $end_of_current_expr = true;
209
                        --$open_braces;
210
                        if ($open_braces == $in_class) {
211
                            $in_class = false;
212
                        }
213
                        if ($open_braces == $in_function) {
214
                            $in_function = false;
215
                        }
216
                        break;
217
                }
218
219
                continue;
220
            }
221
222
            list($id, $text) = $token;
223
224
            switch ($id) {
225
                case T_CURLY_OPEN:
226
                case T_DOLLAR_OPEN_CURLY_BRACES:
227
                    ++$open_braces;
228
                    break;
229
                case T_WHITESPACE:
230
                case T_OPEN_TAG:
231
                case T_CLOSE_TAG:
232
                    $end_of_current_expr = true;
233
                    $current_line += count(explode("\n", $text)) - 1;
234
                    break;
235
                case T_COMMENT:
236
                case T_DOC_COMMENT:
237
                    $current_line += count(explode("\n", $text)) - 1;
238
                    break;
239
                case T_CLASS:
240
                    $in_class = $open_braces;
241
                    break;
242
                case T_FUNCTION:
243
                    $in_function = $open_braces;
244
                    $in_function_declaration = true;
245
                    break;
246
                case T_AND_EQUAL:
247
                case T_BREAK:
248
                case T_CASE:
249
                case T_CATCH:
250
                case T_CLONE:
251
                case T_CONCAT_EQUAL:
252
                case T_CONTINUE:
253
                case T_DEC:
254
                case T_DECLARE:
255
                case T_DEFAULT:
256
                case T_DIV_EQUAL:
257
                case T_DO:
258
                case T_ECHO:
259
                case T_ELSEIF:
260
                case T_EMPTY:
261
                case T_ENDDECLARE:
262
                case T_ENDFOR:
263
                case T_ENDFOREACH:
264
                case T_ENDIF:
265
                case T_ENDSWITCH:
266
                case T_ENDWHILE:
267
                case T_EVAL:
268
                case T_EXIT:
269
                case T_FOR:
270
                case T_FOREACH:
271
                case T_GLOBAL:
272
                case T_IF:
273
                case T_INC:
274
                case T_INCLUDE:
275
                case T_INCLUDE_ONCE:
276
                case T_INSTANCEOF:
277
                case T_ISSET:
278
                case T_IS_EQUAL:
279
                case T_IS_GREATER_OR_EQUAL:
280
                case T_IS_IDENTICAL:
281
                case T_IS_NOT_EQUAL:
282
                case T_IS_NOT_IDENTICAL:
283
                case T_IS_SMALLER_OR_EQUAL:
284
                case T_LIST:
285
                case T_LOGICAL_AND:
286
                case T_LOGICAL_OR:
287
                case T_LOGICAL_XOR:
288
                case T_MINUS_EQUAL:
289
                case T_MOD_EQUAL:
290
                case T_MUL_EQUAL:
291
                case T_NEW:
292
                case T_OBJECT_OPERATOR:
293
                case T_OR_EQUAL:
294
                case T_PLUS_EQUAL:
295
                case T_PRINT:
296
                case T_REQUIRE:
297
                case T_REQUIRE_ONCE:
298
                case T_RETURN:
299
                case T_SL:
300
                case T_SL_EQUAL:
301
                case T_SR:
302
                case T_SR_EQUAL:
303
                case T_SWITCH:
304
                case T_THROW:
305
                case T_TRY:
306
                case T_UNSET:
307
                case T_UNSET_CAST:
308
                case T_USE:
309
                case T_WHILE:
310
                case T_XOR_EQUAL:
311
                    $php_lines[$current_line] = true;
312
                    $end_of_current_expr = false;
313
                    break;
314
                default:
315
                    if (false === $end_of_current_expr) {
316
                        $php_lines[$current_line] = true;
317
                    }
318
            }
319
        }
320
321
        return $php_lines;
322
    }
323
324
    /**
325
     * @param $content
326
     * @param $cov
327
     * @return array
328
     */
329
    public function compute($content, $cov)
330
    {
331
        $php_lines = self::get_php_lines($content);
332
333
        // we remove from $cov non php lines
334
        foreach (array_diff_key($cov, $php_lines) as $line => $tmp) {
335
            unset($cov[$line]);
336
        }
337
338
        return array($cov, $php_lines);
339
    }
340
341
    /**
342
     * @param $lines
343
     * @return string
344
     */
345
    public function format_range($lines)
346
    {
347
        sort($lines);
348
        $formatted = '';
349
        $first = -1;
350
        $last = -1;
351
        foreach ($lines as $line) {
352
            if ($last + 1 != $line) {
353
                if ($first != -1) {
354
                    $formatted .= $first == $last ? "$first " : "[$first - $last] ";
355
                }
356
                $first = $line;
357
                $last = $line;
358
            } else {
359
                $last = $line;
360
            }
361
        }
362
        if ($first != -1) {
363
            $formatted .= $first == $last ? "$first " : "[$first - $last] ";
364
        }
365
366
        return $formatted;
367
    }
368
}
369