the whole shebang

This commit is contained in:
2014-11-25 16:42:40 +01:00
parent 7f74c0613e
commit ab1334c0cf
3686 changed files with 496409 additions and 1 deletions

View File

@@ -0,0 +1,14 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Exception;
use ErrorException as BaseErrorException;
/**
* Wraps ErrorException; mostly used for typing (at least now)
* to easily cleanup the stack trace of redundant info.
*/
class ErrorException extends BaseErrorException {}

View File

@@ -0,0 +1,228 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Exception;
use InvalidArgumentException;
use Serializable;
class Frame implements Serializable
{
/**
* @var array
*/
protected $frame;
/**
* @var string
*/
protected $fileContentsCache;
/**
* @var array[]
*/
protected $comments = array();
/**
* @param array[]
*/
public function __construct(array $frame)
{
$this->frame = $frame;
}
/**
* @param bool $shortened
* @return string|null
*/
public function getFile($shortened = false)
{
$file = !empty($this->frame['file']) ? $this->frame['file'] : null;
if ($shortened && is_string($file)) {
// Replace the part of the path that all frames have in common, and add 'soft hyphens' for smoother line-breaks.
$dirname = dirname(dirname(dirname(dirname(dirname(dirname(__DIR__))))));
$file = str_replace($dirname, "", $file);
$file = str_replace("/", "/&shy;", $file);
}
return $file;
}
/**
* @return int|null
*/
public function getLine()
{
return isset($this->frame['line']) ? $this->frame['line'] : null;
}
/**
* @return string|null
*/
public function getClass()
{
return isset($this->frame['class']) ? $this->frame['class'] : null;
}
/**
* @return string|null
*/
public function getFunction()
{
return isset($this->frame['function']) ? $this->frame['function'] : null;
}
/**
* @return array
*/
public function getArgs()
{
return isset($this->frame['args']) ? (array) $this->frame['args'] : array();
}
/**
* Returns the full contents of the file for this frame,
* if it's known.
* @return string|null
*/
public function getFileContents()
{
if($this->fileContentsCache === null && $filePath = $this->getFile()) {
$this->fileContentsCache = file_get_contents($filePath);
}
return $this->fileContentsCache;
}
/**
* Adds a comment to this frame, that can be received and
* used by other handlers. For example, the PrettyPage handler
* can attach these comments under the code for each frame.
*
* An interesting use for this would be, for example, code analysis
* & annotations.
*
* @param string $comment
* @param string $context Optional string identifying the origin of the comment
*/
public function addComment($comment, $context = 'global')
{
$this->comments[] = array(
'comment' => $comment,
'context' => $context
);
}
/**
* Returns all comments for this frame. Optionally allows
* a filter to only retrieve comments from a specific
* context.
*
* @param string $filter
* @return array[]
*/
public function getComments($filter = null)
{
$comments = $this->comments;
if($filter !== null) {
$comments = array_filter($comments, function($c) use($filter) {
return $c['context'] == $filter;
});
}
return $comments;
}
/**
* Returns the array containing the raw frame data from which
* this Frame object was built
*
* @return array
*/
public function getRawFrame()
{
return $this->frame;
}
/**
* Returns the contents of the file for this frame as an
* array of lines, and optionally as a clamped range of lines.
*
* NOTE: lines are 0-indexed
*
* @example
* Get all lines for this file
* $frame->getFileLines(); // => array( 0 => '<?php', 1 => '...', ...)
* @example
* Get one line for this file, starting at line 10 (zero-indexed, remember!)
* $frame->getFileLines(9, 1); // array( 10 => '...', 11 => '...')
*
* @param int $start
* @param int $length
* @return string[]|null
*/
public function getFileLines($start = 0, $length = null)
{
if(null !== ($contents = $this->getFileContents())) {
$lines = explode("\n", $contents);
// Get a subset of lines from $start to $end
if($length !== null)
{
$start = (int) $start;
$length = (int) $length;
if ($start < 0) {
$start = 0;
}
if($length <= 0) {
throw new InvalidArgumentException(
"\$length($length) cannot be lower or equal to 0"
);
}
$lines = array_slice($lines, $start, $length, true);
}
return $lines;
}
}
/**
* Implements the Serializable interface, with special
* steps to also save the existing comments.
*
* @see Serializable::serialize
* @return string
*/
public function serialize()
{
$frame = $this->frame;
if(!empty($this->comments)) {
$frame['_comments'] = $this->comments;
}
return serialize($frame);
}
/**
* Unserializes the frame data, while also preserving
* any existing comment data.
*
* @see Serializable::unserialize
* @param string $serializedFrame
*/
public function unserialize($serializedFrame)
{
$frame = unserialize($serializedFrame);
if(!empty($frame['_comments'])) {
$this->comments = $frame['_comments'];
unset($frame['_comments']);
}
$this->frame = $frame;
}
}

View File

@@ -0,0 +1,122 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Exception;
use Whoops\Exception\Frame;
use UnexpectedValueException;
use IteratorAggregate;
use ArrayIterator;
use Serializable;
use Countable;
/**
* Exposes a fluent interface for dealing with an ordered list
* of stack-trace frames.
*/
class FrameCollection implements IteratorAggregate, Serializable, Countable
{
/**
* @var array[]
*/
private $frames;
/**
* @param array $frames
*/
public function __construct(array $frames)
{
$this->frames = array_map(function($frame) {
return new Frame($frame);
}, $frames);
}
/**
* Filters frames using a callable, returns the same FrameCollection
*
* @param callable $callable
* @return Whoops\Exception\FrameCollection
*/
public function filter($callable)
{
$this->frames = array_filter($this->frames, $callable);
return $this;
}
/**
* Map the collection of frames
*
* @param callable $callable
* @return Whoops\Exception\FrameCollection
*/
public function map($callable)
{
// Contain the map within a higher-order callable
// that enforces type-correctness for the $callable
$this->frames = array_map(function($frame) use($callable) {
$frame = call_user_func($callable, $frame);
if(!$frame instanceof Frame) {
throw new UnexpectedValueException(
"Callable to " . __METHOD__ . " must return a Frame object"
);
}
return $frame;
}, $this->frames);
return $this;
}
/**
* Returns an array with all frames, does not affect
* the internal array.
*
* @todo If this gets any more complex than this,
* have getIterator use this method.
* @see Whoops\Exception\FrameCollection::getIterator
* @return array
*/
public function getArray()
{
return $this->frames;
}
/**
* @see IteratorAggregate::getIterator
* @return ArrayIterator
*/
public function getIterator()
{
return new ArrayIterator($this->frames);
}
/**
* @see Countable::count
* @return int
*/
public function count()
{
return count($this->frames);
}
/**
* @see Serializable::serialize
* @return string
*/
public function serialize()
{
return serialize($this->frames);
}
/**
* @see Serializable::unserialize
* @param string $serializedFrames
*/
public function unserialize($serializedFrames)
{
$this->frames = unserialize($serializedFrames);
}
}

