Development

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

You must first sign up to be able to contribute.

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

Revision 33539, 12.1 kB (checked in by fabien, 2 years ago)

[1.4] fixed exception format when using the PHP 5.4 built-in server (closes #10067, based on a patch from jgskin)

  • 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       if (sfConfig::get('sf_compressed')) {
104           ob_start('ob_gzhandler');
105       }
106
107       header('HTTP/1.0 500 Internal Server Error');
108     }
109
110     try
111     {
112       $this->outputStackTrace($exception);
113     }
114     catch (Exception $e)
115     {
116     }
117
118     if (!sfConfig::get('sf_test'))
119     {
120       exit(1);
121     }
122   }
123
124   /**
125    * Gets the stack trace for this exception.
126    */
127   static protected function outputStackTrace(Exception $exception)
128   {
129     $format = 'html';
130     $code   = '500';
131     $text   = 'Internal Server Error';
132
133     $response = null;
134     if (class_exists('sfContext', false) && sfContext::hasInstance() && is_object($request = sfContext::getInstance()->getRequest()) && is_object($response = sfContext::getInstance()->getResponse()))
135     {
136       $dispatcher = sfContext::getInstance()->getEventDispatcher();
137
138       if (sfConfig::get('sf_logging_enabled'))
139       {
140         $dispatcher->notify(new sfEvent($exception, 'application.log', array($exception->getMessage(), 'priority' => sfLogger::ERR)));
141       }
142
143       $event = $dispatcher->notifyUntil(new sfEvent($exception, 'application.throw_exception'));
144       if ($event->isProcessed())
145       {
146         return;
147       }
148
149       if ($response->getStatusCode() < 300)
150       {
151         // status code has already been sent, but is included here for the purpose of testing
152         $response->setStatusCode(500);
153       }
154
155       $response->setContentType('text/html');
156
157       if (!sfConfig::get('sf_test'))
158       {
159         foreach ($response->getHttpHeaders() as $name => $value)
160         {
161           header($name.': '.$value);
162         }
163       }
164
165       $code = $response->getStatusCode();
166       $text = $response->getStatusText();
167
168       $format = $request->getRequestFormat();
169       if (!$format)
170       {
171         $format = 'html';
172       }
173
174       if ($mimeType = $request->getMimeType($format))
175       {
176         $response->setContentType($mimeType);
177       }
178     }
179     else
180     {
181       // a backward compatible default
182       if (!sfConfig::get('sf_test'))
183       {
184         header('Content-Type: text/html; charset='.sfConfig::get('sf_charset', 'utf-8'));
185       }
186     }
187
188     // send an error 500 if not in debug mode
189     if (!sfConfig::get('sf_debug'))
190     {
191       if ($template = self::getTemplatePathForError($format, false))
192       {
193         include $template;
194         return;
195       }
196     }
197
198     // when using CLI, we force the format to be TXT. Compare exactly to
199     // the string 'cli' because the php 5.4 server is identified by 'cli-server'
200     if ('cli' == PHP_SAPI)
201     {
202       $format = 'txt';
203     }
204
205     $message = null === $exception->getMessage() ? 'n/a' : $exception->getMessage();
206     $name    = get_class($exception);
207     $traces  = self::getTraces($exception, $format);
208
209     // dump main objects values
210     $sf_settings = '';
211     $settingsTable = $requestTable = $responseTable = $globalsTable = $userTable = '';
212     if (class_exists('sfContext', false) && sfContext::hasInstance())
213     {
214       $context = sfContext::getInstance();
215       $settingsTable = self::formatArrayAsHtml(sfDebug::settingsAsArray());
216       $requestTable  = self::formatArrayAsHtml(sfDebug::requestAsArray($context->getRequest()));
217       $responseTable = self::formatArrayAsHtml(sfDebug::responseAsArray($context->getResponse()));
218       $userTable     = self::formatArrayAsHtml(sfDebug::userAsArray($context->getUser()));
219       $globalsTable  = self::formatArrayAsHtml(sfDebug::globalsAsArray());
220     }
221
222     if (isset($response) && $response)
223     {
224       $response->sendHttpHeaders();
225     }
226
227     if ($template = self::getTemplatePathForError($format, true))
228     {
229       if (isset($dispatcher))
230       {
231         ob_start();
232         include $template;
233         $content = ob_get_clean();
234
235         $event = $dispatcher->filter(new sfEvent($response, 'response.filter_content'), $content);
236
237         echo $event->getReturnValue();
238       }
239       else
240       {
241         include $template;
242       }
243
244       return;
245     }
246   }
247
248   /**
249    * Returns the path for the template error message.
250    *
251    * @param  string  $format The request format
252    * @param  Boolean $debug  Whether to return a template for the debug mode or not
253    *
254    * @return string|Boolean  false if the template cannot be found for the given format,
255    *                         the absolute path to the template otherwise
256    */
257   static public function getTemplatePathForError($format, $debug)
258   {
259     $templatePaths = array(
260       sfConfig::get('sf_app_config_dir').'/error',
261       sfConfig::get('sf_config_dir').'/error',
262       dirname(__FILE__).'/data',
263     );
264
265     $template = sprintf('%s.%s.php', $debug ? 'exception' : 'error', $format);
266     foreach ($templatePaths as $path)
267     {
268       if (null !== $path && is_readable($file = $path.'/'.$template))
269       {
270         return $file;
271       }
272     }
273
274     return false;
275   }
276
277   /**
278    * Returns an array of exception traces.
279    *
280    * @param Exception $exception  An Exception implementation instance
281    * @param string    $format     The trace format (txt or html)
282    *
283    * @return array An array of traces
284    */
285   static protected function getTraces($exception, $format = 'txt')
286   {
287     $traceData = $exception->getTrace();
288     array_unshift($traceData, array(
289       'function' => '',
290       'file'     => $exception->getFile() != null ? $exception->getFile() : null,
291       'line'     => $exception->getLine() != null ? $exception->getLine() : null,
292       'args'     => array(),
293     ));
294
295     $traces = array();
296     if ($format == 'html')
297     {
298       $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>';
299     }
300     else
301     {
302       $lineFormat = 'at %s%s%s(%s) in %s line %s';
303     }
304
305     for ($i = 0, $count = count($traceData); $i < $count; $i++)
306     {
307       $line = isset($traceData[$i]['line']) ? $traceData[$i]['line'] : null;
308       $file = isset($traceData[$i]['file']) ? $traceData[$i]['file'] : null;
309       $args = isset($traceData[$i]['args']) ? $traceData[$i]['args'] : array();
310       $traces[] = sprintf($lineFormat,
311         (isset($traceData[$i]['class']) ? $traceData[$i]['class'] : ''),
312         (isset($traceData[$i]['type']) ? $traceData[$i]['type'] : ''),
313         $traceData[$i]['function'],
314         self::formatArgs($args, false, $format),
315         self::formatFile($file, $line, $format, null === $file ? 'n/a' : sfDebug::shortenFilePath($file)),
316         null === $line ? 'n/a' : $line,
317         'trace_'.$i,
318         'trace_'.$i,
319         $i == 0 ? 'block' : 'none',
320         self::fileExcerpt($file, $line)
321       );
322     }
323
324     return $traces;
325   }
326
327   /**
328    * Returns an HTML version of an array as YAML.
329    *
330    * @param array $values The values array
331    *
332    * @return string An HTML string
333    */
334   static protected function formatArrayAsHtml($values)
335   {
336     return '<pre>'.self::escape(@sfYaml::dump($values)).'</pre>';
337   }
338
339   /**
340    * Returns an excerpt of a code file around the given line number.
341    *
342    * @param string $file  A file path
343    * @param int    $line  The selected line number
344    *
345    * @return string An HTML string
346    */
347   static protected function fileExcerpt($file, $line)
348   {
349     if (is_readable($file))
350     {
351       $content = preg_split('#<br />#', preg_replace('/^<code>(.*)<\/code>$/s', '$1', highlight_file($file, true)));
352
353       $lines = array();
354       for ($i = max($line - 3, 1), $max = min($line + 3, count($content)); $i <= $max; $i++)
355       {
356         $lines[] = '<li'.($i == $line ? ' class="selected"' : '').'>'.$content[$i - 1].'</li>';
357       }
358
359       return '<ol start="'.max($line - 3, 1).'">'.implode("\n", $lines).'</ol>';
360     }
361   }
362
363   /**
364    * Formats an array as a string.
365    *
366    * @param array   $args     The argument array
367    * @param boolean $single
368    * @param string  $format   The format string (html or txt)
369    *
370    * @return string
371    */
372   static protected function formatArgs($args, $single = false, $format = 'html')
373   {
374     $result = array();
375
376     $single and $args = array($args);
377
378     foreach ($args as $key => $value)
379     {
380       if (is_object($value))
381       {
382         $formattedValue = ($format == 'html' ? '<em>object</em>' : 'object').sprintf("('%s')", get_class($value));
383       }
384       else if (is_array($value))
385       {
386         $formattedValue = ($format == 'html' ? '<em>array</em>' : 'array').sprintf("(%s)", self::formatArgs($value));
387       }
388       else if (is_string($value))
389       {
390         $formattedValue = ($format == 'html' ? sprintf("'%s'", self::escape($value)) : "'$value'");
391       }
392       else if (null === $value)
393       {
394         $formattedValue = ($format == 'html' ? '<em>null</em>' : 'null');
395       }
396       else
397       {
398         $formattedValue = $value;
399       }
400       
401       $result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", self::escape($key), $formattedValue);
402     }
403
404     return implode(', ', $result);
405   }
406
407   /**
408    * Formats a file path.
409    *
410    * @param  string  $file   An absolute file path
411    * @param  integer $line   The line number
412    * @param  string  $format The output format (txt or html)
413    * @param  string  $text   Use this text for the link rather than the file path
414    *
415    * @return string
416    */
417   static protected function formatFile($file, $line, $format = 'html', $text = null)
418   {
419     if (null === $text)
420     {
421       $text = $file;
422     }
423
424     if ('html' == $format && $file && $line && $linkFormat = sfConfig::get('sf_file_link_format', ini_get('xdebug.file_link_format')))
425     {
426       $link = strtr($linkFormat, array('%f' => $file, '%l' => $line));
427       $text = sprintf('<a href="%s" title="Click to open this file" class="file_link">%s</a>', $link, $text);
428     }
429
430     return $text;
431   }
432
433   /**
434    * Escapes a string value with html entities
435    *
436    * @param  string  $value
437    *
438    * @return string
439    */
440   static protected function escape($value)
441   {
442     if (!is_string($value))
443     {
444       return $value;
445     }
446     
447     return htmlspecialchars($value, ENT_QUOTES, sfConfig::get('sf_charset', 'UTF-8'));
448   }
449 }
450
Note: See TracBrowser for help on using the browser.