<?php
/**
* @category Mad
* @package Mad_Controller
* @copyright (c) 2007-2009 Maintainable Software, LLC
* @license http://opensource.org/licenses/bsd-license.php BSD
*/
/**
* Render an error page when an exception occurs.
*
* @category Mad
* @package Mad_Controller
* @copyright (c) 2007-2009 Maintainable Software, LLC
* @license http://opensource.org/licenses/bsd-license.php BSD
*/
class Mad_Controller_Rescue_Renderer
{
/**
* @var Mad_View_Base
*/
protected $_view = null;
/**
* @var Mad_Controller_Rescue_SourceExtractor
*/
protected $_extractor = null;
/**
* Constructor.
*
* @var Mad_View_Base $view
*/
public function __construct($view = null, $extractor = null)
{
if ($view === null) {
$view = new Mad_View_Base();
$view->addPath( dirname(__FILE__), $relative = false );
}
$this->_view = $view;
if ($extractor === null) {
$extractor = new Mad_Controller_Rescue_SourceExtractor();
}
$this->_extractor = $extractor;
}
/**
* Render an HTML error page from an exception.
*
* @param Exception $exception
* @param Mad_Controller_Request_Http $request
* @param Mad_Controller_Response_Http $response
*/
public function render($exception, $request, $response)
{
// If there is anything leftover in the output buffer, such
// as interrupted template rendering, destroy it.
while (ob_get_level()) { ob_get_clean(); }
// title
if ($exception instanceof Mad_Support_Exception) {
$title = $exception->getTitle();
} else {
$title = get_class($exception);
}
// message
if (! strlen($message = $exception->getMessage())) {
$message = "<no message>";
}
// assignments
$this->_view->title = $title;
$this->_view->message = $message;
$this->_view->exception = $exception;
$this->_view->trace = $this->formatTrace($exception);
$this->_view->request = $request;
$this->_view->response = $response;
$this->_view->extraction = $this->extractSource($exception);
// render the error page contents
$this->_view->contents = $this->_view->render('diagnostics');
// render the error layout
$html = $this->_view->render('layout');
return $html;
}
/**
* Extract the source code around where $exception occurred.
*
* @param Exception $exception PHP exception
* @return array line number => source code
*/
public function extractSource($exception)
{
return $this->_extractor->extractSourceFromException($exception);
}
/**
* Build a more readable traceback from an $exception.
*
* @see formatFrame()
*
* @param Exception $exception PHP exception
* @return array<stdClass> Array of frames
*/
public function formatTrace($exception)
{
// PHP's Exception class declares getTrace() as final, but
// some exceptions need to doctor the trace.
if ($exception instanceof Mad_Support_Exception) {
$trace = $exception->getDoctoredTrace();
} else {
$trace = $exception->getTrace();
}
// build an array of objects for the trace frames
$out = array();
foreach ($trace as $frame) {
$out[] = $this->formatFrame($frame);
}
// PHP's trace doesn't include the line where the error
// occurred, so we prepend it to get a full trace.
if (isset($out[0])) {
$frame = clone $out[0];
$frame->file = $exception->getFile();
$frame->fileStripped = $this->stripPath( $exception->getFile() );
$frame->line = $exception->getLine();
$frame->url = $this->linkTo($frame->file, $frame->line);
array_unshift($out, $frame);
}
return $out;
}
/**
* Given a single frame from a PHP exception trace, return an
* object for that frame with properties $file, $line, and $method.
*
* @param array $frame PHP trace fram
* @return object Equivalent object
*/
public function formatFrame($frame)
{
$file = isset($frame['file']) ? $frame['file'] : 'Unknown file';
$line = isset($frame['line']) ? $frame['line'] : '?';
$method = isset($frame['class']) ? $frame['class'] : '';
$method .= isset($frame['type']) ? $frame['type'] : '';
$method .= isset($frame['function']) ? $frame['function'] : '';
return (object)array('file' => $file,
'fileStripped' => $this->stripPath($file),
'url' => $this->linkTo($file, $line),
'line' => $line,
'method' => $method);
}
/**
* Given a $path, strip the MAD_ROOT and Mad stream wrapper protocol.
*
* @param string $path Path to strip
* @return string Stripped path
*/
public function stripPath($path)
{
$path = str_replace(MAD_ROOT . DIRECTORY_SEPARATOR, '', $path);
$path = $this->stripMadProtocols($path);
return $path;
}
/**
* Strip the Mad protocols like madview:// from the path
* to make it more readable.
*
* @param string $path
* @return string
*/
public function stripMadProtocols($path) {
return preg_replace('!^mad\w+://!', '', $path);
}
/**
* Link to $line number in source file at $path.
*
* @param string $path Path to source code
* @param integer $line Line number in source
* @return string URL
*/
public function linkTo($path, $line)
{
$url = 'file://' . $this->stripMadProtocols($path);
if (PHP_OS == 'Darwin') {
$url = "txmt://open/?url=$url&line=$line";
}
return $url;
}
}