View File

@@ -0,0 +1,115 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Exception;
use Whoops\Exception\FrameCollection;
use Whoops\Exception\ErrorException;
use Exception;
class Inspector
{
/**
* @var Exception
*/
private $exception;
/**
* @var Whoops\Exception\FrameCollection
*/
private $frames;
/**
* @param Exception $exception The exception to inspect
*/
public function __construct(Exception $exception)
{
$this->exception = $exception;
}
/**
* @return Exception
*/
public function getException()
{
return $this->exception;
}
/**
* @return string
*/
public function getExceptionName()
{
return get_class($this->exception);
}
/**
* @return string
*/
public function getExceptionMessage()
{
return $this->exception->getMessage();
}
/**
* Returns an iterator for the inspected exception's
* frames.
* @return Whoops\Exception\FrameCollection
*/
public function getFrames()
{
if($this->frames === null) {
$frames = $this->exception->getTrace();
// If we're handling an ErrorException thrown by Whoops,
// get rid of the last frame, which matches the handleError method,
// and do not add the current exception to trace. We ensure that
// the next frame does have a filename / linenumber, though.
if($this->exception instanceof ErrorException && empty($frames[1]['line'])) {
$frames = array($this->getFrameFromError($this->exception));
} else {
$firstFrame = $this->getFrameFromException($this->exception);
array_unshift($frames, $firstFrame);
}
$this->frames = new FrameCollection($frames);
}
return $this->frames;
}
/**
* Given an exception, generates an array in the format
* generated by Exception::getTrace()
* @param Exception $exception
* @return array
*/
protected function getFrameFromException(Exception $exception)
{
return array(
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'class' => get_class($exception),
'args' => array(
$exception->getMessage()
)
);
}
/**
* Given an error, generates an array in the format
* generated by ErrorException
* @param ErrorException $exception
* @return array
*/
protected function getFrameFromError(ErrorException $exception)
{
return array(
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'class' => null,
'args' => array()
);
}
}

View File

@@ -0,0 +1,48 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Handler;
use Whoops\Handler\Handler;
use InvalidArgumentException;
/**
* Wrapper for Closures passed as handlers. Can be used
* directly, or will be instantiated automagically by Whoops\Run
* if passed to Run::pushHandler
*/
class CallbackHandler extends Handler
{
/**
* @var callable
*/
protected $callable;
/**
* @param callable $callable
*/
public function __construct($callable)
{
if(!is_callable($callable)) {
throw new InvalidArgumentException(
'Argument to ' . __METHOD__ . ' must be valid callable'
);
}
$this->callable = $callable;
}
/**
* @return int|null
*/
public function handle()
{
$exception = $this->getException();
$inspector = $this->getInspector();
$run = $this->getRun();
return call_user_func($this->callable, $exception, $inspector, $run);
}
}

View File

@@ -0,0 +1,89 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Handler;
use Whoops\Handler\HandlerInterface;
use Whoops\Exception\Inspector;
use Whoops\Run;
use Exception;
/**
* Abstract implementation of a Handler.
*/
abstract class Handler implements HandlerInterface
{
/**
* Return constants that can be returned from Handler::handle
* to message the handler walker.
*/
const DONE = 0x10; // returning this is optional, only exists for
// semantic purposes
const LAST_HANDLER = 0x20;
const QUIT = 0x30;
/**
* @var Whoops\Run
*/
private $run;
/**
* @var Whoops\Exception\Inspector $inspector
*/
private $inspector;
/**
* @var Exception $exception
*/
private $exception;
/**
* @param Whoops\Run $run
*/
public function setRun(Run $run)
{
$this->run = $run;
}
/**
* @return Whoops\Run
*/
protected function getRun()
{
return $this->run;
}
/**
* @param Whoops\Exception\Inspector $inspector
*/
public function setInspector(Inspector $inspector)
{
$this->inspector = $inspector;
}
/**
* @return Whoops\Run
*/
protected function getInspector()
{
return $this->inspector;
}
/**
* @param Exception $exception
*/
public function setException(Exception $exception)
{
$this->exception = $exception;
}
/**
* @return Exception
*/
protected function getException()
{
return $this->exception;
}
}

View File

@@ -0,0 +1,33 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Handler;
use Whoops\Exception\Inspector;
use Whoops\Run;
use Exception;
interface HandlerInterface
{
/**
* @return int|null A handler may return nothing, or a Handler::HANDLE_* constant
*/
public function handle();
/**
* @param Whoops\Run $run
*/
public function setRun(Run $run);
/**
* @param Exception $exception
*/
public function setException(Exception $exception);
/**
* @param Whoops\Exception\Inspector $run
*/
public function setInspector(Inspector $inspector);
}

View File

