Completed
Push — master ( 518bf9...f6cf33 )
by Steevan
03:24
created

DumpBacktrace.php (4 issues)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
class DumpBacktrace
4
{
5
    /** @var bool|string */
6
    protected static $removePathPrefix = true;
7
8
    /**
9
     * @param bool|string $remove
10
     */
11
    public static function setRemovePathPrefix($remove)
12
    {
13
        static::$removePathPrefix = $remove;
14
    }
15
16
    /**
17
     * @param int $offset
18
     * @param int|null $limit
19
     */
20
    public static function dump($offset = 0, $limit = null)
21
    {
22
        echo static::getBacktracesDump(static::getBacktraces($offset + 1, $limit));
23
    }
24
25
    /**
26
     * @param int $offset
27
     * @param int|null $limit
28
     * @return array
29
     */
30
    public static function getBacktraces($offset = 0, $limit = null)
31
    {
32
        if ($limit !== null) {
33
            $limit += $offset;
34
        }
35
36
        $filteredBacktraces = [];
37
        foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $limit) as $dumpIndex => $backtrace) {
38
            if ($dumpIndex > $offset) {
39
                $filteredBacktrace = [
40
                    'file' => isset($backtrace['file']) ? $backtrace['file'] : null,
41
                    'line' => isset($backtrace['line']) ? $backtrace['line'] : null,
42
                ];
43
                if (isset($backtrace['class'])) {
44
                    $filteredBacktrace['call'] =
45
                        $backtrace['class'] . $backtrace['type'] . $backtrace['function'] . '()';
46
                } elseif (isset($backtrace['function'])) {
47
                    $filteredBacktrace['call'] = $backtrace['function'] . '()';
48
                } else {
49
                    $filteredBacktrace['call'] = '(Unknow call)';
50
                }
51
52
                $filteredBacktraces[] = $filteredBacktrace;
53
            }
54
        }
55
56
        return $filteredBacktraces;
57
    }
58
59
    /**
60
     * @return array|null
61
     */
62
    protected static function getCaller()
63
    {
64
        $backtraces = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 7);
65
        $nextIsCaller = false;
66
        $caller = null;
67
        foreach ($backtraces as $backtrace) {
68
            if (
69
                isset($backtrace['file'])
70
                && strpos($backtrace['file'], __FILE__) !== false
71
            ) {
72
                $nextIsCaller = true;
73
            } elseif (
74
                $nextIsCaller
75
                && (
76
                    (isset($backtrace['file']) && strpos($backtrace['file'], __FILE__) === false)
77
                    || isset($backtrace['file']) === false
78
                )
79
            ) {
80
                $caller = $backtrace;
81
                break;
82
            }
83
        }
84
        if ($nextIsCaller === false && count($backtraces) > 0) {
85
            $caller = $backtraces[0];
86
        }
87
88
        return $caller;
89
    }
90
91
    /**
92
     * @param array $backtraces
93
     * @return string
94
     */
95
    protected static function getBacktracesDump(array $backtraces)
96
    {
97
        $return = static::getStylesDump();
98
        $return .= static::getJavascriptDump();
99
100
        $return .= '<div class="steevanb-backtrace-container">';
101
        $return .= static::getCallerDump();
102
103
        $return .= '
104
            <table class="table-backtrace">
105
                <tr>
106
                    <th>#</th>
107
                    <th>File::Line</th>
108
                    <th>Call</th>
109
                </tr>
110
        ';
111
        $previewPrefix = uniqid('steevanb_backtrace_preview');
112
        foreach ($backtraces as $index => $backtrace) {
113
            $return .= static::getBacktraceDump($backtrace, $index, $previewPrefix);
114
        }
115
        $return .= '</table>';
116
117
        $return .= '</div>';
118
119
        return $return;
120
    }
121
122
    /**
123
     * @return string
124
     */
125
    protected static function getStylesDump()
126
    {
127
        return '
128
            <style type="text/css">
129
                .steevanb-backtrace-container {
130
                    padding: 5px;
131
                    border: solid 2px #9e9e9e;
132
                    background-color: #F5F5F5;
133
                    cursor: default;
134
                    font-family: monospace;
135
                }
136
                .steevanb-backtrace-container table {
137
                    border-collapse: collapse;
138
                }
139
                .steevanb-backtrace-container table.table-backtrace tr.dark {
140
                    background-color: #e5e5e5;
141
                }
142
                .steevanb-backtrace-container table.table-backtrace td {
143
                    padding: 2px !important;
144
                }
145
                .steevanb-backtrace-container table.table-backtrace td a,
146
                .steevanb-backtrace-container table.table-backtrace td a:hover,
147
                .steevanb-backtrace-container table.table-backtrace td a:visited
148
                {
149
                    color: #4e7ca9 !important;
150
                    text-decoration: none !important;
151
                    cursor: pointer !important;
152
                }
153
                .steevanb-backtrace-container table.table-backtrace td a:hover {
154
                    text-decoration: underline !important;
155
                }
156
            </style>';
157
    }
