1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
// +---------------------------------------------------------------------------+ |
4
|
|
|
// | This file is part of the Agavi package. | |
5
|
|
|
// | Copyright (c) 2005-2011 the Agavi Project. | |
6
|
|
|
// | Based on the Mojavi3 MVC Framework, Copyright (c) 2003-2005 Sean Kerr. | |
7
|
|
|
// | | |
8
|
|
|
// | For the full copyright and license information, please view the LICENSE | |
9
|
|
|
// | file that was distributed with this source code. You can also view the | |
10
|
|
|
// | LICENSE file online at http://www.agavi.org/LICENSE.txt | |
11
|
|
|
// | vi: set noexpandtab: | |
12
|
|
|
// | Local Variables: | |
13
|
|
|
// | indent-tabs-mode: t | |
14
|
|
|
// | End: | |
15
|
|
|
// +---------------------------------------------------------------------------+ |
16
|
|
|
|
17
|
|
|
|
18
|
|
|
namespace Agavi\Exception; |
19
|
|
|
|
20
|
|
|
use Agavi\Config\Config; |
21
|
|
|
use Agavi\Core\Context; |
22
|
|
|
use Agavi\Dispatcher\ExecutionContainer; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* AgaviException is the base class for all Agavi related exceptions and |
26
|
|
|
* provides an additional method for printing up a detailed view of an |
27
|
|
|
* exception. |
28
|
|
|
* |
29
|
|
|
* @package agavi |
30
|
|
|
* @subpackage exception |
31
|
|
|
* |
32
|
|
|
* @author David Zülke <[email protected]> |
33
|
|
|
* @author Sean Kerr <[email protected]> |
34
|
|
|
* @author Bob Zoller <[email protected]> |
35
|
|
|
* @copyright Authors |
36
|
|
|
* @copyright The Agavi Project |
37
|
|
|
* |
38
|
|
|
* @since 0.9.0 |
39
|
|
|
* |
40
|
|
|
* @version $Id$ |
41
|
|
|
*/ |
42
|
|
|
class AgaviException extends \Exception |
43
|
|
|
{ |
44
|
|
|
/** |
45
|
|
|
* Print the stack trace for this exception. |
46
|
|
|
* |
47
|
|
|
* @param \Exception $e The original exception. |
48
|
|
|
* @param Context $context The context instance. |
49
|
|
|
* @param ExecutionContainer $container The execution container instance |
50
|
|
|
* |
51
|
|
|
* @author David Zülke <[email protected]> |
52
|
|
|
* @since 0.9.0 |
53
|
|
|
* |
54
|
|
|
* @deprecated Superseded by AgaviException::render() |
55
|
|
|
*/ |
56
|
|
|
public static function printStackTrace(\Exception $e, Context $context = null, ExecutionContainer $container = null) |
57
|
|
|
{ |
58
|
|
|
return self::render($e, $context, $container); |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* Returns a fixed stack trace in case the original one from the exception |
63
|
|
|
* does not contain the origin as the first entry in the trace array, which |
64
|
|
|
* appears to happen from time to time or with certain PHP/XDebug versions. |
65
|
|
|
* |
66
|
|
|
* @param \Exception $e The exception to pull the trace from. |
67
|
|
|
* @param \Exception $next Optionally, the next exception to display (pulled |
68
|
|
|
* from Exception::getPrevious() and displayed in |
69
|
|
|
* reverse order), which will then result in identical |
70
|
|
|
* parts of the stack trace not being returned. |
71
|
|
|
* |
72
|
|
|
* @return array The trace containing the exception origin as first item. |
73
|
|
|
* |
74
|
|
|
* @author David Zülke <[email protected]> |
75
|
|
|
* @since 1.0.3 |
76
|
|
|
*/ |
77
|
|
|
public static function getFixedTrace(\Exception $e, \Exception $next = null) |
78
|
|
|
{ |
79
|
|
|
// fix stack trace in case it doesn't contain the exception origin as the first entry |
80
|
|
|
$fixedTrace = $e->getTrace(); |
81
|
|
|
|
82
|
|
|
if (!isset($fixedTrace[0]['file']) || !($fixedTrace[0]['file'] == $e->getFile() && $fixedTrace[0]['line'] == $e->getLine())) { |
83
|
|
|
$fixedTrace = array_merge(array(array('file' => $e->getFile(), 'line' => $e->getLine())), $fixedTrace); |
84
|
|
|
} |
85
|
|
|
|
86
|
|
|
if ($next) { |
87
|
|
|
$nextTrace = self::getFixedTrace($next); |
88
|
|
|
foreach ($fixedTrace as $i => $fixedTraceItem) { |
89
|
|
|
if ($fixedTraceItem == $nextTrace[1]) { |
90
|
|
|
$fixedTrace = array_slice($fixedTrace, 0, $i); |
91
|
|
|
break; |
92
|
|
|
} |
93
|
|
|
} |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
return $fixedTrace; |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* Build a list of parameters passed to a method. Example: |
101
|
|
|
* array([object AgaviFilter], 'baz' => array(1, 2), 'log' => [resource stream]) |
102
|
|
|
* |
103
|
|
|
* @param array $params An (associative) array of variables. |
104
|
|
|
* @param bool $html Whether or not to style and encode for HTML output. |
105
|
|
|
* @param int $level |
106
|
|
|
* |
107
|
|
|
* @return string A string, possibly formatted using HTML "em" tags. |
108
|
|
|
* |
109
|
|
|
* @author David Zülke <[email protected]> |
110
|
|
|
* @since 0.11.0 |
111
|
|
|
*/ |
112
|
|
|
public static function buildParamList($params, $html = true, $level = 1) |
113
|
|
|
{ |
114
|
|
|
$retval = array(); |
115
|
|
|
foreach ($params as $key => $param) { |
116
|
|
|
if (is_string($key)) { |
117
|
|
View Code Duplication |
if (preg_match('/^(.{5}).{2,}(.{5})$/us', $key, $matches)) { |
|
|
|
|
118
|
|
|
$key = $matches[1] . '…' . $matches[2]; |
119
|
|
|
} |
120
|
|
|
$key = var_export($key, true) . ' => '; |
121
|
|
|
if ($html) { |
122
|
|
|
$key = htmlspecialchars($key); |
123
|
|
|
} |
124
|
|
|
} else { |
125
|
|
|
$key = ''; |
126
|
|
|
} |
127
|
|
|
switch (gettype($param)) { |
128
|
|
|
case 'array': |
129
|
|
|
$retval[] = $key . 'array(' . ($level < 2 ? self::buildParamList($param, $html, ++$level) : (count($param) ? '…' : '')) . ')'; |
130
|
|
|
break; |
131
|
|
|
case 'object': |
132
|
|
|
if ($html) { |
133
|
|
|
$retval[] = $key . '[object <em>' . get_class($param) . '</em>]'; |
134
|
|
|
} else { |
135
|
|
|
$retval[] = $key . '[object ' . get_class($param) . ']'; |
136
|
|
|
} |
137
|
|
|
break; |
138
|
|
|
case 'resource': |
139
|
|
|
if ($html) { |
140
|
|
|
$retval[] = $key . '[resource <em>' . htmlspecialchars(get_resource_type($param)) . '</em>]'; |
141
|
|
|
} else { |
142
|
|
|
$retval[] = $key . '[resource ' . get_resource_type($param) . ']'; |
143
|
|
|
} |
144
|
|
|
break; |
145
|
|
|
case 'string': |
146
|
|
|
$val = $param; |
147
|
|
View Code Duplication |
if (preg_match('/^(.{20}).{3,}(.{20})$/us', $val, $matches)) { |
|
|
|
|
148
|
|
|
$val = $matches[1] . ' … ' . $matches[2]; |
149
|
|
|
} |
150
|
|
|
$val = var_export($val, true); |
151
|
|
|
if ($html) { |
152
|
|
|
$retval[] = $key . htmlspecialchars($val); |
153
|
|
|
} else { |
154
|
|
|
$retval[] = $key . $val; |
155
|
|
|
} |
156
|
|
|
break; |
157
|
|
|
default: |
158
|
|
|
if ($html) { |
159
|
|
|
$retval[] = $key . htmlspecialchars(var_export($param, true)); |
160
|
|
|
} else { |
161
|
|
|
$retval[] = $key . var_export($param, true); |
162
|
|
|
} |
163
|
|
|
} |
164
|
|
|
} |
165
|
|
|
return implode(', ', $retval); |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* Perform PHP syntax highlighting on the given file. |
170
|
|
|
* |
171
|
|
|
* @param string $filepath The path of the file to highlight. |
172
|
|
|
* |
173
|
|
|
* @return array An 0-indexed array of HTML-highlighted code lines. |
174
|
|
|
* |
175
|
|
|
* @author David Zülke <[email protected]> |
176
|
|
|
* @since 1.0.3 |
177
|
|
|
*/ |
178
|
|
|
public static function highlightFile($filepath) |
179
|
|
|
{ |
180
|
|
|
return self::highlightString(file_get_contents($filepath)); |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
/** |
184
|
|
|
* Perform PHP syntax highlighting on the given code string. |
185
|
|
|
* |
186
|
|
|
* @param string $code The PHP code to highlight. |
187
|
|
|
* |
188
|
|
|
* @return array An 0-indexed array of HTML-highlighted code lines. |
189
|
|
|
* |
190
|
|
|
* @author David Zülke <[email protected]> |
191
|
|
|
* @since 1.0.3 |
192
|
|
|
*/ |
193
|
|
|
public static function highlightString($code) |
194
|
|
|
{ |
195
|
|
|
$code = highlight_string(str_replace(' ', ' ', $code), true); |
196
|
|
|
// time to cleanup this highlighted string |
197
|
|
|
// first, drop all newlines (we'll explode by "<br />") |
198
|
|
|
$code = str_replace(array("\r\n", "\n", "\r"), array('', '', ''), $code); |
199
|
|
|
// second, remove start and end wrappers and replace with numeric entity |
200
|
|
|
$code = str_replace(array(sprintf('<code><span style="color: %s">', ini_get('highlight.html')), '</span></code>', ' '), array('', '', ' '), $code); |
201
|
|
|
// make an array of lines |
202
|
|
|
$code = explode('<br />', $code); |
203
|
|
|
// iterate and cleanup each line |
204
|
|
|
$remember = null; |
205
|
|
|
foreach ($code as &$line) { |
206
|
|
|
// we need at least an nbsp for empty lines |
207
|
|
|
if ($line == '') { |
208
|
|
|
$line = ' '; |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
// drop leading </span> |
212
|
|
|
if (strpos($line, '</span>') === 0) { |
213
|
|
|
$line = substr($line, 7); |
214
|
|
|
// no style to carry over from previous line(s) |
215
|
|
|
$remember = null; |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
// prepend style from previous line if we have one |
219
|
|
|
if ($remember) { |
|
|
|
|
220
|
|
|
$line = sprintf('<span style="color: %s">%s', $remember, $line); |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
$openingSpanPos = strpos($line, '<span'); |
224
|
|
|
$openingSpanRPos = strrpos($line, '<span'); |
225
|
|
|
$closingSpanPos = strpos($line, '</span>'); |
226
|
|
|
$closingSpanRPos = strrpos($line, '</span>'); |
|
|
|
|
227
|
|
|
|
228
|
|
|
$balanced = (($openingSpanCount = preg_match_all('#<span#', $line, $matches)) == ($closingSpanCount = preg_match_all('#</span>#', $line, $matches))); |
229
|
|
|
if ($balanced && $openingSpanPos !== false && $openingSpanPos < $closingSpanPos) { |
230
|
|
|
// already balanced, no further cleanup necessary |
231
|
|
|
$remember = null; |
232
|
|
|
continue; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
if (substr($line, -7) == '</span>') { |
236
|
|
|
// discard previous style if style terminates in this line |
237
|
|
|
$remember = null; |
238
|
|
|
} else { |
239
|
|
|
// otherwise, remember last style from this line if there is one |
240
|
|
|
if ($openingSpanRPos !== false) { |
241
|
|
|
// must remember previous color; 20 is the length of '<span style="color: ' |
242
|
|
|
// we're using strpos since someone could set the colors to "#333" or "red" through php.ini, so we don't know the length |
243
|
|
|
$remember = substr($line, $openingSpanRPos + 20, strpos($line, '"', $openingSpanRPos + 20) - ($openingSpanRPos + 20)); |
244
|
|
|
} |
245
|
|
|
// append closing tag |
246
|
|
|
$line .= '</span>'; |
247
|
|
|
$closingSpanCount++; |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
// in case things still are not right... |
251
|
|
|
// can happen for instance when the first line of the file is HTML and we drop the first span, since that is a wrapper for everything |
252
|
|
|
if ($openingSpanCount < $closingSpanCount) { |
253
|
|
|
$line = sprintf('%1$s%2$s', str_repeat('<span color="%3s">', $closingSpanCount - $openingSpanCount), $line, ini_get('highlight.html')); |
254
|
|
|
} |
255
|
|
|
if ($closingSpanCount < $openingSpanCount) { |
256
|
|
|
$line = sprintf('%s%s', $line, str_repeat('</span>', $openingSpanCount - $closingSpanCount), $line); |
257
|
|
|
} |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
return $code; |
261
|
|
|
} |
262
|
|
|
|
263
|
|
|
/** |
264
|
|
|
* Pretty-print this exception using a template. |
265
|
|
|
* |
266
|
|
|
* @param \Exception $exception The original exception. |
|
|
|
|
267
|
|
|
* @param Context $context The context instance. |
268
|
|
|
* @param ExecutionContainer $container The response instance. |
269
|
|
|
* |
270
|
|
|
* @author David Zülke <[email protected]> |
271
|
|
|
* @since 1.0.0 |
272
|
|
|
*/ |
273
|
|
|
public static function render(\Exception $e, Context $context = null, ExecutionContainer $container = null) |
274
|
|
|
{ |
275
|
|
|
// exit code is 70, EX_SOFTWARE, according to /usr/include/sysexits.h: http://cvs.opensolaris.org/source/xref/on/usr/src/head/sysexits.h |
276
|
|
|
// nice touch: an exception template can change this value :) |
277
|
|
|
$exitCode = 70; |
278
|
|
|
|
279
|
|
|
$exceptions = array(); |
280
|
|
|
// reverse order of exceptions for linking |
281
|
|
|
$ce = $e; |
282
|
|
|
while ($ce) { |
283
|
|
|
array_unshift($exceptions, $ce); |
284
|
|
|
$ce = $ce->getPrevious(); |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
// discard any previous output waiting in the buffer |
288
|
|
|
while (@ob_end_clean()) { |
|
|
|
|
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
if ($container !== null && $container->getOutputType() !== null && $container->getOutputType()->getExceptionTemplate() !== null) { |
292
|
|
|
// an exception template was defined for the container's output type |
293
|
|
|
include($container->getOutputType()->getExceptionTemplate()); |
294
|
|
|
exit($exitCode); |
|
|
|
|
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
if ($context !== null && $context->getDispatcher() !== null) { |
298
|
|
|
try { |
299
|
|
|
// check if an exception template was defined for the default output type |
300
|
|
|
if ($context->getDispatcher()->getOutputType()->getExceptionTemplate() !== null) { |
301
|
|
|
include($context->getDispatcher()->getOutputType()->getExceptionTemplate()); |
302
|
|
|
exit($exitCode); |
|
|
|
|
303
|
|
|
} |
304
|
|
|
} catch (\Exception $e2) { |
305
|
|
|
unset($e2); |
306
|
|
|
} |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
if ($context !== null && Config::get('exception.templates.' . $context->getName()) !== null) { |
310
|
|
|
// a template was set for this context |
311
|
|
|
include(Config::get('exception.templates.' . $context->getName())); |
312
|
|
|
exit($exitCode); |
|
|
|
|
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
// include default exception template |
316
|
|
|
include(Config::get('exception.default_template')); |
317
|
|
|
|
318
|
|
|
// bail out |
319
|
|
|
exit($exitCode); |
|
|
|
|
320
|
|
|
} |
321
|
|
|
} |
322
|
|
|
|
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.