@@ -0,0 +1,106 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Handler;
use Whoops\Handler\Handler;
/**
* Catches an exception and converts it to a JSON
* response. Additionally can also return exception
* frames for consumption by an API.
*/
class JsonResponseHandler extends Handler
{
/**
* @var bool
*/
private $returnFrames = false;
/**
* @var bool
*/
private $onlyForAjaxRequests = false;
/**
* @param bool|null $returnFrames
* @return null|bool
*/
public function addTraceToOutput($returnFrames = null)
{
if(func_num_args() == 0) {
return $this->returnFrames;
}
$this->returnFrames = (bool) $returnFrames;
}
/**
* @param bool|null $onlyForAjaxRequests
* @return null|bool
*/
public function onlyForAjaxRequests($onlyForAjaxRequests = null)
{
if(func_num_args() == 0) {
return $this->onlyForAjaxRequests;
}
$this->onlyForAjaxRequests = (bool) $onlyForAjaxRequests;
}
/**
* Check, if possible, that this execution was triggered by an AJAX request.
* @param bool
*/
private function isAjaxRequest()
{
return (
!empty($_SERVER['HTTP_X_REQUESTED_WITH'])
&& strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest')
;
}
/**
* @return int
*/
public function handle()
{
if($this->onlyForAjaxRequests() && !$this->isAjaxRequest()) {
return Handler::DONE;
}
$exception = $this->getException();
$response = array(
'error' => array(
'type' => get_class($exception),
'message' => $exception->getMessage(),
'file' => $exception->getFile(),
'line' => $exception->getLine()
)
);
if($this->addTraceToOutput()) {
$inspector = $this->getInspector();
$frames = $inspector->getFrames();
$frameData = array();
foreach($frames as $frame) {
$frameData[] = array(
'file' => $frame->getFile(),
'line' => $frame->getLine(),
'function' => $frame->getFunction(),
'class' => $frame->getClass(),
'args' => $frame->getArgs()
);
}
$response['error']['trace'] = $frameData;
}
echo json_encode($response);
return Handler::QUIT;
}
}

View File

@@ -0,0 +1,328 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Handler;
use Whoops\Handler\Handler;
use InvalidArgumentException;
class PrettyPageHandler extends Handler
{
/**
* @var string
*/
private $resourcesPath;
/**
* @var array[]
*/
private $extraTables = array();
/**
* @var string
*/
private $pageTitle = 'Whoops! There was an error.';
/**
* A string identifier for a known IDE/text editor, or a closure
* that resolves a string that can be used to open a given file
* in an editor. If the string contains the special substrings
* %file or %line, they will be replaced with the correct data.
*
* @example
* "txmt://open?url=%file&line=%line"
* @var mixed $editor
*/
protected $editor;
/**
* A list of known editor strings
* @var array
*/
protected $editors = array(
'sublime' => 'subl://open?url=file://%file&line=%line',
'textmate' => 'txmt://open?url=file://%file&line=%line',
'emacs' => 'emacs://open?url=file://%file&line=%line',
'macvim' => 'mvim://open/?url=file://%file&line=%line'
);
/**
* Constructor.
*/
public function __construct()
{
if (extension_loaded('xdebug')) {
// Register editor using xdebug's file_link_format option.
$this->editors['xdebug'] = function($file, $line) {
return str_replace(array('%f', '%l'), array($file, $line), ini_get('xdebug.file_link_format'));
};
}
}
/**
* @return int|null
*/
public function handle()
{
// Check conditions for outputting HTML:
// @todo: make this more robust
if(php_sapi_name() === 'cli' && !isset($_ENV['whoops-test'])) {
return Handler::DONE;
}
// Get the 'pretty-template.php' template file
// @todo: this can be made more dynamic &&|| cleaned-up
if(!($resources = $this->getResourcesPath())) {
$resources = __DIR__ . '/../Resources';
}
$templateFile = "$resources/pretty-template.php";
// @todo: Make this more reliable,
// possibly by adding methods to append CSS & JS to the page
$cssFile = "$resources/pretty-page.css";
// Prepare the $v global variable that will pass relevant
// information to the template
$inspector = $this->getInspector();
$frames = $inspector->getFrames();
$v = (object) array(
'title' => $this->getPageTitle(),
'name' => explode('\\', $inspector->getExceptionName()),
'message' => $inspector->getException()->getMessage(),
'frames' => $frames,
'hasFrames' => !!count($frames),
'handler' => $this,
'handlers' => $this->getRun()->getHandlers(),
'pageStyle' => file_get_contents($cssFile),
'tables' => array(
'Server/Request Data' => $_SERVER,
'GET Data' => $_GET,
'POST Data' => $_POST,
'Files' => $_FILES,
'Cookies' => $_COOKIE,
'Session' => isset($_SESSION) ? $_SESSION: array(),
'Environment Variables' => $_ENV
)
);
$extraTables = array_map(function($table) {
return $table instanceof \Closure ? $table() : $table;
}, $this->getDataTables());
// Add extra entries list of data tables:
$v->tables = array_merge($extraTables, $v->tables);
call_user_func(function() use($templateFile, $v) {
// $e -> cleanup output, optionally preserving URIs as anchors:
$e = function($_, $allowLinks = false) {
$escaped = htmlspecialchars($_, ENT_QUOTES, 'UTF-8');
// convert URIs to clickable anchor elements:
if($allowLinks) {
$escaped = preg_replace(
'@([A-z]+?://([-\w\.]+[-\w])+(:\d+)?(/([\w/_\.#-]*(\?\S+)?[^\.\s])?)?)@',
"<a href=\"$1\" target=\"_blank\">$1</a>", $escaped
);
}
return $escaped;
};
// $slug -> sluggify string (i.e: Hello world! -> hello-world)
$slug = function($_) {
$_ = str_replace(" ", "-", $_);
$_ = preg_replace('/[^\w\d\-\_]/i', '', $_);
return strtolower($_);
};
require $templateFile;
});
return Handler::QUIT;
}
/**
* Adds an entry to the list of tables displayed in the template.
* The expected data is a simple associative array. Any nested arrays
* will be flattened with print_r
* @param string $label
* @param array $data
*/
public function addDataTable($label, array $data)
{
$this->extraTables[$label] = $data;
}
/**
* Lazily adds an entry to the list of tables displayed in the table.
* The supplied callback argument will be called when the error is rendered,
* it should produce a simple associative array. Any nested arrays will
* be flattened with print_r.
* @param string $label
* @param callable $callback Callable returning an associative array
*/
public function addDataTableCallback($label, /* callable */ $callback)
{
if (!is_callable($callback)) {
throw new InvalidArgumentException('Expecting callback argument to be callable');
}
$this->extraTables[$label] = function() use ($callback) {
try {
$result = call_user_func($callback);
// Only return the result if it can be iterated over by foreach().
return is_array($result) || $result instanceof \Traversable ? $result : array();
} catch (\Exception $e) {
// Don't allow failiure to break the rendering of the original exception.
return array();
}
};
}
/**
* Returns all the extra data tables registered with this handler.
* Optionally accepts a 'label' parameter, to only return the data
* table under that label.
* @param string|null $label
* @return array[]
*/
public function getDataTables($label = null)
{
if($label !== null) {
return isset($this->extraTables[$label]) ?
$this->extraTables[$label] : array();
}
return $this->extraTables;
}
/**
* Adds an editor resolver, identified by a string
* name, and that may be a string path, or a callable
* resolver. If the callable returns a string, it will
* be set as the file reference's href attribute.
*
* @example
* $run->addEditor('macvim', "mvim://open?url=file://%file&line=%line")
* @example
* $run->addEditor('remove-it', function($file, $line) {
* unlink($file);
* return "http://stackoverflow.com";
* });
* @param string $identifier
* @param string $resolver
*/
public function addEditor($identifier, $resolver)
{
$this->editors[$identifier] = $resolver;
}
/**
* Set the editor to use to open referenced files, by a string
* identifier, or a callable that will be executed for every
* file reference, with a $file and $line argument, and should
* return a string.
*
* @example
* $run->setEditor(function($file, $line) { return "file:///{$file}"; });
* @example
* $run->setEditor('sublime');
*
* @param string|callable $editor
*/
public function setEditor($editor)
{
if(!is_callable($editor) && !isset($this->editors[$editor])) {
throw new InvalidArgumentException(
"Unknown editor identifier: $editor. Known editors:" .
implode(",", array_keys($this->editors))
);
}
$this->editor = $editor;
}
/**
* Given a string file path, and an integer file line,
* executes the editor resolver and returns, if available,
* a string that may be used as the href property for that
* file reference.
*
* @param string $filePath
* @param int $line
* @return string|false
*/
public function getEditorHref($filePath, $line)
{
if($this->editor === null) {
return false;
}
$editor = $this->editor;
if(is_string($editor)) {
$editor = $this->editors[$editor];
}
if(is_callable($editor)) {
$editor = call_user_func($editor, $filePath, $line);
}
// Check that the editor is a string, and replace the
// %line and %file placeholders:
if(!is_string($editor)) {
throw new InvalidArgumentException(
__METHOD__ . " should always resolve to a string; got something else instead"
);
}
$editor = str_replace("%line", rawurlencode($line), $editor);
$editor = str_replace("%file", rawurlencode($filePath), $editor);
return $editor;
}
/**
* @var string
*/
public function setPageTitle($title)
{
$this->pageTitle = (string) $title;
}
/**
* @return string
*/
public function getPageTitle()
{
return $this->pageTitle;
}
/**
* @return string
*/
public function getResourcesPath()
{
return $this->resourcesPath;
}
/**
* @param string $resourcesPath
*/
public function setResourcesPath($resourcesPath)
{
if(!is_dir($resourcesPath)) {
throw new InvalidArgumentException(
"$resourcesPath is not a valid directory"
);
}
$this->resourcesPath = $resourcesPath;
}
}

View File

@@ -0,0 +1,69 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Provider\Phalcon;
use Whoops\Run;
use Whoops\Handler\PrettyPageHandler;
use Phalcon\DI;
use Phalcon\DI\Exception;
class WhoopsServiceProvider
{
/**
* @param Phalcon\DI $di
*/
public function __construct(DI $di = null)
{
if (!$di) {
$di = DI::getDefault();
}
// There's only ever going to be one error page...right?
$di->setShared('whoops.error_page_handler', function() {
return new PrettyPageHandler;
});
// Retrieves info on the Phalcon environment and ships it off
// to the PrettyPageHandler's data tables:
// This works by adding a new handler to the stack that runs
// before the error page, retrieving the shared page handler
// instance, and working with it to add new data tables
$phalcon_info_handler = function() use($di) {
try {
$request = $di['request'];
} catch (Exception $e) {
// This error occurred too early in the application's life
// and the request instance is not yet available.
return;
}
// Request info:
$di['whoops.error_page_handler']->addDataTable('Phalcon Application (Request)', array(
'URI' => $request->getScheme().'://'.$request->getServer('HTTP_HOST').$request->getServer('REQUEST_URI'),
'Request URI' => $request->getServer('REQUEST_URI'),
'Path Info' => $request->getServer('PATH_INFO'),
'Query String'=> $request->getServer('QUERY_STRING') ?: '<none>',
'HTTP Method' => $request->getMethod(),
'Script Name' => $request->getServer('SCRIPT_NAME'),
//'Base Path' => $request->getBasePath(),
//'Base URL' => $request->getBaseUrl(),
'Scheme' => $request->getScheme(),
'Port' => $request->getServer('SERVER_PORT'),
'Host' => $request->getServerName(),
));
};
$di->setShared('whoops', function() use($di, $phalcon_info_handler) {
$run = new Run;
$run->pushHandler($di['whoops.error_page_handler']);
$run->pushHandler($phalcon_info_handler);
return $run;
});
$di['whoops']->register();
}
}

View File

@@ -0,0 +1,81 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Provider\Silex;
use Whoops\Run;
use Whoops\Handler\PrettyPageHandler;
use Silex\ServiceProviderInterface;
use Silex\Application;
use RuntimeException;
class WhoopsServiceProvider implements ServiceProviderInterface
{
/**
* @see Silex\ServiceProviderInterface::register
* @param Silex\Application $app
*/
public function register(Application $app)
{
// There's only ever going to be one error page...right?
$app['whoops.error_page_handler'] = $app->share(function() {
return new PrettyPageHandler;
});
// Retrieves info on the Silex environment and ships it off
// to the PrettyPageHandler's data tables:
// This works by adding a new handler to the stack that runs
// before the error page, retrieving the shared page handler
// instance, and working with it to add new data tables
$app['whoops.silex_info_handler'] = $app->protect(function() use($app) {
try {
$request = $app['request'];
} catch (RuntimeException $e) {
// This error occurred too early in the application's life
// and the request instance is not yet available.
return;
}
// General application info:
$app['whoops.error_page_handler']->addDataTable('Silex Application', array(
'Charset' => $app['charset'],
'Locale' => $app['locale'],
'Route Class' => $app['route_class'],
'Dispatcher Class' => $app['dispatcher_class'],
'Application Class'=> get_class($app)
));
// Request info:
$app['whoops.error_page_handler']->addDataTable('Silex Application (Request)', array(
'URI' => $request->getUri(),
'Request URI' => $request->getRequestUri(),
'Path Info' => $request->getPathInfo(),
'Query String'=> $request->getQueryString() ?: '<none>',
'HTTP Method' => $request->getMethod(),
'Script Name' => $request->getScriptName(),
'Base Path' => $request->getBasePath(),
'Base URL' => $request->getBaseUrl(),
'Scheme' => $request->getScheme(),
'Port' => $request->getPort(),
'Host' => $request->getHost(),
));
});
$app['whoops'] = $app->share(function() use($app) {
$run = new Run;
$run->pushHandler($app['whoops.error_page_handler']);
$run->pushHandler($app['whoops.silex_info_handler']);
return $run;
});
$app->error(array($app['whoops'], Run::EXCEPTION_HANDLER));
$app['whoops']->register();
}
/**
* @see Silex\ServiceProviderInterface::boot
*/
public function boot(Application $app) {}
}

View File

@@ -0,0 +1,56 @@
<?php
/**
* ZF2 Integration for Whoops
* @author Balázs Németh <zsilbi@zsilbi.hu>
*/
namespace Whoops\Provider\Zend;
use Whoops\Run;
use Zend\Mvc\View\Http\ExceptionStrategy as BaseExceptionStrategy;
use Zend\Mvc\MvcEvent;
use Zend\Mvc\Application;
class ExceptionStrategy extends BaseExceptionStrategy {
protected $run;
public function __construct(Run $run) {
$this->run = $run;
return $this;
}
public function prepareExceptionViewModel(MvcEvent $event) {
// Do nothing if no error in the event
$error = $event->getError();
if (empty($error)) {
return;
}
// Do nothing if the result is a response object
$result = $event->getResult();
if ($result instanceof Response) {
return;
}
switch ($error) {
case Application::ERROR_CONTROLLER_NOT_FOUND:
case Application::ERROR_CONTROLLER_INVALID:
case Application::ERROR_ROUTER_NO_MATCH:
// Specifically not handling these
return;
case Application::ERROR_EXCEPTION:
default:
$response = $event->getResponse();
if (!$response || $response->getStatusCode() === 200) {
header('HTTP/1.0 500 Internal Server Error', true, 500);
}
ob_clean();
$this->run->handleException($event->getParam('exception'));
break;
}
}
}

View File

@@ -0,0 +1,106 @@
<?php
/**
* ZF2 Integration for Whoops
* @author Balázs Németh <zsilbi@zsilbi.hu>
*
* The Whoops directory should be added as a module to ZF2 (/vendor/Whoops)
*
* Whoops must be added as the first module
* For example:
* 'modules' => array(
* 'Whoops',
* 'Application',
* ),
*
* This file should be moved next to Whoops/Run.php (/vendor/Whoops/Module.php)
*
*/
namespace Whoops;
use Whoops\Run;
use Whoops\Provider\Zend\ExceptionStrategy;
use Whoops\Provider\Zend\RouteNotFoundStrategy;
use Whoops\Handler\JsonResponseHandler;
use Whoops\Handler\PrettyPageHandler;
use Zend\EventManager\EventInterface;
use Zend\Console\Request as ConsoleRequest;
class Module
{
protected $run;
public function onBootstrap(EventInterface $event)
{
$prettyPageHandler = new PrettyPageHandler();
// Set editor
$config = $event->getApplication()->getServiceManager()->get('Config');
if (isset($config['view_manager']['editor'])) {
$prettyPageHandler->setEditor($config['view_manager']['editor']);
}
$this->run = new Run();
$this->run->register();
$this->run->pushHandler($prettyPageHandler);
$this->attachListeners($event);
}
public function getAutoloaderConfig()
{
return array(
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
),
),
);
}
private function attachListeners(EventInterface $event)
{
$request = $event->getRequest();
$application = $event->getApplication();
$services = $application->getServiceManager();
$events = $application->getEventManager();
$config = $services->get('Config');
//Display exceptions based on configuration and console mode
if ($request instanceof ConsoleRequest || empty($config['view_manager']['display_exceptions']))
return;
$jsonHandler = new JsonResponseHandler();
if (!empty($config['view_manager']['json_exceptions']['show_trace'])) {
//Add trace to the JSON output
$jsonHandler->addTraceToOutput(true);
}
if (!empty($config['view_manager']['json_exceptions']['ajax_only'])) {
//Only return JSON response for AJAX requests
$jsonHandler->onlyForAjaxRequests(true);
}
if (!empty($config['view_manager']['json_exceptions']['display'])) {
//Turn on JSON handler
$this->run->pushHandler($jsonHandler);
}
//Attach the Whoops ExceptionStrategy
$exceptionStrategy = new ExceptionStrategy($this->run);
$exceptionStrategy->attach($events);
//Attach the Whoops RouteNotFoundStrategy
$routeNotFoundStrategy = new RouteNotFoundStrategy($this->run);
$routeNotFoundStrategy->attach($events);
//Detach default ExceptionStrategy
$services->get('Zend\Mvc\View\Http\ExceptionStrategy')->detach($events);
//Detach default RouteNotFoundStrategy
$services->get('Zend\Mvc\View\Http\RouteNotFoundStrategy')->detach($events);
}
}

