Development

/branches/1.3/lib/exception/sfException.class.php

You must first sign up to be able to contribute.

root/branches/1.3/lib/exception/sfException.class.php

Revision 23901, 11.9 kB (checked in by bschussek, 5 years ago)

[1.2, 1.3, 1.4] The last exception is reset on every new page call in functional tests (fixes #6342, patch by Stefan.Koopmanschap)

  • Property svn:mime-type set to text/x-php
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1 <?php
2
3 /*
4  * This file is part of the symfony package.
5  * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
6  * (c) 2004-2006 Sean Kerr <sean@code-box.org>
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 /**
13  * sfException is the base class for all symfony related exceptions and
14  * provides an additional method for printing up a detailed view of an
15  * exception.
16  *
17  * @package    symfony
18  * @subpackage exception
19  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
20  * @author     Sean Kerr <sean@code-box.org>
21  * @version    SVN: $Id$
22  */
23 class sfException extends Exception
24 {
25   protected
26     $wrappedException = null;
27
28   static protected
29     $lastException = null;
30
31   /**
32    * Wraps an Exception.
33    *
34    * @param Exception $e An Exception instance
35    *
36    * @return sfException An sfException instance that wraps the given Exception object
37    */
38   static public function createFromException(Exception $e)
39   {
40     $exception = new sfException(sprintf('Wrapped %s: %s', get_class($e), $e->getMessage()));
41     $exception->setWrappedException($e);
42     self::$lastException = $e;
43
44     return $exception;
45   }
46
47   /**
48    * Sets the wrapped exception.
49    *
50    * @param Exception $e An Exception instance
51    */
52   public function setWrappedException(Exception $e)
53   {
54     $this->wrappedException = $e;
55
56     self::$lastException = $e;
57   }
58
59   /**
60    * Gets the last wrapped exception.
61    *
62    * @return Exception An Exception instance
63    */
64   static public function getLastException()
65   {
66     return self::$lastException;
67   }
68
69   /**
70    * Clears the $lastException property (added for #6342)
71    */
72   static public function clearLastException()
73   {
74       self::$lastException = null;
75   }
76  
77   /**
78    * Prints the stack trace for this exception.
79    */
80   public function printStackTrace()
81   {
82     if (null === $this->wrappedException)
83     {
84       $this->setWrappedException($this);
85     }
86
87     $exception = $this->wrappedException;
88
89     if (!sfConfig::get('sf_test'))
90     {
91       // log all exceptions in php log
92       error_log($exception->getMessage());
93
94       // clean current output buffer
95       while (ob_get_level())
96       {
97         if (!ob_end_clean())
98         {
99           break;
100         }
101       }
102
103       ob_start(sfConfig::get('sf_compressed') ? 'ob_gzhandler' : '');
104
105       header('HTTP/1.0 500 Internal Server Error');
106     }
107
108     try
109     {
110       $this->outputStackTrace($exception);
111     }
112     catch (Exception $e)
113     {
114     }
115
116     if (!sfConfig::get('sf_test'))
117     {
118       exit(1);
119     }
120   }
121
122   /**
123    * Gets the stack trace for this exception.
124    */
125   static protected function outputStackTrace(Exception $exception)
126   {
127     $format = 'html';
128     $code   = '500';
129     $text   = 'Internal Server Error';
130
131     $response = null;
132     if (class_exists('sfContext', false) && sfContext::hasInstance() && is_object($request = sfContext::getInstance()->getRequest()) && is_object($response = sfContext::getInstance()->getResponse()))
133     {
134       $dispatcher = sfContext::getInstance()->getEventDispatcher();
135
136       if (sfConfig::get('sf_logging_enabled'))
137       {
138         $dispatcher->notify(new sfEvent($exception, 'application.log', array($exception->getMessage(), 'priority' => sfLogger::ERR)));
139       }
140
141       $event = $dispatcher->notifyUntil(new sfEvent($exception, 'application.throw_exception'));
142       if ($event->isProcessed())
143       {
144         return;
145       }
146
147       if ($response->getStatusCode() < 300)
148       {
149         // status code has already been sent, but is included here for the purpose of testing
150         $response->setStatusCode(500);
151       }
152
153       $response->setContentType('text/html');
154
155       if (!sfConfig::get('sf_test'))
156       {
157         foreach ($response->getHttpHeaders() as $name => $value)
158         {
159           header($name.': '.$value);
160         }
161       }
162
163       $code = $response->getStatusCode();
164       $text = $response->getStatusText();
165
166       $format = $request->getRequestFormat();
167       if (!$format)
168       {
169         $format = 'html';
170       }
171
172       if ($mimeType = $request->getMimeType($format))
173       {
174         $response->setContentType($mimeType);
175       }
176     }
177     else
178     {
179       // a backward compatible default
180       if (!sfConfig::get('sf_test'))
181       {
182         header('Content-Type: text/html; charset='.sfConfig::get('sf_charset', 'utf-8'));
183       }
184     }
185
186     // send an error 500 if not in debug mode
187     if (!sfConfig::get('sf_debug'))
188     {
189       if ($template = self::getTemplatePathForError($format, false))
190       {
191         include $template;
192         return;
193       }
194     }
195
196     // when using CLI, we force the format to be TXT
197     if (0 == strncasecmp(PHP_SAPI, 'cli', 3))
198     {
199       $format = 'txt';
200     }
201
202     $message = null === $exception->getMessage() ? 'n/a' : $exception->getMessage();
203     $name    = get_class($exception);
204     $traces  = self::getTraces($exception, $format);
205
206     // dump main objects values
207     $sf_settings = '';
208     $settingsTable = $requestTable = $responseTable = $globalsTable = $userTable = '';
209     if (class_exists('sfContext', false) && sfContext::hasInstance())
210     {
211       $context = sfContext::getInstance();
212       $settingsTable = self::formatArrayAsHtml(sfDebug::settingsAsArray());
213       $requestTable  = self::formatArrayAsHtml(sfDebug::requestAsArray($context->getRequest()));
214       $responseTable = self::formatArrayAsHtml(sfDebug::responseAsArray($context->getResponse()));
215       $userTable     = self::formatArrayAsHtml(sfDebug::userAsArray($context->getUser()));
216       $globalsTable  = self::formatArrayAsHtml(sfDebug::globalsAsArray());
217     }
218
219     if (isset($response) && $response)
220     {
221       $response->sendHttpHeaders();
222     }
223
224     if ($template = self::getTemplatePathForError($format, true))
225     {
226       if (isset($dispatcher))
227       {
228         ob_start();
229         include $template;
230         $content = ob_get_clean();
231
232         $event = $dispatcher->filter(new sfEvent($response, 'response.filter_content'), $content);
233
234         echo $event->getReturnValue();
235       }
236       else
237       {
238         include $template;
239       }
240
241       return;
242     }
243   }
244
245   /**
246    * Returns the path for the template error message.
247    *
248    * @param  string  $format The request format
249    * @param  Boolean $debug  Whether to return a template for the debug mode or not
250    *
251    * @return string|Boolean  false if the template cannot be found for the given format,
252    *                         the absolute path to the template otherwise
253    */
254   static public function getTemplatePathForError($format, $debug)
255   {
256     $templatePaths = array(
257       sfConfig::get('sf_app_config_dir').'/error',
258       sfConfig::get('sf_config_dir').'/error',
259       dirname(__FILE__).'/data',
260     );
261
262     $template = sprintf('%s.%s.php', $debug ? 'exception' : 'error', $format);
263     foreach ($templatePaths as $path)
264     {
265       if (null !== $path && is_readable($file = $path.'/'.$template))
266       {
267         return $file;
268       }
269     }
270
271     return false;
272   }
273
274   /**
275    * Returns an array of exception traces.
276    *
277    * @param Exception $exception  An Exception implementation instance
278    * @param string    $format     The trace format (txt or html)
279    *
280    * @return array An array of traces
281    */
282   static protected function getTraces($exception, $format = 'txt')
283   {
284     $traceData = $exception->getTrace();
285     array_unshift($traceData, array(
286       'function' => '',
287       'file'     => $exception->getFile() != null ? $exception->getFile() : null,
288       'line'     => $exception->getLine() != null ? $exception->getLine() : null,
289       'args'     => array(),
290     ));
291
292     $traces = array();
293     if ($format == 'html')
294     {
295       $lineFormat = 'at <strong>%s%s%s</strong>(%s)<br />in <em>%s</em> line %s <a href="#" onclick="toggle(\'%s\'); return false;">...</a><br /><ul class="code" id="%s" style="display: %s">%s</ul>';
296     }
297     else
298     {
299       $lineFormat = 'at %s%s%s(%s) in %s line %s';
300     }
301
302     for ($i = 0, $count = count($traceData); $i < $count; $i++)
303     {
304       $line = isset($traceData[$i]['line']) ? $traceData[$i]['line'] : null;
305       $file = isset($traceData[$i]['file']) ? $traceData[$i]['file'] : null;
306       $args = isset($traceData[$i]['args']) ? $traceData[$i]['args'] : array();
307       $traces[] = sprintf($lineFormat,
308         (isset($traceData[$i]['class']) ? $traceData[$i]['class'] : ''),
309         (isset($traceData[$i]['type']) ? $traceData[$i]['type'] : ''),
310         $traceData[$i]['function'],
311         self::formatArgs($args, false, $format),
312         self::formatFile($file, $line, $format, null === $file ? 'n/a' : sfDebug::shortenFilePath($file)),
313         null === $line ? 'n/a' : $line,
314         'trace_'.$i,
315         'trace_'.$i,
316         $i == 0 ? 'block' : 'none',
317         self::fileExcerpt($file, $line)
318       );
319     }
320
321     return $traces;
322   }
323
324   /**
325    * Returns an HTML version of an array as YAML.
326    *
327    * @param array $values The values array
328    *
329    * @return string An HTML string
330    */
331   static protected function formatArrayAsHtml($values)
332   {
333     return '<pre>'.self::escape(@sfYaml::dump($values)).'</pre>';
334   }
335
336   /**
337    * Returns an excerpt of a code file around the given line number.
338    *
339    * @param string $file  A file path
340    * @param int    $line  The selected line number
341    *
342    * @return string An HTML string
343    */
344   static protected function fileExcerpt($file, $line)
345   {
346     if (is_readable($file))
347     {
348       $content = preg_split('#<br />#', highlight_file($file, true));
349
350       $lines = array();
351       for ($i = max($line - 3, 1), $max = min($line + 3, count($content)); $i <= $max; $i++)
352       {
353         $lines[] = '<li'.($i == $line ? ' class="selected"' : '').'>'.$content[$i - 1].'</li>';
354       }
355
356       return '<ol start="'.max($line - 3, 1).'">'.implode("\n", $lines).'</ol>';
357     }
358   }
359
360   /**
361    * Formats an array as a string.
362    *
363    * @param array   $args     The argument array
364    * @param boolean $single
365    * @param string  $format   The format string (html or txt)
366    *
367    * @return string
368    */
369   static protected function formatArgs($args, $single = false, $format = 'html')
370   {
371     $result = array();
372
373     $single and $args = array($args);
374
375     foreach ($args as $key => $value)
376     {
377       if (is_object($value))
378       {
379         $formattedValue = ($format == 'html' ? '<em>object</em>' : 'object').sprintf("('%s')", get_class($value));
380       }
381       else if (is_array($value))
382       {
383         $formattedValue = ($format == 'html' ? '<em>array</em>' : 'array').sprintf("(%s)", self::formatArgs($value));
384       }
385       else if (is_string($value))
386       {
387         $formattedValue = ($format == 'html' ? sprintf("'%s'", self::escape($value)) : "'$value'");
388       }
389       else if (null === $value)
390       {
391         $formattedValue = ($format == 'html' ? '<em>null</em>' : 'null');
392       }
393       else
394       {
395         $formattedValue = $value;
396       }
397       
398       $result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", self::escape($key), $formattedValue);
399     }
400
401     return implode(', ', $result);
402   }
403
404   /**
405    * Formats a file path.
406    *
407    * @param  string  $file   An absolute file path
408    * @param  integer $line   The line number
409    * @param  string  $format The output format (txt or html)
410    * @param  string  $text   Use this text for the link rather than the file path
411    *
412    * @return string
413    */
414   static protected function formatFile($file, $line, $format = 'html', $text = null)
415   {
416     if (null === $text)
417     {
418       $text = $file;
419     }
420
421     if ('html' == $format && $file && $line && $linkFormat = sfConfig::get('sf_file_link_format', ini_get('xdebug.file_link_format')))
422     {
423       $link = strtr($linkFormat, array('%f' => $file, '%l' => $line));
424       $text = sprintf('<a href="%s" title="Click to open this file" class="file_link">%s</a>', $link, $text);
425     }
426
427     return $text;
428   }
429
430   /**
431    * Escapes a string value with html entities
432    *
433    * @param  string  $value
434    *
435    * @return string
436    */
437   static protected function escape($value)
438   {
439     if (!is_string($value))
440     {
441       return $value;
442     }
443     
444     return htmlspecialchars($value, ENT_QUOTES, sfConfig::get('sf_charset', 'UTF-8'));
445   }
446 }
447
Note: See TracBrowser for help on using the browser.