Completed
Push — master ( 8301cc...3db963 )
by Steevan
04:33 queued 01:58
created

DumpBacktrace::dump()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 2
1
<?php
2
3
class DumpBacktrace
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

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