View File

@@ -0,0 +1,64 @@
<?php
/**
* ZF2 Integration for Whoops
* @author Balázs Németh <zsilbi@zsilbi.hu>
*/
namespace Whoops\Provider\Zend;
use Whoops\Run;
use Zend\Mvc\View\Http\RouteNotFoundStrategy as BaseRouteNotFoundStrategy;
use Zend\Mvc\MvcEvent;
use Zend\Stdlib\ResponseInterface as Response;
use Zend\View\Model\ViewModel;
class RouteNotFoundStrategy extends BaseRouteNotFoundStrategy {
protected $run;
public function __construct(Run $run) {
$this->run = $run;
}
public function prepareNotFoundViewModel(MvcEvent $e) {
$vars = $e->getResult();
if ($vars instanceof Response) {
// Already have a response as the result
return;
}
$response = $e->getResponse();
if ($response->getStatusCode() != 404) {
// Only handle 404 responses
return;
}
if (!$vars instanceof ViewModel) {
$model = new ViewModel();
if (is_string($vars)) {
$model->setVariable('message', $vars);
} else {
$model->setVariable('message', 'Page not found.');
}
} else {
$model = $vars;
if ($model->getVariable('message') === null) {
$model->setVariable('message', 'Page not found.');
}
}
// If displaying reasons, inject the reason
$this->injectNotFoundReason($model, $e);
// If displaying exceptions, inject
$this->injectException($model, $e);
// Inject controller if we're displaying either the reason or the exception
$this->injectController($model, $e);
ob_clean();
throw new \Exception($model->getVariable('message') . ' ' . $model->getVariable('reason'));
}
}

