|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/** |
|
4
|
|
|
* @author Marwan Al-Soltany <[email protected]> |
|
5
|
|
|
* @copyright Marwan Al-Soltany 2021 |
|
6
|
|
|
* For the full copyright and license information, please view |
|
7
|
|
|
* the LICENSE file that was distributed with this source code. |
|
8
|
|
|
*/ |
|
9
|
|
|
|
|
10
|
|
|
declare(strict_types=1); |
|
11
|
|
|
|
|
12
|
|
|
namespace MAKS\Velox\Helper; |
|
13
|
|
|
|
|
14
|
|
|
use MAKS\Velox\App; |
|
15
|
|
|
use MAKS\Velox\Frontend\HTML; |
|
16
|
|
|
use MAKS\Velox\Helper\Misc; |
|
17
|
|
|
|
|
18
|
|
|
/** |
|
19
|
|
|
* A class that dumps variables and exception in a nice formatting. |
|
20
|
|
|
* |
|
21
|
|
|
* @package Velox\Helper |
|
22
|
|
|
* @since 1.0.0 |
|
23
|
|
|
*/ |
|
24
|
|
|
class Dumper |
|
25
|
|
|
{ |
|
26
|
|
|
/** |
|
27
|
|
|
* Regular expressions to transform `var_export()` result |
|
28
|
|
|
* from array construct (`array()`) to valid square brackets array (`[]`). |
|
29
|
|
|
* |
|
30
|
|
|
* @var array |
|
31
|
|
|
* |
|
32
|
|
|
* @since 1.5.6 |
|
33
|
|
|
*/ |
|
34
|
|
|
protected const VAR_EXPORT_CONVERSIONS = [ |
|
35
|
|
|
// replace array construct opening alone |
|
36
|
|
|
'/array \(/' => '[', |
|
37
|
|
|
// replace array construct opening inside a function call |
|
38
|
|
|
'/(\()array\(/' => '$1[', |
|
39
|
|
|
// replace array construct opening for stdClass |
|
40
|
|
|
'/\(object\) array\(/' => '(object)[', |
|
41
|
|
|
// replace array construct closing not part of a string |
|
42
|
|
|
'/\)(\))(?=([^\']*\'[^\']*\')*[^\']*$)/' => ']$1', |
|
43
|
|
|
// replace array construct closing alone |
|
44
|
|
|
'/^([ ]*)\)(,?)$/m' => '$1]$2', |
|
45
|
|
|
// replace array construct closing inside a function call |
|
46
|
|
|
'/(\n)([ ]*)\]\)/' => '$1$2])', |
|
47
|
|
|
// replace array key with nested array |
|
48
|
|
|
'/([ ]*)(\'[^\']+\') => ([\[\'])/' => '$1$2 => $3', |
|
49
|
|
|
// replace array construct/bracket opening after arrow with newline and spaces |
|
50
|
|
|
'/=>[ ]?\n[ ]+(\[|\()/' => '=> $1', |
|
51
|
|
|
// replace any valid php after arrow with a newline and spaces |
|
52
|
|
|
'/=>[ ]?\n[ ]+([a-zA-Z0-9_\x7f-\xff])/' => '=> $1', |
|
53
|
|
|
// replace empty array brackets array with a newline and spaces |
|
54
|
|
|
'/\[[ ]?\n[ ]*\]/' => '[]', |
|
55
|
|
|
// replace NULL with null |
|
56
|
|
|
'/NULL/' => 'null', |
|
57
|
|
|
]; |
|
58
|
|
|
|
|
59
|
|
|
/** |
|
60
|
|
|
* Regular expressions to transform `var_dump()` result |
|
61
|
|
|
* from var dump syntax to a valid square brackets array (`[]`). |
|
62
|
|
|
* |
|
63
|
|
|
* @var array |
|
64
|
|
|
* |
|
65
|
|
|
* @since 1.5.6 |
|
66
|
|
|
*/ |
|
67
|
|
|
protected const VAR_DUMP_CONVERSIONS = [ |
|
68
|
|
|
// replace unnecessary line breaks after arrow with spaces only |
|
69
|
|
|
'/(=>)\s*(.+)/' => ' $1 $2', |
|
70
|
|
|
// replace opening curly brace with opening square bracket |
|
71
|
|
|
'/{\n/' => "[\n", |
|
72
|
|
|
// replace closing curly brace with closing square bracket |
|
73
|
|
|
'/}\n/' => "]\n", |
|
74
|
|
|
// replace multiline empty square brackets with single line square brackets |
|
75
|
|
|
'/\[\n\s*\]/' => "[]", |
|
76
|
|
|
// add comma to all line endings except the ones wrapped in double quotes and the ones preceded by opening brackets |
|
77
|
|
|
'/(?<!\[)\n(?=([^"]*["][^"]*["])*[^"]*$)/' => ",\n", |
|
78
|
|
|
// add object type info as comment after array opening bracket |
|
79
|
|
|
'/&?(object\(.+\))(#\d+) \(\d+\) (\[)/' => '/* $1 [SPL-ID: $2] */ $3', |
|
80
|
|
|
// add resource type info as comment in a single line |
|
81
|
|
|
'/&?(resource\(\d+\) ([\w ]+) \((\w+)\))(,)*/' => '/* $1 */ "$3"$4', |
|
82
|
|
|
// remove the type hint and variable length for strings, and arrays at the beginning of line |
|
83
|
|
|
'/^&?(?:string|array|\w+)(\(.+\)) /m' => '', |
|
84
|
|
|
// remove the type hint and variable length for strings, and arrays after arrow |
|
85
|
|
|
'/(=>) &?(?:string|array|\w+)(\(.+\)) ([\["])/' => '$1 $3', |
|
86
|
|
|
// replace bool($var), int($var), float($var), enum($var) with $var |
|
87
|
|
|
'/&?(?:bool|int|float|enum)\((.+?)\)/' => '$1', |
|
88
|
|
|
// replace uninitialized($var) with empty __NONE__ and add type info as comment |
|
89
|
|
|
'/(uninitialized\(.+\))/' => '/* $1 */ __NONE__', |
|
90
|
|
|
// replace NULL with null |
|
91
|
|
|
'/NULL/' => 'null', |
|
92
|
|
|
// replace all backslashes with escaped backslashes |
|
93
|
|
|
'/(\\\\)/' => '\\\\$1', |
|
94
|
|
|
// replace all single quotes with an escaped single quotes |
|
95
|
|
|
'/(\')/' => '\\\\$1', |
|
96
|
|
|
// replace private visibility with a better formatted one |
|
97
|
|
|
'/\["(.+?)":"(.+)":(private)\]/' => '["$1":$3($2)]', |
|
98
|
|
|
// replace key with visibility in double quotes in square brackets with key in single quotes and add visibility as comment |
|
99
|
|
|
'/\["(.+?)":(.+?)\] (=>) (.+)/' => "'$1' $3 /* $2 */ $4", |
|
100
|
|
|
// replace key in double quotes in square brackets with key in single quotes |
|
101
|
|
|
'/\["(.*)"\] (=>)/' => "'$1' $2", |
|
102
|
|
|
// replace numeric key in square brackets with key |
|
103
|
|
|
'/\[(-?\d+)\] (=>)/' => '$1 $2', |
|
104
|
|
|
// replace string opening double quotes with single quotes |
|
105
|
|
|
'/(=>)([ ]\/\*.*\*\/)? "/' => "$1$2 '", |
|
106
|
|
|
// replace string closing double quotes with single quotes |
|
107
|
|
|
'/(.+)"(,)( \/\/.*)?\n/' => "$1'$2$3\n", |
|
108
|
|
|
// replace double quotes at the beginning of line with single quotes |
|
109
|
|
|
'/^"/m' => "'", |
|
110
|
|
|
// combine consequent comments with semicolon |
|
111
|
|
|
'/[ ]\*\/ \/\*[ ]/' => '; ', |
|
112
|
|
|
// replace *RECURSION* with __RECURSION__ |
|
113
|
|
|
'/\*(RECURSION)\*/' => '__$1__', |
|
114
|
|
|
]; |
|
115
|
|
|
|
|
116
|
|
|
/** |
|
117
|
|
|
* Whether or not to use `var_dump()` instead of `var_export()` to dump the variables. |
|
118
|
|
|
* |
|
119
|
|
|
* NOTE: The dumper will always fall back to `var_dump()` if `var_export()` fails. |
|
120
|
|
|
* |
|
121
|
|
|
* @var bool |
|
122
|
|
|
*/ |
|
123
|
|
|
public static bool $useVarDump = false; |
|
124
|
|
|
|
|
125
|
|
|
/** |
|
126
|
|
|
* Accent color of exceptions page and dump block. |
|
127
|
|
|
* |
|
128
|
|
|
* @var string |
|
129
|
|
|
*/ |
|
130
|
|
|
public static string $accentColor = '#ff3a60'; |
|
131
|
|
|
|
|
132
|
|
|
/** |
|
133
|
|
|
* Contrast color of exceptions page and dump block. |
|
134
|
|
|
* |
|
135
|
|
|
* @var string |
|
136
|
|
|
*/ |
|
137
|
|
|
public static string $contrastColor = '#030035'; |
|
138
|
|
|
|
|
139
|
|
|
/** |
|
140
|
|
|
* Dumper CSS styles. |
|
141
|
|
|
* The array contains styles for: |
|
142
|
|
|
* - `exceptionPage` |
|
143
|
|
|
* - `traceBlock` |
|
144
|
|
|
* - `dumpBlock` |
|
145
|
|
|
* - `timeBlock` |
|
146
|
|
|
* - `detailsBlock` |
|
147
|
|
|
* |
|
148
|
|
|
* Currently set dumper colors can be inject in CSS using the `%accentColor%` and `%contrastColor%` placeholders. |
|
149
|
|
|
* |
|
150
|
|
|
* @var array |
|
151
|
|
|
* |
|
152
|
|
|
* @since 1.5.2 |
|
153
|
|
|
*/ |
|
154
|
|
|
public static array $styles = [ |
|
155
|
|
|
'exceptionPage' => ":root{--light:#fff;--dark:#000;--accent-color:%accentColor%;--contrast-color:%contrastColor%;--font-normal:-apple-system,'Fira Sans',Ubuntu,Helvetica,Arial,sans-serif;--font-mono:'Fira Code','Ubuntu Mono',Courier,monospace;--font-base-size:16px;--container-width:85vw;--container-max-width:1364px}@media (max-width:992px){:root{--font-base-size:14px;--container-width:100%;--container-max-width:100vw}}*,::after,::before{box-sizing:border-box;scrollbar-width:thin;scrollbar-color:var(--accent-color) rgba(0,0,0,.15)}::-webkit-scrollbar{width:8px;height:8px;opacity:1;-webkit-appearance:none}::-webkit-scrollbar-thumb{background:var(--accent-color);border-radius:4px}::-webkit-scrollbar-track,::selection{background:rgba(0,0,0,.15)}body{background:var(--light);color:var(--dark);font-family:var(--font-normal);font-size:var(--font-base-size);line-height:1.5;margin:0}h1,h2,h3,h4,h5,h6{margin:0}h1{color:var(--accent-color);font-size:2rem}h2{color:var(--accent-color);font-size:1.75rem}h3{color:var(--light)}p{font-size:1rem;margin:1rem 0}a{color:var(--accent-color)}a:hover{text-decoration:underline}ul{padding:1.5rem 1rem;margin:1rem 0}li{white-space:pre;list-style-type:none}pre{white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word}.monospace,code{font-family:var(--font-mono);word-wrap:break-word;word-break:break-all}.container{width:var(--container-width);max-width:var(--container-max-width);min-height:100vh;background:var(--light);padding:7vh calc((var(--container-max-width) * .03)) 10vh;margin:0 auto;overflow:hidden}.capture-section,.info-section,.trace-section{margin-bottom:3rem}.message{background:var(--accent-color);color:var(--light);padding:2rem 1rem 1rem 1rem}.scrollable{overflow-x:scroll}.code{display:block;width:max-content;min-width:100%;background:var(--contrast-color);font-family:var(--font-mono);font-size:.875rem;margin:0;overflow-y:scroll;-ms-overflow-style:none;scrollbar-width:none;cursor:initial}.code::-webkit-scrollbar{display:none}.code *{background:0 0}.code-line{display:inline-block;width:calc(3ch + (2 * .75ch));background:rgba(255,255,255,.25);color:var(--light);text-align:right;padding:.25rem .75ch;margin:0 1.5ch 0 0;user-select:none}.code-line.exception-line{color:var(--accent-color);font-weight:700}.code-line.exception-line+code>span>span:not(:first-child){padding-bottom:3px;border-bottom:2px solid var(--accent-color)}.button{display:inline-block;vertical-align:baseline;background:var(--accent-color);color:var(--light);font-size:1rem;text-decoration:none;padding:.5rem 1rem;margin:0 0 1rem 0;border:none;border-radius:2.5rem;cursor:pointer}.button:hover{background:var(--contrast-color);text-decoration:inherit}.button:last-child{margin-bottom:0}.table{width:100%;border-collapse:collapse;border-spacing:0}.table .table-cell{padding:.75rem}.table .table-head .table-cell{background:var(--contrast-color);color:var(--light);text-align:left;padding-top:.75rem;padding-bottom:.75rem}.table-cell.compact{width:1%}.table-row{background:var(--light);border-top:1px solid rgba(0,0,0,.15)}.table .table-row:hover{background:rgba(0,0,0,.065)!important}.table .table-row.additional .table-cell{padding:0}.table .table-row.odd,.table .table-row.odd+.additional{background:var(--light)}.table .table-row.even,.table .table-row.even+.additional{background:rgba(0,0,0,.035)}.table .table-row.even+.additional,.table .table-row.odd+.additional{border-top:none}.pop-up{cursor:help}.line,.number{text-align:center}.class,.function{font-size:.875rem;font-weight:700}.arguments{white-space:nowrap}.argument{display:inline-block;background:rgba(0,0,0,.125);color:var(--accent-color);font-size:.875rem;font-style:italic;padding:.125rem .5rem;margin:0 .25rem 0 0;border-radius:2.5rem}.argument:hover{background:var(--accent-color);color:var(--contrast-color)}.accordion{cursor:pointer;position:relative}.accordion-summary{width:1.5rem;height:1.5rem;background:var(--accent-color);color:var(--light);line-height:1.5rem;text-align:center;list-style:none;border-radius:50%;position:absolute;top:-2.2925rem;left:1.425rem;user-select:none;cursor:pointer}.accordion-summary:hover{background:var(--contrast-color)}.accordion-details{padding:0}", |
|
156
|
|
|
'traceBlock' => "background:#fff;color:%accentColor%;font-family:-apple-system,'Fira Sans',Ubuntu,Helvetica,Arial,sans-serif;font-size:12px;padding:4px 8px;margin-bottom:18px;", |
|
157
|
|
|
'dumpBlock' => "display:table;background:%contrastColor%;color:#fff;font-family:'Fira Code','Ubuntu Mono',Courier,monospace;font-size:18px;padding:18px;margin-bottom:8px;", |
|
158
|
|
|
'timeBlock' => "display:table;background:%accentColor%;color:#fff;font-family:'Fira Code','Ubuntu Mono',Courier,monospace;font-size:12px;font-weight:bold;padding:12px;margin-bottom:8px;", |
|
159
|
|
|
'detailsBlock' => "background:%accentColor%;color:#fff;font-family:-apple-system,'Fira Sans',Ubuntu,Helvetica,Arial,sans-serif;font-size:12px;font-weight:bold;padding:12px;margin-bottom:8px;cursor:pointer;user-select:none;", |
|
160
|
|
|
]; |
|
161
|
|
|
|
|
162
|
|
|
/** |
|
163
|
|
|
* Colors of syntax tokens. |
|
164
|
|
|
* |
|
165
|
|
|
* @var array |
|
166
|
|
|
*/ |
|
167
|
|
|
public static array $syntaxHighlightColors = [ |
|
168
|
|
|
'comment' => '#aeaeae', |
|
169
|
|
|
'keyword' => '#00bfff', |
|
170
|
|
|
'string' => '#e4ba80', |
|
171
|
|
|
'default' => '#e8703a', |
|
172
|
|
|
'html' => '#ab8703', |
|
173
|
|
|
]; |
|
174
|
|
|
|
|
175
|
|
|
/** |
|
176
|
|
|
* Additional CSS styling of syntax tokens. |
|
177
|
|
|
* |
|
178
|
|
|
* @var array |
|
179
|
|
|
*/ |
|
180
|
|
|
public static array $syntaxHighlightStyles = [ |
|
181
|
|
|
'comment' => 'font-weight: lighter;', |
|
182
|
|
|
'keyword' => 'font-weight: bold;', |
|
183
|
|
|
'string' => '', |
|
184
|
|
|
'default' => '', |
|
185
|
|
|
'html' => '', |
|
186
|
|
|
]; |
|
187
|
|
|
|
|
188
|
|
|
/** |
|
189
|
|
|
* PHP highlighting syntax tokens. |
|
190
|
|
|
* |
|
191
|
|
|
* @var string[] |
|
192
|
|
|
*/ |
|
193
|
|
|
private static array $syntaxHighlightTokens = ['comment', 'keyword', 'string', 'default', 'html']; |
|
194
|
|
|
|
|
195
|
|
|
|
|
196
|
|
|
/** |
|
197
|
|
|
* Dumps a variable and dies. |
|
198
|
|
|
* |
|
199
|
|
|
* @param mixed ...$variable |
|
200
|
|
|
* |
|
201
|
|
|
* @return void The result will simply get echoed. |
|
202
|
|
|
* |
|
203
|
|
|
* @codeCoverageIgnore |
|
204
|
|
|
*/ |
|
205
|
|
|
public static function dd(...$variable): void |
|
206
|
|
|
{ |
|
207
|
|
|
self::dump(...$variable); |
|
208
|
|
|
|
|
209
|
|
|
App::terminate(); |
|
210
|
|
|
} |
|
211
|
|
|
|
|
212
|
|
|
/** |
|
213
|
|
|
* Dumps a variable in a nice HTML block with syntax highlighting. |
|
214
|
|
|
* |
|
215
|
|
|
* @param mixed ...$variable |
|
216
|
|
|
* |
|
217
|
3 |
|
* @return void The result will simply get echoed. |
|
218
|
|
|
*/ |
|
219
|
3 |
|
public static function dump(...$variable): void |
|
220
|
3 |
|
{ |
|
221
|
|
|
$caller = self::getValidCallerTrace(); |
|
222
|
3 |
|
$blocks = self::getDumpingBlocks(); |
|
223
|
|
|
|
|
224
|
3 |
|
$dump = ''; |
|
225
|
3 |
|
|
|
226
|
3 |
|
foreach ($variable as $var) { |
|
227
|
3 |
|
$trace = sprintf($blocks['traceBlock'], $caller); |
|
228
|
|
|
$highlightedDump = self::exportExpressionWithSyntaxHighlighting($var, $trace); |
|
229
|
3 |
|
$block = sprintf($blocks['dumpBlock'], $highlightedDump); |
|
230
|
|
|
|
|
231
|
|
|
$dump .= sprintf($blocks['detailsBlock'], $block); |
|
232
|
3 |
|
} |
|
233
|
3 |
|
|
|
234
|
|
|
$time = (microtime(true) - START_TIME) * 1000; |
|
235
|
3 |
|
$dump .= sprintf($blocks['timeBlock'], $time); |
|
236
|
3 |
|
|
|
237
|
|
|
if (self::isCli()) { |
|
238
|
3 |
|
echo $dump; |
|
239
|
|
|
|
|
240
|
|
|
return; |
|
241
|
|
|
} |
|
242
|
|
|
|
|
243
|
|
|
// @codeCoverageIgnoreStart |
|
244
|
|
|
(new HTML(false)) |
|
245
|
|
|
->open('div', ['id' => $id = 'dump-' . uniqid()]) |
|
246
|
|
|
->style("#{$id} * { background: transparent; padding: 0; }") |
|
247
|
|
|
->div($dump) |
|
248
|
|
|
->close() |
|
249
|
|
|
->echo(); |
|
250
|
|
|
// @codeCoverageIgnoreEnd |
|
251
|
|
|
} |
|
252
|
|
|
|
|
253
|
|
|
/** |
|
254
|
|
|
* Dumps an exception in a nice HTML page or as string and exits the script. |
|
255
|
|
|
* |
|
256
|
|
|
* @param \Throwable $exception |
|
257
|
|
|
* |
|
258
|
|
|
* @return void The result will be echoed as HTML page or a string representation of the exception if the interface is CLI. |
|
259
|
|
|
* |
|
260
|
|
|
* @codeCoverageIgnore |
|
261
|
|
|
*/ |
|
262
|
|
|
public static function dumpException(\Throwable $exception): void |
|
263
|
|
|
{ |
|
264
|
|
|
if (self::isCli()) { |
|
265
|
|
|
echo $exception; |
|
266
|
|
|
|
|
267
|
|
|
App::terminate(); |
|
268
|
|
|
} |
|
269
|
|
|
|
|
270
|
|
|
self::setSyntaxHighlighting(); |
|
271
|
|
|
|
|
272
|
|
|
$reflection = new \ReflectionClass($exception); |
|
273
|
|
|
$file = $exception->getFile(); |
|
274
|
|
|
$line = $exception->getLine(); |
|
275
|
|
|
$message = $exception->getMessage(); |
|
276
|
|
|
$trace = $exception->getTrace(); |
|
277
|
|
|
$traceString = $exception->getTraceAsString(); |
|
278
|
|
|
$name = $reflection->getName(); |
|
279
|
|
|
$shortName = $reflection->getShortName(); |
|
280
|
|
|
$fileName = basename($file); |
|
281
|
|
|
|
|
282
|
|
|
$style = Misc::interpolate( |
|
283
|
|
|
static::$styles['exceptionPage'], |
|
284
|
|
|
[ |
|
285
|
|
|
'accentColor' => static::$accentColor, |
|
286
|
|
|
'contrastColor' => static::$contrastColor |
|
287
|
|
|
], |
|
288
|
|
|
'%%' |
|
289
|
|
|
); |
|
290
|
|
|
$favicon = '<svg xmlns="http://www.w3.org/2000/svg" version="1.0" width="512" height="512"><circle cx="256" cy="256" r="256" fill="#F00" /></svg>'; |
|
291
|
|
|
|
|
292
|
|
|
(new HTML(false)) |
|
293
|
|
|
->node('<!DOCTYPE html>') |
|
294
|
|
|
->open('html', ['lang' => 'en']) |
|
295
|
|
|
->open('head') |
|
296
|
|
|
->title('Oops! Something went wrong') |
|
297
|
|
|
->link(null, ['rel' => 'icon', 'href' => 'data:image/svg+xml;base64,' . base64_encode($favicon)]) |
|
298
|
|
|
->style($style, ['type' => 'text/css']) |
|
299
|
|
|
->close() |
|
300
|
|
|
|
|
301
|
|
|
->open('body') |
|
302
|
|
|
->open('div', ['class' => 'container']) |
|
303
|
|
|
->open('section', ['class' => 'info-section']) |
|
304
|
|
|
->h1('Uncaught "' . Misc::transform($shortName, 'title') . '"') |
|
305
|
|
|
->p( |
|
306
|
|
|
"<code><b>{$shortName}</b></code> was thrown on line <code><b>{$line}</b></code> of file " . |
|
307
|
|
|
"<code><b>{$fileName}</b></code> which prevented further execution of the code." |
|
308
|
|
|
) |
|
309
|
|
|
->open('div', ['class' => 'message']) |
|
310
|
|
|
->h3($name) |
|
311
|
|
|
// we need to decode and encode because some messages come escaped |
|
312
|
|
|
->p(nl2br(htmlspecialchars(htmlspecialchars_decode((string)$message), ENT_QUOTES, 'UTF-8'))) |
|
313
|
|
|
->close() |
|
314
|
|
|
->close() |
|
315
|
|
|
|
|
316
|
|
|
->open('section', ['class' => 'capture-section']) |
|
317
|
|
|
->h2('Thrown in:') |
|
318
|
|
|
->execute(function (HTML $html) use ($file, $line) { |
|
319
|
|
|
if (!file_exists($file)) { |
|
320
|
|
|
return; |
|
321
|
|
|
} |
|
322
|
|
|
|
|
323
|
|
|
$html |
|
324
|
|
|
->open('p') |
|
325
|
|
|
->node("File: <code><b>{$file}</b></code>") |
|
326
|
|
|
->entity('nbsp') |
|
327
|
|
|
->entity('nbsp') |
|
328
|
|
|
->a('Open in <b>VS Code</b>', [ |
|
329
|
|
|
'href' => sprintf('vscode://file/%s:%d', $file, $line), |
|
330
|
|
|
'class' => 'button', |
|
331
|
|
|
]) |
|
332
|
|
|
->close(); |
|
333
|
|
|
|
|
334
|
|
|
$html->div(Dumper::highlightFile($file, $line), ['class' => 'scrollable']); |
|
335
|
|
|
}) |
|
336
|
|
|
->close() |
|
337
|
|
|
|
|
338
|
|
|
->open('section', ['class' => 'trace-section']) |
|
339
|
|
|
->h2('Stack trace:') |
|
340
|
|
|
->execute(function (HTML $html) use ($trace, $traceString) { |
|
341
|
|
|
if (!count($trace)) { |
|
342
|
|
|
$html->pre($traceString); |
|
343
|
|
|
|
|
344
|
|
|
return; |
|
345
|
|
|
} |
|
346
|
|
|
|
|
347
|
|
|
$html->node(Dumper::tabulateStacktrace($trace)); |
|
348
|
|
|
}) |
|
349
|
|
|
->close() |
|
350
|
|
|
->close() |
|
351
|
|
|
->close() |
|
352
|
|
|
->close() |
|
353
|
|
|
->echo(); |
|
354
|
|
|
|
|
355
|
|
|
App::terminate(); |
|
356
|
|
|
} |
|
357
|
|
|
|
|
358
|
|
|
/** |
|
359
|
|
|
* Highlights the passed file with the possibility to focus a specific line. |
|
360
|
|
|
* |
|
361
|
|
|
* @param string $file The file to highlight. |
|
362
|
|
|
* @param int $line The line to focus. |
|
363
|
|
|
* |
|
364
|
|
|
* @return string The hightailed file as HTML. |
|
365
|
|
|
* |
|
366
|
|
|
* @since 1.5.5 |
|
367
|
|
|
* |
|
368
|
|
|
* @codeCoverageIgnore |
|
369
|
|
|
*/ |
|
370
|
|
|
private static function highlightFile(string $file, ?int $line = null): string |
|
371
|
|
|
{ |
|
372
|
|
|
return (new HTML(false)) |
|
373
|
|
|
->open('div', ['class' => 'code-highlight']) |
|
374
|
|
|
->open('ul', ['class' => 'code']) |
|
375
|
|
|
->execute(function (HTML $html) use ($file, $line) { |
|
376
|
|
|
$file = (string)$file; |
|
377
|
|
|
$line = (int)$line; |
|
378
|
|
|
$lines = file_exists($file) ? file($file) : []; |
|
379
|
|
|
$count = count($lines); |
|
380
|
|
|
$offset = !$line ? $count : 5; |
|
381
|
|
|
|
|
382
|
|
|
for ($i = $line - $offset; $i < $line + $offset; $i++) { |
|
383
|
|
|
if (!($i > 0 && $i < $count)) { |
|
384
|
|
|
continue; |
|
385
|
|
|
} |
|
386
|
|
|
|
|
387
|
|
|
$highlightedCode = highlight_string('<?php ' . $lines[$i], true); |
|
388
|
|
|
$highlightedCode = preg_replace( |
|
389
|
|
|
['/\n/', '/<br ?\/?>/', '/<\?php /'], |
|
390
|
|
|
['', '', ''], |
|
391
|
|
|
$highlightedCode |
|
392
|
|
|
); |
|
393
|
|
|
|
|
394
|
|
|
$causer = $i === $line - 1; |
|
395
|
|
|
$number = strval($i + 1); |
|
396
|
|
|
|
|
397
|
|
|
if ($causer) { |
|
398
|
|
|
$number = str_pad('>', strlen($number), '=', STR_PAD_LEFT); |
|
399
|
|
|
} |
|
400
|
|
|
|
|
401
|
|
|
$html |
|
402
|
|
|
->open('li') |
|
403
|
|
|
->condition($causer === true) |
|
404
|
|
|
->span($number, ['class' => 'code-line exception-line']) |
|
405
|
|
|
->condition($causer === false) |
|
406
|
|
|
->span($number, ['class' => 'code-line']) |
|
407
|
|
|
->node($highlightedCode) |
|
408
|
|
|
->close(); |
|
409
|
|
|
} |
|
410
|
|
|
}) |
|
411
|
|
|
->close() |
|
412
|
|
|
->close() |
|
413
|
|
|
->return(); |
|
414
|
|
|
} |
|
415
|
|
|
|
|
416
|
|
|
/** |
|
417
|
|
|
* Tabulates the passed stacktrace in an HTML table. |
|
418
|
|
|
* |
|
419
|
|
|
* @param array $trace Exception stacktrace array. |
|
420
|
|
|
* |
|
421
|
|
|
* @return string The tabulated trace as HTML. |
|
422
|
|
|
* |
|
423
|
|
|
* @since 1.5.5 |
|
424
|
|
|
* |
|
425
|
|
|
* @codeCoverageIgnore |
|
426
|
|
|
*/ |
|
427
|
|
|
private static function tabulateStacktrace(array $trace): string |
|
428
|
|
|
{ |
|
429
|
|
|
return (new HTML(false)) |
|
430
|
|
|
->p('<i>Fields with * can reveal more info. * Hoverable. ** Clickable.</i>') |
|
431
|
|
|
->open('div', ['class' => 'scrollable']) |
|
432
|
|
|
->open('table', ['class' => 'table']) |
|
433
|
|
|
->open('thead', ['class' => 'table-head']) |
|
434
|
|
|
->open('tr', ['class' => 'table-row']) |
|
435
|
|
|
->th('No. **', ['class' => 'table-cell compact']) |
|
436
|
|
|
->th('File *', ['class' => 'table-cell']) |
|
437
|
|
|
->th('Line', ['class' => 'table-cell compact']) |
|
438
|
|
|
->th('Class', ['class' => 'table-cell']) |
|
439
|
|
|
->th('Function', ['class' => 'table-cell']) |
|
440
|
|
|
->th('Arguments *', ['class' => 'table-cell']) |
|
441
|
|
|
->close() |
|
442
|
|
|
->close() |
|
443
|
|
|
->open('tbody', ['class' => 'table-body']) |
|
444
|
|
|
->execute(function (HTML $html) use ($trace) { |
|
445
|
|
|
foreach ($trace as $i => $trace) { |
|
446
|
|
|
$count = (int)$i + 1; |
|
447
|
|
|
|
|
448
|
|
|
$html |
|
449
|
|
|
->open('tr', ['class' => 'table-row ' . ($count % 2 == 0 ? 'even' : 'odd')]) |
|
450
|
|
|
->td(isset($trace['file']) ? '' : strval($count), ['class' => 'table-cell number']) |
|
451
|
|
|
->td( |
|
452
|
|
|
isset($trace['file']) |
|
453
|
|
|
? sprintf('<a href="vscode://file/%s:%d" title="Open in VS Code">%s</a>', $trace['file'], $trace['line'], basename($trace['file'])) |
|
454
|
|
|
: 'N/A', |
|
455
|
|
|
['class' => 'table-cell file pop-up', 'title' => $trace['file'] ?? 'N/A'] |
|
456
|
|
|
) |
|
457
|
|
|
->td(strval($trace['line'] ?? 'N/A'), ['class' => 'table-cell line']) |
|
458
|
|
|
->td(strval($trace['class'] ?? 'N/A'), ['class' => 'table-cell class monospace']) |
|
459
|
|
|
->td(strval($trace['function'] ?? 'N/A'), ['class' => 'table-cell function monospace']) |
|
460
|
|
|
->open('td', ['class' => 'table-cell arguments monospace']) |
|
461
|
|
|
->execute(function (HTML $html) use ($trace) { |
|
462
|
|
|
if (!isset($trace['args'])) { |
|
463
|
|
|
$html->node('NULL'); |
|
464
|
|
|
|
|
465
|
|
|
return; |
|
466
|
|
|
} |
|
467
|
|
|
|
|
468
|
|
|
foreach ($trace['args'] as $argument) { |
|
469
|
|
|
$html->span(gettype($argument), [ |
|
470
|
|
|
'class' => 'argument pop-up', |
|
471
|
|
|
'title' => htmlspecialchars( |
|
472
|
|
|
Misc::callObjectMethod(Dumper::class, 'exportExpression', $argument), |
|
473
|
|
|
ENT_QUOTES, |
|
474
|
|
|
'UTF-8' |
|
475
|
|
|
), |
|
476
|
|
|
]); |
|
477
|
|
|
} |
|
478
|
|
|
}) |
|
479
|
|
|
->close() |
|
480
|
|
|
->close() |
|
481
|
|
|
->execute(function (HTML $html) use ($trace, $count) { |
|
482
|
|
|
isset($trace['file']) && $html |
|
483
|
|
|
->open('tr', ['class' => 'table-row additional', 'id' => 'trace-' . $count]) |
|
484
|
|
|
->open('td', ['class' => 'table-cell', 'colspan' => 6]) |
|
485
|
|
|
->open('details', ['class' => 'accordion']) |
|
486
|
|
|
->summary(strval($count), ['class' => 'accordion-summary']) |
|
487
|
|
|
->div( |
|
488
|
|
|
Dumper::highlightFile($trace['file'] ?? '', $trace['line'] ?? null), |
|
489
|
|
|
['class' => 'accordion-details'] |
|
490
|
|
|
) |
|
491
|
|
|
->close() |
|
492
|
|
|
->close() |
|
493
|
|
|
->close(); |
|
494
|
|
|
}); |
|
495
|
|
|
} |
|
496
|
|
|
}) |
|
497
|
|
|
->close() |
|
498
|
|
|
->close() |
|
499
|
|
|
->close() |
|
500
|
|
|
->return(); |
|
501
|
|
|
} |
|
502
|
|
|
|
|
503
|
|
|
/** |
|
504
|
|
|
* Returns dump of the passed variable using `var_export()`. |
|
505
|
|
|
* |
|
506
|
|
|
* @param mixed $variable |
|
507
|
|
|
* |
|
508
|
|
|
* @return string |
|
509
|
|
|
* |
|
510
|
1 |
|
* @since 1.5.6 |
|
511
|
|
|
*/ |
|
512
|
1 |
|
protected static function varExport($variable): string |
|
513
|
1 |
|
{ |
|
514
|
1 |
|
$dump = var_export($variable, true); |
|
515
|
1 |
|
$dump = preg_replace( |
|
516
|
|
|
array_keys(static::VAR_EXPORT_CONVERSIONS), |
|
517
|
|
|
array_values(static::VAR_EXPORT_CONVERSIONS), |
|
518
|
1 |
|
$dump |
|
519
|
|
|
); |
|
520
|
|
|
$dump = rtrim(trim(strval($dump)), ','); |
|
521
|
|
|
|
|
522
|
|
|
// var_export() indents using 3 spaces and messes the indentation up |
|
523
|
1 |
|
// with odd numbers starting from number 3, this omits spaces |
|
524
|
1 |
|
// for odd numbers making it indents using 2 spaces instead of 3 |
|
525
|
1 |
|
$dump = preg_replace_callback('/([ ]{3,})/', function ($matches) { |
|
526
|
|
|
$indentation = strlen(strlen($matches[1]) % 2 === 0 ? $matches[1] : substr($matches[1], 0, -1)); |
|
527
|
|
|
return str_repeat(' ', $indentation); |
|
528
|
1 |
|
}, $dump); |
|
529
|
|
|
|
|
530
|
|
|
return $dump; |
|
531
|
|
|
} |
|
532
|
|
|
|
|
533
|
|
|
/** |
|
534
|
|
|
* Returns dump of the passed variable using `var_dump()`. |
|
535
|
|
|
* |
|
536
|
|
|
* @param mixed $variable |
|
537
|
|
|
* |
|
538
|
|
|
* @return string |
|
539
|
|
|
* |
|
540
|
2 |
|
* @since 1.5.6 |
|
541
|
|
|
*/ |
|
542
|
2 |
|
protected static function varDump($variable): string |
|
543
|
2 |
|
{ |
|
544
|
2 |
|
ob_start(); |
|
545
|
2 |
|
var_dump($variable); |
|
|
|
|
|
|
546
|
2 |
|
$dump = ob_get_clean(); |
|
547
|
2 |
|
$dump = preg_replace( |
|
548
|
|
|
array_keys(static::VAR_DUMP_CONVERSIONS), |
|
549
|
|
|
array_values(static::VAR_DUMP_CONVERSIONS), |
|
550
|
2 |
|
$dump |
|
551
|
|
|
); |
|
552
|
2 |
|
$dump = rtrim(trim(strval($dump)), ','); |
|
553
|
|
|
|
|
554
|
|
|
return $dump; |
|
555
|
|
|
} |
|
556
|
|
|
|
|
557
|
|
|
/** |
|
558
|
|
|
* Dumps an expression using `var_export()` or `var_dump()`. |
|
559
|
|
|
* |
|
560
|
|
|
* @param mixed $expression |
|
561
|
|
|
* |
|
562
|
3 |
|
* @return string |
|
563
|
|
|
*/ |
|
564
|
3 |
|
public static function exportExpression($expression): string |
|
565
|
|
|
{ |
|
566
|
3 |
|
$recursive = strpos(print_r($expression, true), '*RECURSION*') !== false; |
|
|
|
|
|
|
567
|
2 |
|
|
|
568
|
3 |
|
$dump = static::$useVarDump == true || $recursive == true |
|
|
|
|
|
|
569
|
|
|
? self::varDump($expression) |
|
570
|
3 |
|
: self::varExport($expression); |
|
571
|
|
|
|
|
572
|
|
|
$info = static::$useVarDump == false && $recursive == true ? Misc::interpolate( |
|
|
|
|
|
|
573
|
|
|
'// {class} failed to dump the variable.{eol}' . |
|
574
|
1 |
|
'// Reason: var_export() does not handle circular references.{eol}' . |
|
575
|
3 |
|
'// Here is a dump of the variable using var_dump() formatted in a valid PHP array.{eol}{eol}', |
|
576
|
|
|
['class' => static::class, 'eol' => PHP_EOL] |
|
577
|
3 |
|
) : ''; |
|
578
|
|
|
|
|
579
|
3 |
|
$dump = $info . $dump; |
|
580
|
|
|
|
|
581
|
|
|
return $dump; |
|
582
|
|
|
} |
|
583
|
|
|
|
|
584
|
|
|
/** |
|
585
|
|
|
* Dumps an expression using `var_export()` or `var_dump()` with syntax highlighting. |
|
586
|
|
|
* |
|
587
|
|
|
* @param mixed $expression |
|
588
|
|
|
* @param string|null $phpReplacement `<?php` replacement. |
|
589
|
|
|
* |
|
590
|
3 |
|
* @return string |
|
591
|
|
|
*/ |
|
592
|
3 |
|
private static function exportExpressionWithSyntaxHighlighting($expression, ?string $phpReplacement = ''): string |
|
593
|
|
|
{ |
|
594
|
3 |
|
self::setSyntaxHighlighting(); |
|
595
|
|
|
|
|
596
|
3 |
|
$export = self::exportExpression($expression); |
|
597
|
3 |
|
|
|
598
|
|
|
$code = highlight_string('<?php ' . $export, true); |
|
599
|
3 |
|
$html = preg_replace( |
|
600
|
|
|
'/<\?php /', |
|
601
|
|
|
$phpReplacement ?? '', |
|
602
|
|
|
$code, |
|
603
|
|
|
1 |
|
604
|
3 |
|
); |
|
605
|
|
|
|
|
606
|
|
|
if (!self::isCli()) { |
|
607
|
|
|
// @codeCoverageIgnoreStart |
|
608
|
|
|
return $html; |
|
609
|
|
|
// @codeCoverageIgnoreEnd |
|
610
|
3 |
|
} |
|
611
|
|
|
|
|
612
|
3 |
|
$mixed = preg_replace_callback( |
|
613
|
3 |
|
'/@CLR\((#\w+)\)/', |
|
614
|
3 |
|
fn ($matches) => self::getAnsiCodeFromHexColor($matches[1]), |
|
615
|
3 |
|
preg_replace( |
|
616
|
|
|
['/<\w+\s+style="color:\s*(#[a-z0-9]+)">(.*?)<\/\w+>/im', '/<br ?\/?>/', '/ /'], |
|
617
|
|
|
["\e[@CLR($1)m$2\e[0m", "\n", " "], |
|
618
|
|
|
$html |
|
619
|
|
|
) |
|
620
|
3 |
|
); |
|
621
|
|
|
|
|
622
|
3 |
|
$ansi = trim(html_entity_decode(strip_tags($mixed))); |
|
623
|
|
|
|
|
624
|
|
|
return $ansi; |
|
625
|
|
|
} |
|
626
|
|
|
|
|
627
|
|
|
/** |
|
628
|
|
|
* Returns an array containing HTML/ANSI wrapping blocks. |
|
629
|
|
|
* Available blocks are: `traceBlock`, `dumpBlock`, `timeBlock`, and `detailsBlock`. |
|
630
|
|
|
* All this blocks will contain a placeholder for a `*printf()` function to inject content. |
|
631
|
|
|
* |
|
632
|
3 |
|
* @return void |
|
633
|
|
|
*/ |
|
634
|
3 |
|
private static function getDumpingBlocks(): array |
|
635
|
|
|
{ |
|
636
|
|
|
$isCli = self::isCli(); |
|
637
|
3 |
|
|
|
638
|
3 |
|
$colors = [ |
|
639
|
|
|
'accentColor' => static::$accentColor, |
|
640
|
|
|
'contrastColor' => static::$contrastColor, |
|
641
|
3 |
|
]; |
|
642
|
3 |
|
|
|
643
|
|
|
$traceBlock = HTML::div('%s', [ |
|
644
|
|
|
'style' => Misc::interpolate(static::$styles['traceBlock'], $colors, '%%') |
|
645
|
3 |
|
]); |
|
646
|
3 |
|
|
|
647
|
|
|
$dumpBlock = HTML::div('%s', [ |
|
648
|
|
|
'style' => Misc::interpolate(static::$styles['dumpBlock'], $colors, '%%') |
|
649
|
3 |
|
]); |
|
650
|
3 |
|
|
|
651
|
|
|
$timeBlock = HTML::div('START_TIME + %.2fms', [ |
|
652
|
|
|
'style' => Misc::interpolate(static::$styles['timeBlock'], $colors, '%%') |
|
653
|
3 |
|
]); |
|
654
|
3 |
|
|
|
655
|
3 |
|
$detailsBlock = (new HTML(false)) |
|
656
|
3 |
|
->open('details', ['open' => null]) |
|
657
|
|
|
->summary('Expand/Collapse', [ |
|
658
|
3 |
|
'style' => Misc::interpolate(static::$styles['detailsBlock'], $colors, '%%') |
|
659
|
3 |
|
]) |
|
660
|
3 |
|
->main('%s') |
|
661
|
|
|
->close() |
|
662
|
3 |
|
->return(); |
|
663
|
3 |
|
|
|
664
|
3 |
|
if ($isCli) { |
|
665
|
3 |
|
$traceBlock = "\n// \e[33;1mTRACE:\e[0m \e[34;46m[%s]\e[0m \n\n"; |
|
666
|
3 |
|
$dumpBlock = "%s"; |
|
667
|
|
|
$timeBlock = "\n\n// \e[36mSTART_TIME\e[0m + \e[35m%.2f\e[0mms \n\n\n"; |
|
668
|
|
|
$detailsBlock = "%s"; |
|
669
|
3 |
|
} |
|
670
|
|
|
|
|
671
|
|
|
return compact('traceBlock', 'dumpBlock', 'timeBlock', 'detailsBlock'); |
|
672
|
|
|
} |
|
673
|
|
|
|
|
674
|
|
|
/** |
|
675
|
|
|
* Returns the last caller trace before `dd()` or `dump()` if the format of `file:line`. |
|
676
|
|
|
* |
|
677
|
3 |
|
* @return string |
|
678
|
|
|
*/ |
|
679
|
3 |
|
private static function getValidCallerTrace(): string |
|
680
|
|
|
{ |
|
681
|
3 |
|
$trace = 'Trace: N/A'; |
|
682
|
|
|
|
|
683
|
3 |
|
array_filter(array_reverse(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)), function ($backtrace) use (&$trace) { |
|
684
|
3 |
|
static $hasFound = false; |
|
685
|
3 |
|
if (!$hasFound && in_array($backtrace['function'], ['dump', 'dd'])) { |
|
686
|
|
|
$trace = $backtrace['file'] . ':' . $backtrace['line']; |
|
687
|
3 |
|
$hasFound = true; |
|
688
|
|
|
|
|
689
|
|
|
return true; |
|
690
|
3 |
|
} |
|
691
|
|
|
|
|
692
|
|
|
return false; |
|
693
|
3 |
|
}); |
|
694
|
|
|
|
|
695
|
|
|
return $trace; |
|
696
|
|
|
} |
|
697
|
|
|
|
|
698
|
|
|
/** |
|
699
|
|
|
* Converts a hex color to the closest standard ANSI color code. |
|
700
|
|
|
* Standard ANSI colors include: black, red, green, yellow, blue, magenta, cyan and white. |
|
701
|
|
|
* |
|
702
|
3 |
|
* @return int |
|
703
|
|
|
*/ |
|
704
|
|
|
private static function getAnsiCodeFromHexColor(string $color): int |
|
705
|
3 |
|
{ |
|
706
|
|
|
$colors = [ |
|
707
|
|
|
'black' => ['ansi' => 30, 'rgb' => [0, 0, 0]], |
|
708
|
|
|
'red' => ['ansi' => 31, 'rgb' => [255, 0, 0]], |
|
709
|
|
|
'green' => ['ansi' => 32, 'rgb' => [0, 128, 0]], |
|
710
|
|
|
'yellow' => ['ansi' => 33, 'rgb' => [255, 255, 0]], |
|
711
|
|
|
'blue' => ['ansi' => 34, 'rgb' => [0, 0, 255]], |
|
712
|
|
|
'magenta' => ['ansi' => 35, 'rgb' => [255, 0, 255]], |
|
713
|
|
|
'cyan' => ['ansi' => 36, 'rgb' => [0, 255, 255]], |
|
714
|
|
|
'white' => ['ansi' => 37, 'rgb' => [255, 255, 255]], |
|
715
|
|
|
'default' => ['ansi' => 39, 'rgb' => [128, 128, 128]], |
|
716
|
3 |
|
]; |
|
717
|
3 |
|
|
|
718
|
|
|
$hexClr = ltrim($color, '#'); |
|
719
|
3 |
|
$hexNum = strval(strlen($hexClr)); |
|
720
|
|
|
$hexPos = [ |
|
721
|
|
|
'3' => [0, 0, 1, 1, 2, 2], |
|
722
|
|
|
'6' => [0, 1, 2, 3, 4, 5], |
|
723
|
|
|
]; |
|
724
|
3 |
|
|
|
725
|
3 |
|
[$r, $g, $b] = [ |
|
726
|
3 |
|
$hexClr[$hexPos[$hexNum][0]] . $hexClr[$hexPos[$hexNum][1]], |
|
727
|
|
|
$hexClr[$hexPos[$hexNum][2]] . $hexClr[$hexPos[$hexNum][3]], |
|
728
|
|
|
$hexClr[$hexPos[$hexNum][4]] . $hexClr[$hexPos[$hexNum][5]], |
|
729
|
3 |
|
]; |
|
730
|
|
|
|
|
731
|
3 |
|
$color = [hexdec($r), hexdec($g), hexdec($b)]; |
|
732
|
3 |
|
|
|
733
|
3 |
|
$distances = []; |
|
734
|
3 |
|
foreach ($colors as $name => $values) { |
|
735
|
3 |
|
$distances[$name] = sqrt( |
|
736
|
3 |
|
pow($values['rgb'][0] - $color[0], 2) + |
|
737
|
|
|
pow($values['rgb'][1] - $color[1], 2) + |
|
738
|
|
|
pow($values['rgb'][2] - $color[2], 2) |
|
739
|
|
|
); |
|
740
|
3 |
|
} |
|
741
|
3 |
|
|
|
742
|
3 |
|
$colorName = ''; |
|
743
|
3 |
|
$minDistance = pow(2, 30); |
|
744
|
3 |
|
foreach ($distances as $key => $value) { |
|
745
|
3 |
|
if ($value < $minDistance) { |
|
746
|
|
|
$minDistance = $value; |
|
747
|
|
|
$colorName = $key; |
|
748
|
|
|
} |
|
749
|
3 |
|
} |
|
750
|
|
|
|
|
751
|
|
|
return $colors[$colorName]['ansi']; |
|
752
|
|
|
} |
|
753
|
|
|
|
|
754
|
|
|
/** |
|
755
|
|
|
* Sets PHP syntax highlighting colors according to current class state. |
|
756
|
|
|
* |
|
757
|
|
|
* @return void |
|
758
|
|
|
* |
|
759
|
|
|
* @codeCoverageIgnore |
|
760
|
|
|
*/ |
|
761
|
|
|
private static function setSyntaxHighlighting(): void |
|
762
|
|
|
{ |
|
763
|
|
|
if (self::isCli()) { |
|
764
|
|
|
// use default entries for better contrast. |
|
765
|
|
|
return; |
|
766
|
|
|
} |
|
767
|
|
|
|
|
768
|
|
|
$tokens = self::$syntaxHighlightTokens; |
|
769
|
|
|
|
|
770
|
|
|
foreach ($tokens as $token) { |
|
771
|
|
|
$color = self::$syntaxHighlightColors[$token] ?? ini_get("highlight.{$token}"); |
|
772
|
|
|
$style = self::$syntaxHighlightStyles[$token] ?? chr(8); |
|
773
|
|
|
|
|
774
|
|
|
$highlighting = sprintf('%s;%s', $color, $style); |
|
775
|
|
|
|
|
776
|
|
|
ini_set("highlight.{$token}", $highlighting); |
|
777
|
|
|
} |
|
778
|
|
|
} |
|
779
|
|
|
|
|
780
|
|
|
/** |
|
781
|
|
|
* Checks whether the script is currently running in CLI mode or not. |
|
782
|
|
|
* |
|
783
|
3 |
|
* @return bool |
|
784
|
|
|
*/ |
|
785
|
3 |
|
private static function isCli(): bool |
|
786
|
|
|
{ |
|
787
|
|
|
return PHP_SAPI === 'cli'; |
|
788
|
|
|
} |
|
789
|
|
|
} |
|
790
|
|
|
|