158
159
    /**
160
     * @return string
161
     */
162
    protected static function getJavascriptDump()
163
    {
164
        return '
165
            <script type="text/javascript">
166
                function steevanb_dev_showCodePreview(id)
167
                {
168
                    var element = document.getElementById(id);
169
                    element.style.display = (element.style.display === "none") ? "" : "none";
170
                }
171
            </script>';
172
    }
173
174
    /**
175
     * @param array $backtrace
176
     * @param int $index
177
     * @param string $previewPrefix
178
     * @return string
179
     */
180
    protected static function getBacktraceDump(array $backtrace, $index, $previewPrefix)
181
    {
182
        if (isset($backtrace['file'])) {
183
            $file = basename($backtrace['file']);
184
            $filePath = $backtrace['file'];
185
            $fileFound = true;
186
        } else {
187
            $filePath = null;
188
            $fileFound = false;
189
        }
190
191
        if (isset($backtrace['line'])) {
192
            $line = $backtrace['line'];
193
            $lineFound = true;
194
        } else {
195
            $lineFound = false;
196
        }
197
198
        if ($fileFound === false && $lineFound === false) {
199
            $fileLineHtml = '\Closure';
200
        } else {
201
            $previewId = $previewPrefix . '_' . $index;
202
            $codePreview = static::getCodePreview($filePath, $line);
0 ignored issues
show
The variable $line 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...
203
            $fileLineHtml = '
204
                <a
205
                    title="' . static::getFilePath($filePath) . '"
206
                    onclick="steevanb_dev_showCodePreview(\'' . $previewId . '\')"
207
                >
208
                    ' . $file . '::' . $line . '
0 ignored issues
show
The variable $file 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...
209
                </a>
210
            ';
211
        }
212
213
        $html = '
214
            <tr' . ($index % 2 ? null : ' class="dark"') . '>
215
                <td>' . $index . '</td>
216
                <td>' . $fileLineHtml . '</td>
217
                <td>' . $backtrace['call'] . '</td>
218
            </tr>
219
        ';
220
        if ($fileFound && $lineFound) {
221
            $html .= '
222
                <tr' . ($index % 2 ? null : ' class="dark"') . ' id="' . $previewId . '" style="display: none">
0 ignored issues
show
The variable $previewId 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...
223
                    <td colspan="3"><pre>' . $codePreview . '</pre></td>
0 ignored issues
show
The variable $codePreview 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...
224
                </tr>
225
            ';
226
        }
227
228
        return $html;
229
    }
230
231
    /**
232
     * @return string
233
     */
234
    protected static function getCallerDump()
235
    {
236
        $caller = static::getCaller();
237
238
        $return = '<div style="padding: 5px; background-color: #78a1c9; color: white; font-weight: bold">';
239
        $header = null;
240
        if (is_array($caller)) {
241
            $header .= isset($caller['file']) ? static::getFilePath($caller['file']) : '(Unknow file)';
242
            $header .= isset($caller['line']) ? '::' . $caller['line'] : '::(Unknow line)';
243
        } else {
244
            $header= 'Unkonw caller';
245
        }
246
        $return .= $header;
247
        $return .= '</div>';
248
249
        return $return;
250
    }
251
252
    /**
253
     * @param string $code
254
     * @return string
255
     */
256
    protected static function highlightCode($code)
257
    {
258
        $highlight = highlight_string('<?php ' . $code, true);
259
        $highlight = str_replace('>&lt;?php&nbsp;', null, $highlight);
260
261
        return $highlight;
262
    }
263
264
    /**
265
     * @param string $file
266
     * @param int $line
267
     * @return string
268
     */
269
    protected static function getCodePreview($file, $line)
270
    {
271
        $preview = [];
272
        $lineMin = $line - 6;
273
        $lineMax = $line + 4;
274
        foreach (file($file) as $index => $codeLine) {
275
            if ($index >= $lineMin && $index <= $lineMax) {
276
                if ($index === $line - 1) {
277
                    $preview[] = '<span style="background-color: #7fd189">' . rtrim($codeLine) . '</span>';
278
                } else {
279
                    $preview[] = rtrim($codeLine);
280
                }
281
            } elseif ($index > $lineMax) {
282
                break;
283
            }
284
        }
285
286
        return implode('<br />', $preview);
287
    }
288
289
    /**
290
     * @param string $path
291
     * @return string
292
     */
293
    protected static function getFilePath($path)
294
    {
295
        $path = realpath($path);
296
297
        if (static::$removePathPrefix === false) {
298
            $return = $path;
299
        } else {
300
            // assume that we are in vendor/ dir
301
            $prefix = (static::$removePathPrefix === true)
302
                ? realpath(__DIR__ . '/../../../')
303
                : static::$removePathPrefix;
304
            $return = (substr($path, 0, strlen($prefix)) === $prefix) ? substr($path, strlen($prefix) + 1) : $path;
305
        }
306
307
        return $return;
308
    }
309
}
310