1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* TErrorHandler class file |
4
|
|
|
* |
5
|
|
|
* @author Qiang Xue <[email protected]> |
6
|
|
|
* @link https://github.com/pradosoft/prado |
7
|
|
|
* @copyright Copyright © 2005-2016 The PRADO Group |
8
|
|
|
* @license https://github.com/pradosoft/prado/blob/master/LICENSE |
9
|
|
|
* @package Prado\Exceptions |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace Prado\Exceptions; |
13
|
|
|
|
14
|
|
|
use \Prado\TApplicationMode; |
15
|
|
|
use \Prado\Prado; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* TErrorHandler class |
19
|
|
|
* |
20
|
|
|
* TErrorHandler handles all PHP user errors and exceptions generated during |
21
|
|
|
* servicing user requests. It displays these errors using different templates |
22
|
|
|
* and if possible, using languages preferred by the client user. |
23
|
|
|
* Note, PHP parsing errors cannot be caught and handled by TErrorHandler. |
24
|
|
|
* |
25
|
|
|
* The templates used to format the error output are stored under System.Exceptions. |
26
|
|
|
* You may choose to use your own templates, should you not like the templates |
27
|
|
|
* provided by Prado. Simply set {@link setErrorTemplatePath ErrorTemplatePath} |
28
|
|
|
* to the path (in namespace format) storing your own templates. |
29
|
|
|
* |
30
|
|
|
* There are two sets of templates, one for errors to be displayed to client users |
31
|
|
|
* (called external errors), one for errors to be displayed to system developers |
32
|
|
|
* (called internal errors). The template file name for the former is |
33
|
|
|
* <b>error[StatusCode][-LanguageCode].html</b>, and for the latter it is |
34
|
|
|
* <b>exception[-LanguageCode].html</b>, where StatusCode refers to response status |
35
|
|
|
* code (e.g. 404, 500) specified when {@link THttpException} is thrown, |
36
|
|
|
* and LanguageCode is the client user preferred language code (e.g. en, zh, de). |
37
|
|
|
* The templates <b>error.html</b> and <b>exception.html</b> are default ones |
38
|
|
|
* that are used if no other appropriate templates are available. |
39
|
|
|
* Note, these templates are not Prado control templates. They are simply |
40
|
|
|
* html files with keywords (e.g. %%ErrorMessage%%, %%Version%%) |
41
|
|
|
* to be replaced with the corresponding information. |
42
|
|
|
* |
43
|
|
|
* By default, TErrorHandler is registered with {@link TApplication} as the |
44
|
|
|
* error handler module. It can be accessed via {@link TApplication::getErrorHandler()}. |
45
|
|
|
* You seldom need to deal with the error handler directly. It is mainly used |
46
|
|
|
* by the application object to handle errors. |
47
|
|
|
* |
48
|
|
|
* TErrorHandler may be configured in application configuration file as follows |
49
|
|
|
* <module id="error" class="TErrorHandler" ErrorTemplatePath="System.Exceptions" /> |
50
|
|
|
* |
51
|
|
|
* @author Qiang Xue <[email protected]> |
52
|
|
|
* @package Prado\Exceptions |
53
|
|
|
* @since 3.0 |
54
|
|
|
*/ |
55
|
|
|
class TErrorHandler extends \Prado\TModule |
56
|
|
|
{ |
57
|
|
|
/** |
58
|
|
|
* error template file basename |
59
|
|
|
*/ |
60
|
|
|
const ERROR_FILE_NAME = 'error'; |
61
|
|
|
/** |
62
|
|
|
* exception template file basename |
63
|
|
|
*/ |
64
|
|
|
const EXCEPTION_FILE_NAME = 'exception'; |
65
|
|
|
/** |
66
|
|
|
* number of lines before and after the error line to be displayed in case of an exception |
67
|
|
|
*/ |
68
|
|
|
const SOURCE_LINES = 12; |
69
|
|
|
/** |
70
|
|
|
* number of prado internal function calls to be dropped from stack traces on fatal errors |
71
|
|
|
*/ |
72
|
|
|
const FATAL_ERROR_TRACE_DROP_LINES = 5; |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* @var string error template directory |
76
|
|
|
*/ |
77
|
|
|
private $_templatePath; |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* Initializes the module. |
81
|
|
|
* This method is required by IModule and is invoked by application. |
82
|
|
|
* @param TXmlElement $config module configuration |
83
|
|
|
*/ |
84
|
|
|
public function init($config) |
85
|
|
|
{ |
86
|
|
|
$this->getApplication()->setErrorHandler($this); |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* @return string the directory containing error template files. |
91
|
|
|
*/ |
92
|
|
|
public function getErrorTemplatePath() |
93
|
|
|
{ |
94
|
|
|
if ($this->_templatePath === null) { |
95
|
|
|
$this->_templatePath = Prado::getFrameworkPath() . '/Exceptions/templates'; |
96
|
|
|
} |
97
|
|
|
return $this->_templatePath; |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
/** |
101
|
|
|
* Sets the path storing all error and exception template files. |
102
|
|
|
* The path must be in namespace format, such as System.Exceptions (which is the default). |
103
|
|
|
* @param string $value template path in namespace format |
104
|
|
|
* @throws TConfigurationException if the template path is invalid |
105
|
|
|
*/ |
106
|
|
|
public function setErrorTemplatePath($value) |
107
|
|
|
{ |
108
|
|
View Code Duplication |
if (($templatePath = Prado::getPathOfNamespace($value)) !== null && is_dir($templatePath)) { |
|
|
|
|
109
|
|
|
$this->_templatePath = $templatePath; |
110
|
|
|
} else { |
111
|
|
|
throw new TConfigurationException('errorhandler_errortemplatepath_invalid', $value); |
112
|
|
|
} |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* Handles PHP user errors and exceptions. |
117
|
|
|
* This is the event handler responding to the <b>Error</b> event |
118
|
|
|
* raised in {@link TApplication}. |
119
|
|
|
* The method mainly uses appropriate template to display the error/exception. |
120
|
|
|
* It terminates the application immediately after the error is displayed. |
121
|
|
|
* @param mixed $sender sender of the event |
122
|
|
|
* @param mixed $param event parameter (if the event is raised by TApplication, it refers to the exception instance) |
123
|
|
|
*/ |
124
|
|
|
public function handleError($sender, $param) |
125
|
|
|
{ |
126
|
|
|
static $handling = false; |
127
|
|
|
// We need to restore error and exception handlers, |
128
|
|
|
// because within error and exception handlers, new errors and exceptions |
129
|
|
|
// cannot be handled properly by PHP |
130
|
|
|
restore_error_handler(); |
131
|
|
|
restore_exception_handler(); |
132
|
|
|
// ensure that we do not enter infinite loop of error handling |
133
|
|
|
if ($handling) { |
134
|
|
|
$this->handleRecursiveError($param); |
135
|
|
|
} else { |
136
|
|
|
$handling = true; |
137
|
|
|
if (($response = $this->getResponse()) !== null) { |
138
|
|
|
$response->clear(); |
139
|
|
|
} |
140
|
|
|
if (!headers_sent()) { |
141
|
|
|
header('Content-Type: text/html; charset=UTF-8'); |
142
|
|
|
} |
143
|
|
|
if ($param instanceof THttpException) { |
144
|
|
|
$this->handleExternalError($param->getStatusCode(), $param); |
145
|
|
|
} elseif ($this->getApplication()->getMode() === TApplicationMode::Debug) { |
146
|
|
|
$this->displayException($param); |
147
|
|
|
} else { |
148
|
|
|
$this->handleExternalError(500, $param); |
149
|
|
|
} |
150
|
|
|
} |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* @param string $value |
156
|
|
|
* @param Exception|null$exception |
157
|
|
|
* @return string |
158
|
|
|
* @since 3.1.6 |
159
|
|
|
*/ |
160
|
|
|
protected static function hideSecurityRelated($value, $exception = null) |
161
|
|
|
{ |
162
|
|
|
$aRpl = []; |
163
|
|
|
if ($exception !== null && $exception instanceof \Exception) { |
164
|
|
View Code Duplication |
if ($exception instanceof TPhpFatalErrorException && |
|
|
|
|
165
|
|
|
function_exists('xdebug_get_function_stack')) { |
166
|
|
|
$aTrace = array_slice(array_reverse(xdebug_get_function_stack()), self::FATAL_ERROR_TRACE_DROP_LINES, -1); |
167
|
|
|
} else { |
168
|
|
|
$aTrace = $exception->getTrace(); |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
foreach ($aTrace as $item) { |
172
|
|
|
if (isset($item['file'])) { |
173
|
|
|
$aRpl[dirname($item['file']) . DIRECTORY_SEPARATOR] = '<hidden>' . DIRECTORY_SEPARATOR; |
174
|
|
|
} |
175
|
|
|
} |
176
|
|
|
} |
177
|
|
|
$aRpl[$_SERVER['DOCUMENT_ROOT']] = '${DocumentRoot}'; |
178
|
|
|
$aRpl[str_replace('/', DIRECTORY_SEPARATOR, $_SERVER['DOCUMENT_ROOT'])] = '${DocumentRoot}'; |
179
|
|
|
$aRpl[PRADO_DIR . DIRECTORY_SEPARATOR] = '${PradoFramework}' . DIRECTORY_SEPARATOR; |
180
|
|
|
if (isset($aRpl[DIRECTORY_SEPARATOR])) { |
181
|
|
|
unset($aRpl[DIRECTORY_SEPARATOR]); |
182
|
|
|
} |
183
|
|
|
$aRpl = array_reverse($aRpl, true); |
184
|
|
|
|
185
|
|
|
return str_replace(array_keys($aRpl), $aRpl, $value); |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* Displays error to the client user. |
190
|
|
|
* THttpException and errors happened when the application is in <b>Debug</b> |
191
|
|
|
* mode will be displayed to the client user. |
192
|
|
|
* @param int $statusCode response status code |
193
|
|
|
* @param Exception $exception exception instance |
194
|
|
|
*/ |
195
|
|
|
protected function handleExternalError($statusCode, $exception) |
196
|
|
|
{ |
197
|
|
|
if (!($exception instanceof THttpException)) { |
198
|
|
|
error_log($exception->__toString()); |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
$content = $this->getErrorTemplate($statusCode, $exception); |
202
|
|
|
|
203
|
|
|
$serverAdmin = isset($_SERVER['SERVER_ADMIN']) ? $_SERVER['SERVER_ADMIN'] : ''; |
204
|
|
|
|
205
|
|
|
$isDebug = $this->getApplication()->getMode() === TApplicationMode::Debug; |
206
|
|
|
|
207
|
|
|
$errorMessage = $exception->getMessage(); |
208
|
|
|
if ($isDebug) { |
209
|
|
|
$version = $_SERVER['SERVER_SOFTWARE'] . ' <a href="https://github.com/pradosoft/prado">PRADO</a>/' . Prado::getVersion(); |
210
|
|
|
} else { |
211
|
|
|
$version = ''; |
212
|
|
|
$errorMessage = self::hideSecurityRelated($errorMessage, $exception); |
213
|
|
|
} |
214
|
|
|
$tokens = [ |
215
|
|
|
'%%StatusCode%%' => "$statusCode", |
216
|
|
|
'%%ErrorMessage%%' => htmlspecialchars($errorMessage), |
217
|
|
|
'%%ServerAdmin%%' => $serverAdmin, |
218
|
|
|
'%%Version%%' => $version, |
219
|
|
|
'%%Time%%' => @strftime('%Y-%m-%d %H:%M', time()) |
220
|
|
|
]; |
221
|
|
|
|
222
|
|
|
$this->getApplication()->getResponse()->setStatusCode($statusCode, $isDebug ? $exception->getMessage() : null); |
223
|
|
|
|
224
|
|
|
echo strtr($content, $tokens); |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* Handles error occurs during error handling (called recursive error). |
229
|
|
|
* THttpException and errors happened when the application is in <b>Debug</b> |
230
|
|
|
* mode will be displayed to the client user. |
231
|
|
|
* Error is displayed without using existing template to prevent further errors. |
232
|
|
|
* @param Exception $exception exception instance |
233
|
|
|
*/ |
234
|
|
|
protected function handleRecursiveError($exception) |
235
|
|
|
{ |
236
|
|
|
if ($this->getApplication()->getMode() === TApplicationMode::Debug) { |
237
|
|
|
echo "<html><head><title>Recursive Error</title></head>\n"; |
238
|
|
|
echo "<body><h1>Recursive Error</h1>\n"; |
239
|
|
|
echo "<pre>" . $exception->__toString() . "</pre>\n"; |
240
|
|
|
echo "</body></html>"; |
241
|
|
|
} else { |
242
|
|
|
error_log("Error happened while processing an existing error:\n" . $exception->__toString()); |
243
|
|
|
header('HTTP/1.0 500 Internal Error'); |
244
|
|
|
} |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
protected function hidePrivatePathParts($value) |
248
|
|
|
{ |
249
|
|
|
static $aRpl; |
250
|
|
|
if($aRpl === null) |
251
|
|
|
{ |
252
|
|
|
$aRpl[$_SERVER['DOCUMENT_ROOT']] = '${DocumentRoot}'; |
253
|
|
|
$aRpl[str_replace('/', DIRECTORY_SEPARATOR, $_SERVER['DOCUMENT_ROOT'])] = '${DocumentRoot}'; |
254
|
|
|
$aRpl[PRADO_DIR . DIRECTORY_SEPARATOR] = '${PradoFramework}' . DIRECTORY_SEPARATOR; |
255
|
|
|
if (isset($aRpl[DIRECTORY_SEPARATOR])) { |
256
|
|
|
unset($aRpl[DIRECTORY_SEPARATOR]); |
257
|
|
|
} |
258
|
|
|
$aRpl = array_reverse($aRpl, true); |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
return str_replace(array_keys($aRpl), $aRpl, $value); |
262
|
|
|
} |
263
|
|
|
/** |
264
|
|
|
* Displays exception information. |
265
|
|
|
* Exceptions are displayed with rich context information, including |
266
|
|
|
* the call stack and the context source code. |
267
|
|
|
* This method is only invoked when application is in <b>Debug</b> mode. |
268
|
|
|
* @param Exception $exception exception instance |
269
|
|
|
*/ |
270
|
|
|
protected function displayException($exception) |
271
|
|
|
{ |
272
|
|
|
if (php_sapi_name() === 'cli') { |
273
|
|
|
echo $exception->getMessage() . "\n"; |
274
|
|
|
echo $this->getExactTraceAsString($exception); |
275
|
|
|
return; |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
if ($exception instanceof TTemplateException) { |
279
|
|
|
$fileName = $exception->getTemplateFile(); |
280
|
|
|
$lines = empty($fileName) ? explode("\n", $exception->getTemplateSource()) : @file($fileName); |
281
|
|
|
$source = $this->getSourceCode($lines, $exception->getLineNumber()); |
282
|
|
|
if ($fileName === '') { |
283
|
|
|
$fileName = '---embedded template---'; |
284
|
|
|
} |
285
|
|
|
$errorLine = $exception->getLineNumber(); |
286
|
|
|
} else { |
287
|
|
|
if (($trace = $this->getExactTrace($exception)) !== null) { |
288
|
|
|
$fileName = $trace['file']; |
289
|
|
|
$errorLine = $trace['line']; |
290
|
|
|
} else { |
291
|
|
|
$fileName = $exception->getFile(); |
292
|
|
|
$errorLine = $exception->getLine(); |
293
|
|
|
} |
294
|
|
|
$source = $this->getSourceCode(@file($fileName), $errorLine); |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
if ($this->getApplication()->getMode() === TApplicationMode::Debug) { |
298
|
|
|
$version = $_SERVER['SERVER_SOFTWARE'] . ' <a href="https://github.com/pradosoft/prado">PRADO</a>/' . Prado::getVersion(); |
299
|
|
|
} else { |
300
|
|
|
$version = ''; |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
$tokens = [ |
304
|
|
|
'%%ErrorType%%' => get_class($exception), |
305
|
|
|
'%%ErrorMessage%%' => $this->addLink(htmlspecialchars($exception->getMessage())), |
306
|
|
|
'%%SourceFile%%' => htmlspecialchars($this->hidePrivatePathParts($fileName)) . ' (' . $errorLine . ')', |
307
|
|
|
'%%SourceCode%%' => $source, |
308
|
|
|
'%%StackTrace%%' => htmlspecialchars($this->getExactTraceAsString($exception)), |
309
|
|
|
'%%Version%%' => $version, |
310
|
|
|
'%%Time%%' => @strftime('%Y-%m-%d %H:%M', time()) |
311
|
|
|
]; |
312
|
|
|
|
313
|
|
|
$content = $this->getExceptionTemplate($exception); |
314
|
|
|
|
315
|
|
|
echo strtr($content, $tokens); |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
/** |
319
|
|
|
* Retrieves the template used for displaying internal exceptions. |
320
|
|
|
* Internal exceptions will be displayed with source code causing the exception. |
321
|
|
|
* This occurs when the application is in debug mode. |
322
|
|
|
* @param Exception $exception the exception to be displayed |
323
|
|
|
* @return string the template content |
324
|
|
|
*/ |
325
|
|
|
protected function getExceptionTemplate($exception) |
326
|
|
|
{ |
327
|
|
|
$lang = Prado::getPreferredLanguage(); |
328
|
|
|
$exceptionFile = Prado::getFrameworkPath() . '/Exceptions/templates/' . self::EXCEPTION_FILE_NAME . '-' . $lang . '.html'; |
329
|
|
|
if (!is_file($exceptionFile)) { |
330
|
|
|
$exceptionFile = Prado::getFrameworkPath() . '/Exceptions/templates/' . self::EXCEPTION_FILE_NAME . '.html'; |
331
|
|
|
} |
332
|
|
|
if (($content = @file_get_contents($exceptionFile)) === false) { |
333
|
|
|
die("Unable to open exception template file '$exceptionFile'."); |
334
|
|
|
} |
335
|
|
|
return $content; |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
/** |
339
|
|
|
* Retrieves the template used for displaying external exceptions. |
340
|
|
|
* External exceptions are those displayed to end-users. They do not contain |
341
|
|
|
* error source code. Therefore, you might want to override this method |
342
|
|
|
* to provide your own error template for displaying certain external exceptions. |
343
|
|
|
* The following tokens in the template will be replaced with corresponding content: |
344
|
|
|
* %%StatusCode%% : the status code of the exception |
345
|
|
|
* %%ErrorMessage%% : the error message (HTML encoded). |
346
|
|
|
* %%ServerAdmin%% : the server admin information (retrieved from Web server configuration) |
347
|
|
|
* %%Version%% : the version information of the Web server. |
348
|
|
|
* %%Time%% : the time the exception occurs at |
349
|
|
|
* |
350
|
|
|
* @param int $statusCode status code (such as 404, 500, etc.) |
351
|
|
|
* @param Exception $exception the exception to be displayed |
352
|
|
|
* @return string the template content |
353
|
|
|
*/ |
354
|
|
|
protected function getErrorTemplate($statusCode, $exception) |
355
|
|
|
{ |
356
|
|
|
$base = $this->getErrorTemplatePath() . DIRECTORY_SEPARATOR . self::ERROR_FILE_NAME; |
357
|
|
|
$lang = Prado::getPreferredLanguage(); |
358
|
|
|
if (is_file("$base$statusCode-$lang.html")) { |
359
|
|
|
$errorFile = "$base$statusCode-$lang.html"; |
360
|
|
|
} elseif (is_file("$base$statusCode.html")) { |
361
|
|
|
$errorFile = "$base$statusCode.html"; |
362
|
|
|
} elseif (is_file("$base-$lang.html")) { |
363
|
|
|
$errorFile = "$base-$lang.html"; |
364
|
|
|
} else { |
365
|
|
|
$errorFile = "$base.html"; |
366
|
|
|
} |
367
|
|
|
if (($content = @file_get_contents($errorFile)) === false) { |
368
|
|
|
die("Unable to open error template file '$errorFile'."); |
369
|
|
|
} |
370
|
|
|
return $content; |
371
|
|
|
} |
372
|
|
|
|
373
|
|
|
private function getExactTrace($exception) |
374
|
|
|
{ |
375
|
|
|
$result = null; |
376
|
|
View Code Duplication |
if ($exception instanceof TPhpFatalErrorException && |
|
|
|
|
377
|
|
|
function_exists('xdebug_get_function_stack')) { |
378
|
|
|
$trace = array_slice(array_reverse(xdebug_get_function_stack()), self::FATAL_ERROR_TRACE_DROP_LINES, -1); |
379
|
|
|
} else { |
380
|
|
|
$trace = $exception->getTrace(); |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
// if PHP exception, we want to show the 2nd stack level context |
384
|
|
|
// because the 1st stack level is of little use (it's in error handler) |
385
|
|
|
if ($exception instanceof TPhpErrorException) { |
386
|
|
|
if (isset($trace[0]['file'])) { |
387
|
|
|
$result = $trace[0]; |
388
|
|
|
} elseif (isset($trace[1])) { |
389
|
|
|
$result = $trace[1]; |
390
|
|
|
} |
391
|
|
|
} elseif ($exception instanceof TInvalidOperationException) { |
392
|
|
|
// in case of getter or setter error, find out the exact file and row |
393
|
|
|
if (($result = $this->getPropertyAccessTrace($trace, '__get')) === null) { |
394
|
|
|
$result = $this->getPropertyAccessTrace($trace, '__set'); |
395
|
|
|
} |
396
|
|
|
} |
397
|
|
View Code Duplication |
if ($result !== null && strpos($result['file'], ': eval()\'d code') !== false) { |
|
|
|
|
398
|
|
|
return null; |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
return $result; |
402
|
|
|
} |
403
|
|
|
|
404
|
|
|
private function getExactTraceAsString($exception) |
405
|
|
|
{ |
406
|
|
|
if ($exception instanceof TPhpFatalErrorException && |
407
|
|
|
function_exists('xdebug_get_function_stack')) { |
408
|
|
|
$trace = array_slice(array_reverse(xdebug_get_function_stack()), self::FATAL_ERROR_TRACE_DROP_LINES, -1); |
409
|
|
|
$txt = ''; |
410
|
|
|
$row = 0; |
411
|
|
|
|
412
|
|
|
// try to mimic Exception::getTraceAsString() |
413
|
|
|
foreach ($trace as $line) { |
414
|
|
|
if (array_key_exists('function', $line)) { |
415
|
|
|
$func = $line['function'] . '(' . implode(',', $line['params']) . ')'; |
416
|
|
|
} else { |
417
|
|
|
$func = 'unknown'; |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
$txt .= '#' . $row . ' ' . $this->hidePrivatePathParts($line['file']) . '(' . $line['line'] . '): ' . $func . "\n"; |
421
|
|
|
$row++; |
422
|
|
|
} |
423
|
|
|
|
424
|
|
|
return $txt; |
425
|
|
|
} |
426
|
|
|
|
427
|
|
|
return $this->hidePrivatePathParts($exception->getTraceAsString()); |
428
|
|
|
} |
429
|
|
|
|
430
|
|
|
private function getPropertyAccessTrace($trace, $pattern) |
431
|
|
|
{ |
432
|
|
|
$result = null; |
433
|
|
|
foreach ($trace as $t) { |
434
|
|
|
if (isset($t['function']) && $t['function'] === $pattern) { |
435
|
|
|
$result = $t; |
436
|
|
|
} else { |
437
|
|
|
break; |
438
|
|
|
} |
439
|
|
|
} |
440
|
|
|
return $result; |
441
|
|
|
} |
442
|
|
|
|
443
|
|
|
private function getSourceCode($lines, $errorLine) |
444
|
|
|
{ |
445
|
|
|
$beginLine = $errorLine - self::SOURCE_LINES >= 0 ? $errorLine - self::SOURCE_LINES : 0; |
446
|
|
|
$endLine = $errorLine + self::SOURCE_LINES <= count($lines) ? $errorLine + self::SOURCE_LINES : count($lines); |
447
|
|
|
|
448
|
|
|
$source = ''; |
449
|
|
|
for ($i = $beginLine; $i < $endLine; ++$i) { |
450
|
|
|
if ($i === $errorLine - 1) { |
451
|
|
|
$line = htmlspecialchars(sprintf("%04d: %s", $i + 1, str_replace("\t", ' ', $lines[$i]))); |
452
|
|
|
$source .= "<div class=\"error\">" . $line . "</div>"; |
453
|
|
|
} else { |
454
|
|
|
$source .= htmlspecialchars(sprintf("%04d: %s", $i + 1, str_replace("\t", ' ', $lines[$i]))); |
455
|
|
|
} |
456
|
|
|
} |
457
|
|
|
return $source; |
458
|
|
|
} |
459
|
|
|
|
460
|
|
|
private function addLink($message) |
461
|
|
|
{ |
462
|
|
|
if (null !== ($class = $this->getErrorClassNameSpace($message))) { |
463
|
|
|
return str_replace($class['name'], '<a href="' . $class['url'] . '" target="_blank">' . $class['name'] . '</a>', $message); |
464
|
|
|
} |
465
|
|
|
return $message; |
466
|
|
|
} |
467
|
|
|
|
468
|
|
|
private function getErrorClassNameSpace($message) |
469
|
|
|
{ |
470
|
|
|
$matches = []; |
471
|
|
|
preg_match('/\b(T[A-Z]\w+)\b/', $message, $matches); |
472
|
|
|
if (is_array($matches) && count($matches) > 0) { |
473
|
|
|
$class = $matches[0]; |
474
|
|
|
try { |
475
|
|
|
$function = new \ReflectionClass($class); |
476
|
|
|
} catch (\Exception $e) { |
477
|
|
|
return null; |
478
|
|
|
} |
479
|
|
|
$classname = $function->getNamespaceName(); |
480
|
|
|
return [ |
481
|
|
|
'url' => 'http://pradosoft.github.io/docs/manual/class-' . str_replace('\\', '.', (string) $classname) . '.' . $class . '.html', |
482
|
|
|
'name' => $class, |
483
|
|
|
]; |
484
|
|
|
} |
485
|
|
|
return null; |
486
|
|
|
} |
487
|
|
|
} |
488
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.