1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* This file is part of the ILess |
5
|
|
|
* |
6
|
|
|
* For the full copyright and license information, please view the LICENSE |
7
|
|
|
* file that was distributed with this source code. |
8
|
|
|
*/ |
9
|
|
|
|
10
|
|
|
namespace ILess\Exception; |
11
|
|
|
|
12
|
|
|
use ILess\FileInfo; |
13
|
|
|
use ILess\Util; |
14
|
|
|
use ILess\ImportedFile; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* Base exception. |
18
|
|
|
*/ |
19
|
|
|
class Exception extends \Exception |
20
|
|
|
{ |
21
|
|
|
/** |
22
|
|
|
* The current file. |
23
|
|
|
* |
24
|
|
|
* @var ImportedFile|FileInfo|string |
25
|
|
|
*/ |
26
|
|
|
private $currentFile; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* The current parser index. |
30
|
|
|
* |
31
|
|
|
* @var int |
32
|
|
|
*/ |
33
|
|
|
private $index; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* Current line. |
37
|
|
|
* |
38
|
|
|
* @var int|null |
39
|
|
|
*/ |
40
|
|
|
private $errorLine; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* Current column. |
44
|
|
|
* |
45
|
|
|
* @var int|null |
46
|
|
|
*/ |
47
|
|
|
private $errorColumn; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Excerpt from the string which contains error. |
51
|
|
|
* |
52
|
|
|
* @var Util\StringExcerpt |
53
|
|
|
*/ |
54
|
|
|
private $excerpt; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* File editor link. Allows variable holders:. |
58
|
|
|
* |
59
|
|
|
* * `%file` or `%f` - current file |
60
|
|
|
* * `%line` or `%l` - current line |
61
|
|
|
* |
62
|
|
|
* @var string |
63
|
|
|
*/ |
64
|
|
|
protected static $fileEditUrlFormat = 'editor://open?file=%f&line=%l'; |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* File excerpt line number. |
68
|
|
|
* |
69
|
|
|
* @var int|false |
70
|
|
|
*/ |
71
|
|
|
protected static $fileExcerptLineNumber = 3; |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Constructor. |
75
|
|
|
* |
76
|
|
|
* @param string $message The exception message |
77
|
|
|
* @param int $index The current parser index |
78
|
|
|
* @param FileInfo|ImportedFile|string $currentFile The file |
79
|
|
|
* @param \Exception $previous Previous exception |
80
|
|
|
* @param int $code The exception code |
81
|
|
|
*/ |
82
|
|
|
public function __construct( |
83
|
|
|
$message = null, |
84
|
|
|
$index = null, |
85
|
|
|
$currentFile = null, |
86
|
|
|
\Exception $previous = null, |
87
|
|
|
$code = 0 |
88
|
|
|
) { |
89
|
|
|
$message = $this->formatMessage($message, $previous); |
90
|
|
|
|
91
|
|
|
parent::__construct($message, $code, $previous); |
92
|
|
|
|
93
|
|
|
$this->currentFile = $currentFile; |
94
|
|
|
$this->index = $index; |
95
|
|
|
|
96
|
|
|
if ($currentFile && $this->index !== null) { |
97
|
|
|
$this->updateFileErrorInformation(); |
98
|
|
|
} |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Formats the message. |
103
|
|
|
* |
104
|
|
|
* @param string $message The exception message |
105
|
|
|
* @param \Exception $previous Previous exception |
106
|
|
|
* |
107
|
|
|
* @return string |
108
|
|
|
*/ |
109
|
|
|
private function formatMessage($message, \Exception $previous = null) |
110
|
|
|
{ |
111
|
|
|
$messageFormatted = $message; |
112
|
|
|
if ($previous && $previous->getMessage() !== $message) { |
113
|
|
|
$messageFormatted .= ': ' . $previous->getMessage(); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
return $messageFormatted; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* Returns the current line and column. |
121
|
|
|
* |
122
|
|
|
* @param FileInfo|ImportedFile|string $currentFile The file |
123
|
|
|
* @param int $index Current position index |
124
|
|
|
* @param bool $excerpt Include the string excerpt? |
125
|
|
|
* |
126
|
|
|
* @return array |
127
|
|
|
*/ |
128
|
|
|
protected function getLocation($currentFile, $index, $column = null, $excerpt = true) |
129
|
|
|
{ |
130
|
|
|
$line = $col = $excerptContent = null; |
131
|
|
|
if ($index !== null && $currentFile) { |
132
|
|
|
$content = null; |
133
|
|
|
if ($currentFile instanceof FileInfo |
134
|
|
|
&& $currentFile->importedFile |
135
|
|
|
) { |
136
|
|
|
$content = $currentFile->importedFile->getContent(); |
137
|
|
|
} elseif (is_string($currentFile) && Util::isPathAbsolute($currentFile) |
138
|
|
|
&& is_readable($currentFile) |
139
|
|
|
) { |
140
|
|
|
$content = file_get_contents($currentFile); |
141
|
|
|
} |
142
|
|
|
if ($content) { |
143
|
|
|
list($line, $col, $excerptContent) = Util::getLocation($content, $index, $column, $excerpt); |
144
|
|
|
} |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
return [ |
148
|
|
|
$line, |
149
|
|
|
$col, |
150
|
|
|
$excerptContent, |
151
|
|
|
]; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* Updates the line, column and excerpt. |
156
|
|
|
*/ |
157
|
|
|
protected function updateFileErrorInformation() |
158
|
|
|
{ |
159
|
|
|
// recalculate the location |
160
|
|
|
list($this->errorLine, $this->errorColumn, $this->excerpt) = |
161
|
|
|
$this->getLocation($this->currentFile, $this->index, $this->errorColumn, self::getFileExcerptLineNumber()); |
|
|
|
|
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* Sets the editor url format. |
166
|
|
|
* |
167
|
|
|
* @param string $format |
168
|
|
|
*/ |
169
|
|
|
public static function setFileEditorUrlFormat($format) |
170
|
|
|
{ |
171
|
|
|
self::$fileEditUrlFormat = (string) $format; |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* Returns the editor url format. |
176
|
|
|
* |
177
|
|
|
* @return string |
178
|
|
|
*/ |
179
|
|
|
public static function getFileEditorUrlFormat() |
180
|
|
|
{ |
181
|
|
|
return self::$fileEditUrlFormat; |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* Sets the number of lines to display in file excerpts when an exception is displayed. |
186
|
|
|
* |
187
|
|
|
* @param int|false $number |
188
|
|
|
*/ |
189
|
|
|
public static function setFileExcerptLineNumber($number) |
190
|
|
|
{ |
191
|
|
|
self::$fileExcerptLineNumber = $number; |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* Returns the number of lines to display in file excerpts. |
196
|
|
|
* |
197
|
|
|
* @return int|false |
198
|
|
|
*/ |
199
|
|
|
public static function getFileExcerptLineNumber() |
200
|
|
|
{ |
201
|
|
|
return self::$fileExcerptLineNumber; |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
/** |
205
|
|
|
* Returns the file. |
206
|
|
|
* |
207
|
|
|
* @return ImportedFile|FileInfo|null |
208
|
|
|
*/ |
209
|
|
|
public function getCurrentFile() |
210
|
|
|
{ |
211
|
|
|
return $this->currentFile; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
* Sets the current file. |
216
|
|
|
* |
217
|
|
|
* @param ImportedFile|FileInfo|string $file |
218
|
|
|
* @param int $index The current index |
219
|
|
|
*/ |
220
|
|
|
public function setCurrentFile($file, $index = null) |
221
|
|
|
{ |
222
|
|
|
$this->currentFile = $file; |
223
|
|
|
if ($index !== null) { |
224
|
|
|
$this->index = $index; |
225
|
|
|
} |
226
|
|
|
$this->updateFileErrorInformation(); |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
/** |
230
|
|
|
* Returns the current index. |
231
|
|
|
* |
232
|
|
|
* @return int |
233
|
|
|
*/ |
234
|
|
|
final public function getIndex() |
235
|
|
|
{ |
236
|
|
|
return $this->index; |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
/** |
240
|
|
|
* Returns the excerpt from the string which contains the error. |
241
|
|
|
* |
242
|
|
|
* @return Util\StringExcerpt|null |
243
|
|
|
*/ |
244
|
|
|
final public function getExcerpt() |
245
|
|
|
{ |
246
|
|
|
return $this->excerpt; |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
/** |
250
|
|
|
* Sets index. |
251
|
|
|
* |
252
|
|
|
* @param int $index |
253
|
|
|
*/ |
254
|
|
|
final public function setIndex($index) |
255
|
|
|
{ |
256
|
|
|
$this->index = $index; |
257
|
|
|
$this->updateFileErrorInformation(); |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
/** |
261
|
|
|
* Returns current line from the file. |
262
|
|
|
* |
263
|
|
|
* @return int|null |
264
|
|
|
*/ |
265
|
|
|
final public function getErrorLine() |
266
|
|
|
{ |
267
|
|
|
return $this->errorLine; |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
/** |
271
|
|
|
* Returns the error column. |
272
|
|
|
* |
273
|
|
|
* @return int|null |
274
|
|
|
*/ |
275
|
|
|
final public function getErrorColumn() |
276
|
|
|
{ |
277
|
|
|
return $this->errorColumn; |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
/** |
281
|
|
|
* Returns file editor link. The link format can be customized. |
282
|
|
|
* |
283
|
|
|
* @param FileInfo|string $file The current file |
284
|
|
|
* @param int $line |
285
|
|
|
* |
286
|
|
|
* @return string|void |
287
|
|
|
* |
288
|
|
|
* @see setFileEditorUrlFormat |
289
|
|
|
*/ |
290
|
|
|
protected function getFileEditorLink($file, $line = null) |
291
|
|
|
{ |
292
|
|
|
if ($file instanceof FileInfo) { |
293
|
|
|
$path = $file->filename; |
294
|
|
|
if ($file->importedFile) { |
295
|
|
|
$path = $file->importedFile->getPath(); |
296
|
|
|
} |
297
|
|
|
if (strpos($path, '__string_to_parse__') === 0) { |
298
|
|
|
$path = '[input string]'; |
299
|
|
|
} |
300
|
|
|
} else { |
301
|
|
|
$path = $file; |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
// when in cli or not accessible via filesystem, don't generate links |
305
|
|
|
if (PHP_SAPI == 'cli' || !Util::isPathAbsolute($path)) { |
306
|
|
|
return $path; |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
return sprintf('<a href="%s" class="file-edit">%s</a>', htmlspecialchars(strtr(self::$fileEditUrlFormat, [ |
310
|
|
|
// allow more formats |
311
|
|
|
'%f' => $path, |
312
|
|
|
'%file' => $path, |
313
|
|
|
'%line' => $line, |
314
|
|
|
'%l' => $line, |
315
|
|
|
])), $path); |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
/** |
319
|
|
|
* Converts the exception to string. |
320
|
|
|
* |
321
|
|
|
* @return string |
322
|
|
|
*/ |
323
|
|
|
public function __toString() |
324
|
|
|
{ |
325
|
|
|
return $this->toString(true, php_sapi_name() !== 'cli'); |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
/** |
329
|
|
|
* Converts the exception to string. |
330
|
|
|
* |
331
|
|
|
* @param bool $includeExcerpt Include excerpt? |
332
|
|
|
* @param bool $html Convert to HTML? |
333
|
|
|
* |
334
|
|
|
* @return string |
335
|
|
|
*/ |
336
|
|
|
public function toString($includeExcerpt = true, $html = true) |
337
|
|
|
{ |
338
|
|
|
$string = []; |
339
|
|
|
if ($this->currentFile) { |
340
|
|
|
// we have an line from the file |
341
|
|
|
if (($line = $this->getErrorLine()) !== null) { |
342
|
|
|
$string[] = sprintf('%s in %s on line: %s, column: %s', $this->message, |
343
|
|
|
$this->getFileEditorLink($this->currentFile, $line), $line, $this->errorColumn); |
|
|
|
|
344
|
|
|
if ($includeExcerpt && $this->excerpt) { |
345
|
|
|
if ($html) { |
346
|
|
|
$string[] = sprintf('<pre>%s</pre>', $this->excerpt->toHtml()); |
347
|
|
|
} else { |
348
|
|
|
$string[] = $this->excerpt->toText(); |
349
|
|
|
} |
350
|
|
|
} |
351
|
|
|
} else { |
352
|
|
|
$string[] = sprintf('%s in %s on line: ?', $this->message, |
353
|
|
|
$this->getFileEditorLink($this->currentFile)); |
|
|
|
|
354
|
|
|
} |
355
|
|
|
} else { |
356
|
|
|
$string[] = $this->message; |
357
|
|
|
} |
358
|
|
|
|
359
|
|
|
return implode("\n", $string); |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
/** |
363
|
|
|
* @return string |
364
|
|
|
*/ |
365
|
|
|
public function prettyPrint($trace = false) |
366
|
|
|
{ |
367
|
|
|
$error = sprintf('<h2>%s</h2>%s', get_class($this), $this->__toString()); |
368
|
|
|
if ($trace) { |
369
|
|
|
$error .= sprintf('<h3>Trace</h3><pre class="exception-trace">%s</pre>', $this->getTraceAsString()); |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
if ($previous = $this->getPrevious()) { |
373
|
|
|
$error .= '<h3>Caused by: ' . get_class($previous) . '</h3>'; |
374
|
|
|
$error .= $previous->getMessage(); |
375
|
|
|
$error .= '<pre class="exception-trace">' . $previous->getTraceAsString() . '</pre>'; |
376
|
|
|
} |
377
|
|
|
|
378
|
|
|
return $error; |
379
|
|
|
} |
380
|
|
|
} |
381
|
|
|
|
This check looks at variables that are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.