Merge pull request #4 from filp/master

merge
This commit is contained in:
Daniel Bannert
2016-06-02 14:58:33 +02:00
18 changed files with 415 additions and 76 deletions
+10 -6
View File
@@ -1,20 +1,24 @@
language: php
php:
- 5.5.9
- 5.5
- 5.6
- 7.0
- '5.5.9'
- '5.5'
- '5.6'
- '7.0'
- hhvm
sudo: false
cache:
directories:
- "$HOME/.composer/cache"
before_script:
- composer install --no-interaction --prefer-source
- composer install --no-interaction --prefer-dist
- if [ $TRAVIS_PHP_VERSION = "5.6" ]; then PHPUNIT_FLAGS="--coverage-clover build/logs/clover.xml"; fi
script:
- phpunit --verbose $PHPUNIT_FLAGS
- vendor/bin/phpunit --verbose $PHPUNIT_FLAGS
after_success:
- if [ $TRAVIS_PHP_VERSION = "5.6" ]; then wget https://scrutinizer-ci.com/ocular.phar && php ocular.phar code-coverage:upload --format=php-clover build/logs/clover.xml; fi
+7
View File
@@ -1,3 +1,10 @@
# 2.1.0
* Add a `SystemFacade` to allow clients to override Whoops behavior.
* Show frame arguments in `PrettyPageHandler`.
* Highlight the line with the error.
* Add icons to search on Google and Stack Overflow.
# 2.0.0
Backwards compatibility breaking changes:
+1 -1
View File
@@ -79,4 +79,4 @@ You can also use pluggable handlers, such as [SOAP handler](https://github.com/w
## Authors
This library was primarily developed by [Filipe Dobreira](https://github.com/filp), and is currently maintained by [Denis Sokolov](https://github.com/denis-sokolov). A lot of awesome fixes and enhancements were also sent in by [various contributors](https://github.com/filp/whoops/contributors).
This library was primarily developed by [Filipe Dobreira](https://github.com/filp), and is currently maintained by [Denis Sokolov](https://github.com/denis-sokolov). A lot of awesome fixes and enhancements were also sent in by [various contributors](https://github.com/filp/whoops/contributors). Special thanks to [Graham Campbell](https://github.com/GrahamCampbell) and [Markus Staab](https://github.com/staabm) for continuous participation.
+2 -1
View File
@@ -16,7 +16,8 @@
},
"require-dev": {
"phpunit/phpunit": "^4.8 || ^5.0",
"mockery/mockery": "0.9.*"
"mockery/mockery": "0.9.*",
"symfony/var-dumper": "~3.0"
},
"suggest": {
"symfony/var-dumper": "Pretty print complex values better with var-dumper available",
+1 -1
View File
@@ -15,7 +15,7 @@ $handler->setEditor('sublime');
The following editors are currently supported by default.
- `sublime` - Sublime Text 2
- `sublime` - Sublime Text 2 and possibly 3 (on OS X you might need [a special handler](https://github.com/dhoulb/subl))
- `emacs` - Emacs
- `textmate` - Textmate
- `macvim` - MacVim
+7 -8
View File
@@ -31,16 +31,15 @@ $run->pushHandler(new PrettyPageHandler());
// Now, we want a second handler that will run before the error page,
// and immediately return an error message in JSON format, if something
// goes awry.
$jsonHandler = new JsonResponseHandler();
if (\Whoops\Util\Misc::isAjaxRequest()) {
$jsonHandler = new JsonResponseHandler();
// Make sure it only triggers for AJAX requests:
$jsonHandler->onlyForAjaxRequests(true);
// You can also tell JsonResponseHandler to give you a full stack trace:
// $jsonHandler->addTraceToOutput(true);
// You can also tell JsonResponseHandler to give you a full stack trace:
// $jsonHandler->addTraceToOutput(true);
// And push it into the stack:
$run->pushHandler($jsonHandler);
// And push it into the stack:
$run->pushHandler($jsonHandler);
}
// That's it! Register Whoops and throw a dummy exception:
$run->register();
+21
View File
@@ -8,6 +8,8 @@ namespace Whoops\Handler;
use InvalidArgumentException;
use RuntimeException;
use Symfony\Component\VarDumper\Cloner\AbstractCloner;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use UnexpectedValueException;
use Whoops\Exception\Formatter;
use Whoops\Util\Misc;
@@ -117,6 +119,25 @@ class PrettyPageHandler extends Handler
// @todo: Make this more dynamic
$helper = new TemplateHelper();
if (class_exists('Symfony\Component\VarDumper\Cloner\VarCloner')) {
$cloner = new VarCloner();
// Only dump object internals if a custom caster exists.
$cloner->addCasters(['*' => function ($obj, $a, $stub, $isNested, $filter = 0) {
$class = $stub->class;
$classes = [$class => $class] + class_parents($class) + class_implements($class);
foreach ($classes as $class) {
if (isset(AbstractCloner::$defaultCasters[$class])) {
return $a;
}
}
// Remove all internals
return [];
}]);
$helper->setCloner($cloner);
}
$templateFile = $this->getResource("views/layout.html.php");
$cssFile = $this->getResource("css/whoops.base.css");
$zeptoFile = $this->getResource("js/zepto.min.js");
File diff suppressed because one or more lines are too long
+6
View File
@@ -119,4 +119,10 @@ Zepto(function($) {
e.preventDefault();
$.get(this.href);
});
// Symfony VarDumper: Close the by default expanded objects
$('.sf-dump-expanded')
.removeClass('sf-dump-expanded')
.addClass('sf-dump-compact');
$('.sf-dump-toggle span').html('▶');
});
@@ -17,7 +17,7 @@
<?php foreach ($data as $k => $value): ?>
<tr>
<td><?php echo $tpl->escape($k) ?></td>
<td><?php echo $tpl->escape($tpl->dump($value)) ?></td>
<td><?php echo $tpl->dump($value) ?></td>
</tr>
<?php endforeach ?>
</table>
@@ -47,6 +47,16 @@
<?php endif ?>
<?php endif ?>
<?php $frameArgs = $tpl->dumpArgs($frame); ?>
<?php if ($frameArgs): ?>
<div class="frame-file">
Arguments
</div>
<div class="code-block frame-args prettyprint">
<?php echo $frameArgs; ?>
</div>
<?php endif ?>
<?php
// Append comments for this frame
$comments = $frame->getComments();
@@ -14,4 +14,4 @@
--><span class="frame-line"><?php echo (int) $frame->getLine() ?></span>
</span>
</div>
<?php endforeach ?>
<?php endforeach; ?>
+21 -3
View File
@@ -20,12 +20,30 @@
<?php endif ?>
<ul class="search-for-help">
<li><a target="_blank" href="https://google.com/search?q=<?php echo urlencode(implode('\\', $name).' '.$message) ?>" title="Search for help on Google."><i class="google"></i></a></li>
<li><a target="_blank" href="https://stackoverflow.com/search?q=<?php echo urlencode(implode('\\', $name).' '.$message) ?>" title="Search for help on Stack Overflow."><i class="stackoverflow"></i></a></li>
<li>
<a target="_blank" href="https://google.com/search?q=<?php echo urlencode(implode('\\', $name).' '.$message) ?>" title="Search for help on Google.">
<!-- Google icon by Alfredo H, from https://www.iconfinder.com/alfredoh -->
<!-- Creative Commons (Attribution 3.0 Unported) -->
<!-- http://creativecommons.org/licenses/by/3.0/ -->
<svg class="google" height="16" viewBox="0 0 512 512" width="16" xmlns="http://www.w3.org/2000/svg">
<path d="M457.732 216.625c2.628 14.04 4.063 28.743 4.063 44.098C461.795 380.688 381.48 466 260.205 466c-116.024 0-210-93.977-210-210s93.976-210 210-210c56.703 0 104.076 20.867 140.44 54.73l-59.205 59.197v-.135c-22.046-21.002-50-31.762-81.236-31.762-69.297 0-125.604 58.537-125.604 127.84 0 69.29 56.306 127.97 125.604 127.97 62.87 0 105.653-35.966 114.46-85.313h-114.46v-81.902h197.528z"/>
</svg>
</a>
</li>
<li>
<a target="_blank" href="https://stackoverflow.com/search?q=<?php echo urlencode(implode('\\', $name).' '.$message) ?>" title="Search for help on Stack Overflow.">
<!-- Stack Overflow icon by Picons.me, from https://www.iconfinder.com/Picons -->
<!-- Free for commercial use -->
<svg class="stackoverflow" height="16" viewBox="-1163 1657.697 56.693 56.693" width="16" xmlns="http://www.w3.org/2000/svg">
<path d="M-1126.04 1689.533l-16.577-9.778 2.088-3.54 16.578 9.778zM-1127.386 1694.635l-18.586-4.996 1.068-3.97 18.586 4.995zM-1127.824 1700.137l-19.165-1.767.378-4.093 19.165 1.767zM-1147.263 1701.293h19.247v4.11h-19.247z"/>
<path d="M-1121.458 1710.947s0 .96-.032.96v.016h-30.796s-.96 0-.96-.016h-.032v-20.03h3.288v16.805h25.244v-16.804h3.288v19.07zM-1130.667 1667.04l10.844 15.903-3.396 2.316-10.843-15.903zM-1118.313 1663.044l3.29 18.963-4.05.703-3.29-18.963z"/>
</svg>
</a>
</li>
</ul>
<span id="plain-exception"><?php echo $tpl->escape($plain_exception) ?></span>
<button id="copy-button" class="clipboard" data-clipboard-text="<?php echo $tpl->escape($plain_exception) ?>" title="Copy exception details to clipabord">
<button id="copy-button" class="clipboard" data-clipboard-text="<?php echo $tpl->escape($plain_exception) ?>" title="Copy exception details to clipboard">
COPY
</button>
</div>
+9 -13
View File
@@ -15,29 +15,25 @@ use Whoops\Handler\HandlerInterface;
use Whoops\Util\Misc;
use Whoops\Util\SystemFacade;
final class Run
final class Run implements RunInterface
{
const EXCEPTION_HANDLER = "handleException";
const ERROR_HANDLER = "handleError";
const SHUTDOWN_HANDLER = "handleShutdown";
protected $isRegistered;
protected $allowQuit = true;
protected $sendOutput = true;
private $isRegistered;
private $allowQuit = true;
private $sendOutput = true;
/**
* @var integer|false
*/
protected $sendHttpCode = 500;
private $sendHttpCode = 500;
/**
* @var HandlerInterface[]
*/
protected $handlerStack = array();
private $handlerStack = array();
protected $silencedPatterns = array();
private $silencedPatterns = array();
protected $system;
private $system;
public function __construct(SystemFacade $system = null)
{
@@ -103,7 +99,7 @@ final class Run
* @param \Throwable $exception
* @return Inspector
*/
protected function getInspector($exception)
private function getInspector($exception)
{
return new Inspector($exception);
}
+131
View File
@@ -0,0 +1,131 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops;
use InvalidArgumentException;
use Whoops\Exception\ErrorException;
use Whoops\Handler\HandlerInterface;
interface RunInterface
{
const EXCEPTION_HANDLER = "handleException";
const ERROR_HANDLER = "handleError";
const SHUTDOWN_HANDLER = "handleShutdown";
/**
* Pushes a handler to the end of the stack
*
* @throws InvalidArgumentException If argument is not callable or instance of HandlerInterface
* @param Callable|HandlerInterface $handler
* @return Run
*/
public function pushHandler($handler);
/**
* Removes the last handler in the stack and returns it.
* Returns null if there"s nothing else to pop.
*
* @return null|HandlerInterface
*/
public function popHandler();
/**
* Returns an array with all handlers, in the
* order they were added to the stack.
*
* @return array
*/
public function getHandlers();
/**
* Clears all handlers in the handlerStack, including
* the default PrettyPage handler.
*
* @return Run
*/
public function clearHandlers();
/**
* Registers this instance as an error handler.
*
* @return Run
*/
public function register();
/**
* Unregisters all handlers registered by this Whoops\Run instance
*
* @return Run
*/
public function unregister();
/**
* Should Whoops allow Handlers to force the script to quit?
*
* @param bool|int $exit
* @return bool
*/
public function allowQuit($exit = null);
/**
* Silence particular errors in particular files
*
* @param array|string $patterns List or a single regex pattern to match
* @param int $levels Defaults to E_STRICT | E_DEPRECATED
* @return \Whoops\Run
*/
public function silenceErrorsInPaths($patterns, $levels = 10240);
/**
* Should Whoops send HTTP error code to the browser if possible?
* Whoops will by default send HTTP code 500, but you may wish to
* use 502, 503, or another 5xx family code.
*
* @param bool|int $code
* @return int|false
*/
public function sendHttpCode($code = null);
/**
* Should Whoops push output directly to the client?
* If this is false, output will be returned by handleException
*
* @param bool|int $send
* @return bool
*/
public function writeToOutput($send = null);
/**
* Handles an exception, ultimately generating a Whoops error
* page.
*
* @param \Throwable $exception
* @return string Output generated by handlers
*/
public function handleException($exception);
/**
* 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
*
* @return bool
* @throws ErrorException
*/
public function handleError($level, $message, $file = null, $line = null);
/**
* Special case to deal with Fatal errors and the like.
*/
public function handleShutdown();
}
+37
View File
@@ -0,0 +1,37 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Whoops\Util;
/**
* Used as output callable for Symfony\Component\VarDumper\Dumper\HtmlDumper::dump()
*
* @see TemplateHelper::dump()
*/
class HtmlDumperOutput
{
private $output;
public function __invoke($line, $depth)
{
// A negative depth means "end of dump"
if ($depth >= 0) {
// Adds a two spaces indentation to the line
$this->output .= str_repeat(' ', $depth) . $line . "\n";
}
}
public function getOutput()
{
return $this->output;
}
public function clear()
{
$this->output = null;
}
}
+107 -24
View File
@@ -6,9 +6,11 @@
namespace Whoops\Util;
use Symfony\Component\VarDumper\Caster\Caster;
use Symfony\Component\VarDumper\Cloner\AbstractCloner;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
use Whoops\Exception\Frame;
/**
* Exposes useful tools for working with/in templates
@@ -21,6 +23,21 @@ class TemplateHelper
*/
private $variables = array();
/**
* @var HtmlDumper
*/
private $htmlDumper;
/**
* @var HtmlDumperOutput
*/
private $htmlDumperOutput;
/**
* @var AbstractCloner
*/
private $cloner;
/**
* Escapes a string for output in an HTML document
*
@@ -62,6 +79,33 @@ class TemplateHelper
);
}
private function getDumper()
{
if (!$this->htmlDumper && class_exists('Symfony\Component\VarDumper\Cloner\VarCloner')) {
$this->htmlDumperOutput = new HtmlDumperOutput();
// re-use the same var-dumper instance, so it won't re-render the global styles/scripts on each dump.
$this->htmlDumper = new HtmlDumper($this->htmlDumperOutput);
$styles = array(
'default' => 'color:#FFFFFF; line-height:normal; font:12px "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace !important; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: normal',
'num' => 'color:#BCD42A',
'const' => 'color: #4bb1b1;',
'str' => 'color:#BCD42A',
'note' => 'color:#ef7c61',
'ref' => 'color:#A0A0A0',
'public' => 'color:#FFFFFF',
'protected' => 'color:#FFFFFF',
'private' => 'color:#FFFFFF',
'meta' => 'color:#FFFFFF',
'key' => 'color:#BCD42A',
'index' => 'color:#ef7c61',
);
$this->htmlDumper->setStyles($styles);
}
return $this->htmlDumper;
}
/**
* Format the given value into a human readable string.
*
@@ -70,36 +114,52 @@ class TemplateHelper
*/
public function dump($value)
{
if (class_exists('Symfony\Component\VarDumper\Cloner\VarCloner')) {
static $dumper = null;
$dumper = $this->getDumper();
// re-use the same var-dumper instance, so it won't re-render the global styles/scripts on each dump.
if (!$dumper) {
$dumper = new HtmlDumper();
if ($dumper) {
// re-use the same DumpOutput instance, so it won't re-render the global styles/scripts on each dump.
// exclude verbose information (e.g. exception stack traces)
$dumper->dump(
$this->getCloner()->cloneVar($value, Caster::EXCLUDE_VERBOSE),
$this->htmlDumperOutput
);
$styles = array(
'default' => '',
'num' => '',
'const' => '',
'str' => '',
'note' => '',
'ref' => '',
'public' => '',
'protected' => '',
'private' => '',
'meta' => '',
'key' => '',
'index' => '',
);
$dumper->setStyles($styles);
}
$output = $this->htmlDumperOutput->getOutput();
$this->htmlDumperOutput->clear();
$cloner = new VarCloner();
return $dumper->dump($cloner->cloneVar($value));
return $output;
}
return print_r($value, true);
}
/**
* Format the args of the given Frame as a human readable html string
*
* @param Frame $frame
* @return string the rendered html
*/
public function dumpArgs(Frame $frame)
{
// we support frame args only when the optional dumper is available
if (!$this->getDumper()) {
return '';
}
$html = '';
$numFrames = count($frame->getArgs());
if ($numFrames > 0) {
$html = '<ol class="linenums">';
foreach($frame->getArgs() as $j => $frameArg) {
$html .= '<li>'. $this->dump($frameArg) .'</li>';
}
$html .= '</ol>';
}
return $html;
}
/**
* Convert a string to a slug version of itself
*
@@ -193,4 +253,27 @@ class TemplateHelper
{
return $this->variables;
}
/**
* Set the cloner used for dumping variables.
*
* @param AbstractCloner $cloner
*/
public function setCloner($cloner)
{
$this->cloner = $cloner;
}
/**
* Get the cloner used for dumping variables.
*
* @return AbstractCloner
*/
public function getCloner()
{
if (!$this->cloner) {
$this->cloner = new VarCloner();
}
return $this->cloner;
}
}
+12
View File
@@ -15,6 +15,18 @@ use Whoops\Handler\Handler;
class RunTest extends TestCase
{
public function testImplementsRunInterface()
{
$this->assertNotFalse(class_implements('Whoops\\Run', 'Whoops\\RunInterface'));
}
public function testConstantsAreAccessibleFromTheClass()
{
$this->assertEquals(RunInterface::ERROR_HANDLER, Run::ERROR_HANDLER);
$this->assertEquals(RunInterface::EXCEPTION_HANDLER, Run::EXCEPTION_HANDLER);
$this->assertEquals(RunInterface::SHUTDOWN_HANDLER, Run::SHUTDOWN_HANDLER);
}
/**
* @param string $message
* @return Exception