View File

@@ -0,0 +1,20 @@
<?php
/**
* ZF2 Integration for Whoops
* @author Balázs Németh <zsilbi@zsilbi.hu>
*
* Example controller configuration
*/
return array(
'view_manager' => array(
'editor' => 'sublime',
'display_not_found_reason' => true,
'display_exceptions' => true,
'json_exceptions' => array(
'display' => true,
'ajax_only' => true,
'show_trace' => true
)
),
);

View File

@@ -0,0 +1,319 @@
.cf:before, .cf:after {content: " ";display: table;} .cf:after {clear: both;} .cf {*zoom: 1;}
body {
font: 14px helvetica, arial, sans-serif;
color: #2B2B2B;
background-color: #D4D4D4;
padding:0;
margin: 0;
max-height: 100%;
}
a {
text-decoration: none;
}
.container{
height: 100%;
width: 100%;
position: fixed;
margin: 0;
padding: 0;
left: 0;
top: 0;
}
.branding {
position: absolute;
top: 10px;
right: 20px;
color: #777777;
font-size: 10px;
z-index: 100;
}
.branding a {
color: #CD3F3F;
}
header {
padding: 30px 20px;
color: white;
background: #272727;
box-sizing: border-box;
border-left: 5px solid #CD3F3F;
}
.exc-title {
margin: 0;
color: #616161;
text-shadow: 0 1px 2px rgba(0, 0, 0, .1);
}
.exc-title-primary { color: #CD3F3F; }
.exc-message {
font-size: 32px;
margin: 5px 0;
word-wrap: break-word;
}
.stack-container {
height: 100%;
position: relative;
}
.details-container {
height: 100%;
overflow: auto;
float: right;
width: 70%;
background: #DADADA;
}
.details {
padding: 10px;
padding-left: 5px;
border-left: 5px solid rgba(0, 0, 0, .1);
}
.frames-container {
height: 100%;
overflow: auto;
float: left;
width: 30%;
background: #FFF;
}
.frame {
padding: 14px;
background: #F3F3F3;
border-right: 1px solid rgba(0, 0, 0, .2);
cursor: pointer;
}
.frame.active {
background-color: #4288CE;
color: #F3F3F3;
box-shadow: inset -2px 0 0 rgba(255, 255, 255, .1);
text-shadow: 0 1px 0 rgba(0, 0, 0, .2);
}
.frame:not(.active):hover {
background: #BEE9EA;
}
.frame-class, .frame-function, .frame-index {
font-weight: bold;
}
.frame-index {
font-size: 11px;
color: #BDBDBD;
}
.frame-class {
color: #4288CE;
}
.active .frame-class {
color: #BEE9EA;
}
.frame-file {
font-family: consolas, monospace;
word-wrap:break-word;
}
.frame-file .editor-link {
color: #272727;
}
.frame-line {
font-weight: bold;
color: #4288CE;
}
.active .frame-line { color: #BEE9EA; }
.frame-line:before {
content: ":";
}
.frame-code {
padding: 10px;
padding-left: 5px;
background: #BDBDBD;
display: none;
border-left: 5px solid #4288CE;
}
.frame-code.active {
display: block;
}
.frame-code .frame-file {
background: #C6C6C6;
color: #525252;
text-shadow: 0 1px 0 #E7E7E7;
padding: 10px 10px 5px 10px;
border-top-right-radius: 6px;
border-top-left-radius: 6px;
border: 1px solid rgba(0, 0, 0, .1);
border-bottom: none;
box-shadow: inset 0 1px 0 #DADADA;
}
.code-block {
padding: 10px;
margin: 0;
box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
}
.linenums {
margin: 0;
margin-left: 10px;
}
.frame-comments {
box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
border: 1px solid rgba(0, 0, 0, .2);
border-top: none;
border-bottom-right-radius: 6px;
border-bottom-left-radius: 6px;
padding: 5px;
font-size: 12px;
background: #404040;
}
.frame-comments.empty {
padding: 8px 15px;
}
.frame-comments.empty:before {
content: "No comments for this stack frame.";
font-style: italic;
color: #828282;
}
.frame-comment {
padding: 10px;
color: #D2D2D2;
}
.frame-comment a {
color: #BEE9EA;
font-weight: bold;
text-decoration: none;
}
.frame-comment a:hover {
color: #4bb1b1;
}
.frame-comment:not(:last-child) {
border-bottom: 1px dotted rgba(0, 0, 0, .3);
}
.frame-comment-context {
font-size: 10px;
font-weight: bold;
color: #86D2B6;
}
.data-table-container label {
font-size: 16px;
font-weight: bold;
color: #4288CE;
margin: 10px 0;
padding: 10px 0;
display: block;
margin-bottom: 5px;
padding-bottom: 5px;
border-bottom: 1px dotted rgba(0, 0, 0, .2);
}
.data-table {
width: 100%;
margin: 10px 0;
}
.data-table tbody {
font: 13px consolas, monospace;
}
.data-table thead {
display: none;
}
.data-table tr {
padding: 5px 0;
}
.data-table td:first-child {
width: 20%;
min-width: 130px;
overflow: hidden;
font-weight: bold;
color: #463C54;
padding-right: 5px;
}
.data-table td:last-child {
width: 80%;
-ms-word-break: break-all;
word-break: break-all;
word-break: break-word;
-webkit-hyphens: auto;
-moz-hyphens: auto;
hyphens: auto;
}
.data-table .empty {
color: rgba(0, 0, 0, .3);
font-style: italic;
}
.handler {
padding: 10px;
font: 14px monospace;
}
.handler.active {
color: #BBBBBB;
background: #989898;
font-weight: bold;
}
/* prettify code style
Uses the Doxy theme as a base */
pre .str, code .str { color: #BCD42A; } /* string */
pre .kwd, code .kwd { color: #4bb1b1; font-weight: bold; } /* keyword*/
pre .com, code .com { color: #888; font-weight: bold; } /* comment */
pre .typ, code .typ { color: #ef7c61; } /* type */
pre .lit, code .lit { color: #BCD42A; } /* literal */
pre .pun, code .pun { color: #fff; font-weight: bold; } /* punctuation */
pre .pln, code .pln { color: #e9e4e5; } /* plaintext */
pre .tag, code .tag { color: #4bb1b1; } /* html/xml tag */
pre .htm, code .htm { color: #dda0dd; } /* html tag */
pre .xsl, code .xsl { color: #d0a0d0; } /* xslt tag */
pre .atn, code .atn { color: #ef7c61; font-weight: normal;} /* html/xml attribute name */
pre .atv, code .atv { color: #bcd42a; } /* html/xml attribute value */
pre .dec, code .dec { color: #606; } /* decimal */
pre.prettyprint, code.prettyprint {
font-family: 'Source Code Pro', Monaco, Consolas, "Lucida Console", monospace;;
background: #333;
color: #e9e4e5;
}
pre.prettyprint {
white-space: pre-wrap;
}
pre.prettyprint a, code.prettyprint a {
text-decoration:none;
}
.linenums li {
color: #A5A5A5;
}
.linenums li.current{
background: rgba(255, 100, 100, .07);
padding-top: 4px;
padding-left: 1px;
}
.linenums li.current.active {
background: rgba(255, 100, 100, .17);
}

View File

@@ -0,0 +1,205 @@
<?php
/**
* Template file for Whoops's pretty error output.
* Check the $v global variable (stdClass) for what's available
* to work with.
* @var $v
*/
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title><?php echo $e($v->title) ?></title>
<style><?php echo $v->pageStyle ?></style>
</head>
<body>
<div class="container">
<div class="stack-container">
<div class="frames-container cf <?php echo (!$v->hasFrames ? 'empty' : '') ?>">
<?php /* List file names & line numbers for all stack frames;
clicking these links/buttons will display the code view
for that particular frame */ ?>
<?php foreach($v->frames as $i => $frame): ?>
<div class="frame <?php echo ($i == 0 ? 'active' : '') ?>" id="frame-line-<?php echo $i ?>">
<div class="frame-method-info">
<span class="frame-index"><?php echo (count($v->frames) - $i - 1) ?>.</span>
<span class="frame-class"><?php echo $e($frame->getClass() ?: '') ?></span>
<span class="frame-function"><?php echo $e($frame->getFunction() ?: '') ?></span>
</div>
<span class="frame-file">
<?php echo ($frame->getFile(true) ?: '<#unknown>') ?><!--
--><span class="frame-line"><?php echo (int) $frame->getLine() ?></span>
</span>
</div>
<?php endforeach ?>
</div>
<div class="details-container cf">
<header>
<div class="exception">
<h3 class="exc-title">
<?php foreach($v->name as $i => $nameSection): ?>
<?php if($i == count($v->name) - 1): ?>
<span class="exc-title-primary"><?php echo $e($nameSection) ?></span>
<?php else: ?>
<?php echo $e($nameSection) . ' \\' ?>
<?php endif ?>
<?php endforeach ?>
</h3>
<p class="exc-message">
<?php echo $e($v->message) ?>
</p>
</div>
</header>
<?php /* Display a code block for all frames in the stack.
* @todo: This should PROBABLY be done on-demand, lest
* we get 200 frames to process. */ ?>
<div class="frame-code-container <?php echo (!$v->hasFrames ? 'empty' : '') ?>">
<?php foreach($v->frames as $i => $frame): ?>
<?php $line = $frame->getLine(); ?>
<div class="frame-code <?php echo ($i == 0 ) ? 'active' : '' ?>" id="frame-code-<?php echo $i ?>">
<div class="frame-file">
<?php $filePath = $frame->getFile(); ?>
<?php if($filePath && $editorHref = $v->handler->getEditorHref($filePath, (int) $line)): ?>
Open:
<a href="<?php echo $editorHref ?>" class="editor-link">
<strong><?php echo $e($filePath ?: '<#unknown>') ?></strong>
</a>
<?php else: ?>
<strong><?php echo $e($filePath ?: '<#unknown>') ?></strong>
<?php endif ?>
</div>
<?php
// Do nothing if there's no line to work off
if($line !== null):
// the $line is 1-indexed, we nab -1 where needed to account for this
$range = $frame->getFileLines($line - 8, 10);
$range = array_map(function($line){ return empty($line) ? ' ' : $line;}, $range);
$start = key($range) + 1;
$code = join("\n", $range);
?>
<pre class="code-block prettyprint linenums:<?php echo $start ?>"><?php echo $e($code) ?></pre>
<?php endif ?>
<?php
// Append comments for this frame */
$comments = $frame->getComments();
?>
<div class="frame-comments <?php echo empty($comments) ? 'empty' : '' ?>">
<?php foreach($comments as $commentNo => $comment): ?>
<?php extract($comment) ?>
<div class="frame-comment" id="comment-<?php echo $i . '-' . $commentNo ?>">
<span class="frame-comment-context"><?php echo $e($context) ?></span>
<?php echo $e($comment, true) ?>
</div>
<?php endforeach ?>
</div>
</div>
<?php endforeach ?>
</div>
<?php /* List data-table values, i.e: $_SERVER, $_GET, .... */ ?>
<div class="details">
<div class="data-table-container" id="data-tables">
<?php foreach($v->tables as $label => $data): ?>
<div class="data-table" id="sg-<?php echo $e($slug($label)) ?>">
<label><?php echo $e($label) ?></label>
<?php if(!empty($data)): ?>
<table class="data-table">
<thead>
<tr>
<td class="data-table-k">Key</td>
<td class="data-table-v">Value</td>
</tr>
</thead>
<?php foreach($data as $k => $value): ?>
<tr>
<td><?php echo $e($k) ?></td>
<td><?php echo $e(print_r($value, true)) ?></td>
</tr>
<?php endforeach ?>
</table>
<?php else: ?>
<span class="empty">empty</span>
<?php endif ?>
</div>
<?php endforeach ?>
</div>
<?php /* List registered handlers, in order of first to last registered */ ?>
<div class="data-table-container" id="handlers">
<label>Registered Handlers</label>
<?php foreach($v->handlers as $i => $handler): ?>
<div class="handler <?php echo ($handler === $v->handler) ? 'active' : ''?>">
<?php echo $i ?>. <?php echo $e(get_class($handler)) ?>
</div>
<?php endforeach ?>
</div>
</div> <!-- .details -->
</div>
</div>
</div>
<script src="//cdnjs.cloudflare.com/ajax/libs/prettify/r224/prettify.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script>
$(function() {
prettyPrint();
var $frameLines = $('[id^="frame-line-"]');
var $activeLine = $('.frames-container .active');
var $activeFrame = $('.active[id^="frame-code-"]').show();
var $container = $('.details-container');
var headerHeight = $('header').css('height');
var highlightCurrentLine = function() {
// Highlight the active and neighboring lines for this frame:
var activeLineNumber = +($activeLine.find('.frame-line').text());
var $lines = $activeFrame.find('.linenums li');
var firstLine = +($lines.first().val());
$($lines[activeLineNumber - firstLine - 1]).addClass('current');
$($lines[activeLineNumber - firstLine]).addClass('current active');
$($lines[activeLineNumber - firstLine + 1]).addClass('current');
}
// Highlight the active for the first frame:
highlightCurrentLine();
$frameLines.click(function() {
var $this = $(this);
var id = /frame\-line\-([\d]*)/.exec($this.attr('id'))[1];
var $codeFrame = $('#frame-code-' + id);
if($codeFrame) {
$activeLine.removeClass('active');
$activeFrame.removeClass('active');
$this.addClass('active');
$codeFrame.addClass('active');
$activeLine = $this;
$activeFrame = $codeFrame;
highlightCurrentLine();
$container.animate({ scrollTop: headerHeight }, "fast");
}
});
});
</script>
</body>
</html>

253
vendor/filp/whoops/src/Whoops/Run.php vendored Normal file
View File

@@ -0,0 +1,253 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops;
use Whoops\Handler\HandlerInterface;
use Whoops\Handler\Handler;
use Whoops\Handler\CallbackHandler;
use Whoops\Exception\Inspector;
use Whoops\Exception\ErrorException;
use InvalidArgumentException;
use Exception;
class Run
{
const EXCEPTION_HANDLER = 'handleException';
const ERROR_HANDLER = 'handleError';
const SHUTDOWN_HANDLER = 'handleShutdown';
protected $isRegistered;
protected $allowQuit = true;
protected $sendOutput = true;
/**
* @var Whoops\Handler\HandlerInterface[]
*/
protected $handlerStack = array();
/**
* Pushes a handler to the end of the stack.
* @param Whoops\HandlerInterface $handler
* @return Whoops\Run
*/
public function pushHandler($handler)
{
if(is_callable($handler)) {
$handler = new CallbackHandler($handler);
}
if(!$handler instanceof HandlerInterface) {
throw new InvalidArgumentException(
'Argument to ' . __METHOD__ . ' must be a callable, or instance of'
. 'Whoops\\Handler\\HandlerInterface'
);
}
$this->handlerStack[] = $handler;
return $this;
}
/**
* Removes the last handler in the stack and returns it.
* Returns null if there's nothing else to pop.
* @return null|Whoops\Handler\HandlerInterface
*/
public function popHandler()
{
return array_pop($this->handlerStack);
}
/**
* Returns an array with all handlers, in the
* order they were added to the stack.
* @return array
*/
public function getHandlers()
{
return $this->handlerStack;
}
/**
* Clears all handlers in the handlerStack, including
* the default PrettyPage handler.
* @return Whoops\Run
*/
public function clearHandlers()
{
$this->handlerStack = array();
return $this;
}
/**
* @param Exception $exception
* @return Whoops\Exception\Inspector
*/
protected function getInspector(Exception $exception)
{
return new Inspector($exception);
}
/**
* Registers this instance as an error handler.
* @return Whoops\Run
*/
public function register()
{
if(!$this->isRegistered) {
set_error_handler(array($this, self::ERROR_HANDLER));
set_exception_handler(array($this, self::EXCEPTION_HANDLER));
register_shutdown_function(array($this, self::SHUTDOWN_HANDLER));
$this->isRegistered = true;
}
return $this;
}
/**
* Unregisters all handlers registered by this Whoops\Run instance
* @return Whoops\Run
*/
public function unregister()
{
if($this->isRegistered) {
restore_exception_handler();
restore_error_handler();
$this->isRegistered = false;
}
return $this;
}
/**
* Should Whoops allow Handlers to force the script to quit?
* @param bool|num $exit
* @return bool
*/
public function allowQuit($exit = null)
{
if(func_num_args() == 0) {
return $this->allowQuit;
}
return $this->allowQuit = (bool) $exit;
}
/**
* Should Whoops push output directly to the client?
* If this is false, output will be returned by handleException
* @param bool|num $send
* @return bool
*/
public function writeToOutput($send = null)
{
if(func_num_args() == 0) {
return $this->sendOutput;
}
return $this->sendOutput = (bool) $send;
}
/**
* Handles an exception, ultimately generating a Whoops error
* page.
*
* @param Exception $exception
* @return string Output generated by handlers
*/
public function handleException(Exception $exception)
{
// Walk the registered handlers in the reverse order
// they were registered, and pass off the exception
$inspector = $this->getInspector($exception);
// Capture output produced while handling the exception,
// we might want to send it straight away to the client,
// or return it silently.
ob_start();
for($i = count($this->handlerStack) - 1; $i >= 0; $i--) {
$handler = $this->handlerStack[$i];
$handler->setRun($this);
$handler->setInspector($inspector);
$handler->setException($exception);
$handlerResponse = $handler->handle($exception);
if(in_array($handlerResponse, array(Handler::LAST_HANDLER, Handler::QUIT))) {
// The Handler has handled the exception in some way, and
// wishes to quit execution (Handler::QUIT), or skip any
// other handlers (Handler::LAST_HANDLER). If $this->allowQuit
// is false, Handler::QUIT behaves like Handler::LAST_HANDLER
break;
}
}
$output = ob_get_clean();
// If we're allowed to, send output generated by handlers directly
// to the output, otherwise, and if the script doesn't quit, return
// it so that it may be used by the caller
if($this->writeToOutput()) {
// @todo Might be able to clean this up a bit better
// If we're going to quit execution, cleanup all other output
// buffers before sending our own output:
if($handlerResponse == Handler::QUIT && $this->allowQuit()) {
while (ob_get_level() > 0) ob_end_clean();
}
echo $output;
}
// Handlers are done! Check if we got here because of Handler::QUIT
// ($handlerResponse will be the response from the last queried handler)
// and if so, try to quit execution.
if($handlerResponse == Handler::QUIT && $this->allowQuit()) {
exit;
}
return $output;
}
/**
* Converts generic PHP errors to \ErrorException
* instances, before passing them off to be handled.
*
* This method MUST be compatible with set_error_handler.
*
* @param int $level
* @param string $message
* @param string $file
* @param int $line
*/
public function handleError($level, $message, $file = null, $line = null)
{
if ($level & error_reporting()) {
$this->handleException(
new ErrorException(
$message, $level, 0, $file, $line
)
);
}
}
/**
* Special case to deal with Fatal errors and the like.
*/
public function handleShutdown()
{
if($error = error_get_last()) {
$this->handleError(
$error['type'],
$error['message'],
$error['file'],
$error['line']
);
}
}
}