Upstream filp/whoops state of the touched files before 5805bc7620
This commit is contained in:
1 parent
7f2cd8f154
commit
a5b89f86ae
4 files changed
+1581
No files matched your search
@@ -0,0 +1,607 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Whoops - php errors for cool kids
|
||||||
|
* @author Filipe Dobreira <http://github.com/filp>
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Whoops;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Throwable;
|
||||||
|
use Whoops\Exception\ErrorException;
|
||||||
|
use Whoops\Handler\CallbackHandler;
|
||||||
|
use Whoops\Handler\Handler;
|
||||||
|
use Whoops\Handler\HandlerInterface;
|
||||||
|
use Whoops\Inspector\CallableInspectorFactory;
|
||||||
|
use Whoops\Inspector\InspectorFactory;
|
||||||
|
use Whoops\Inspector\InspectorFactoryInterface;
|
||||||
|
use Whoops\Inspector\InspectorInterface;
|
||||||
|
use Whoops\Util\Misc;
|
||||||
|
use Whoops\Util\SystemFacade;
|
||||||
|
|
||||||
|
final class Run implements RunInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $isRegistered;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $allowQuit = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $sendOutput = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var integer|false
|
||||||
|
*/
|
||||||
|
private $sendHttpCode = 500;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var integer|false
|
||||||
|
*/
|
||||||
|
private $sendExitCode = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var HandlerInterface[]
|
||||||
|
*/
|
||||||
|
private $handlerStack = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
* @psalm-var list<array{patterns: string, levels: int}>
|
||||||
|
*/
|
||||||
|
private $silencedPatterns = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var SystemFacade
|
||||||
|
*/
|
||||||
|
private $system;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In certain scenarios, like in shutdown handler, we can not throw exceptions.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $canThrowExceptions = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The inspector factory to create inspectors.
|
||||||
|
*
|
||||||
|
* @var InspectorFactoryInterface
|
||||||
|
*/
|
||||||
|
private $inspectorFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<callable>
|
||||||
|
*/
|
||||||
|
private $frameFilters = [];
|
||||||
|
|
||||||
|
public function __construct(?SystemFacade $system = null)
|
||||||
|
{
|
||||||
|
$this->system = $system ?: new SystemFacade;
|
||||||
|
$this->inspectorFactory = new InspectorFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
$this->unregister();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Explicitly request your handler runs as the last of all currently registered handlers.
|
||||||
|
*
|
||||||
|
* @param callable|HandlerInterface $handler
|
||||||
|
*
|
||||||
|
* @return Run
|
||||||
|
*/
|
||||||
|
public function appendHandler($handler)
|
||||||
|
{
|
||||||
|
array_unshift($this->handlerStack, $this->resolveHandler($handler));
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Explicitly request your handler runs as the first of all currently registered handlers.
|
||||||
|
*
|
||||||
|
* @param callable|HandlerInterface $handler
|
||||||
|
*
|
||||||
|
* @return Run
|
||||||
|
*/
|
||||||
|
public function prependHandler($handler)
|
||||||
|
{
|
||||||
|
return $this->pushHandler($handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register your handler as the last of all currently registered handlers (to be executed first).
|
||||||
|
* Prefer using appendHandler and prependHandler for clarity.
|
||||||
|
*
|
||||||
|
* @param callable|HandlerInterface $handler
|
||||||
|
*
|
||||||
|
* @return Run
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException If argument is not callable or instance of HandlerInterface.
|
||||||
|
*/
|
||||||
|
public function pushHandler($handler)
|
||||||
|
{
|
||||||
|
$this->handlerStack[] = $this->resolveHandler($handler);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes and returns the last handler pushed to the handler stack.
|
||||||
|
*
|
||||||
|
* @see Run::removeFirstHandler(), Run::removeLastHandler()
|
||||||
|
*
|
||||||
|
* @return HandlerInterface|null
|
||||||
|
*/
|
||||||
|
public function popHandler()
|
||||||
|
{
|
||||||
|
return array_pop($this->handlerStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the first handler.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function removeFirstHandler()
|
||||||
|
{
|
||||||
|
array_pop($this->handlerStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the last handler.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function removeLastHandler()
|
||||||
|
{
|
||||||
|
array_shift($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 Run
|
||||||
|
*/
|
||||||
|
public function clearHandlers()
|
||||||
|
{
|
||||||
|
$this->handlerStack = [];
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFrameFilters()
|
||||||
|
{
|
||||||
|
return $this->frameFilters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function clearFrameFilters()
|
||||||
|
{
|
||||||
|
$this->frameFilters = [];
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers this instance as an error handler.
|
||||||
|
*
|
||||||
|
* @return Run
|
||||||
|
*/
|
||||||
|
public function register()
|
||||||
|
{
|
||||||
|
if (!$this->isRegistered) {
|
||||||
|
// Workaround PHP bug 42098
|
||||||
|
// https://bugs.php.net/bug.php?id=42098
|
||||||
|
class_exists("\\Whoops\\Exception\\ErrorException");
|
||||||
|
class_exists("\\Whoops\\Exception\\FrameCollection");
|
||||||
|
class_exists("\\Whoops\\Exception\\Frame");
|
||||||
|
class_exists("\\Whoops\\Exception\\Inspector");
|
||||||
|
class_exists("\\Whoops\\Inspector\\InspectorFactory");
|
||||||
|
|
||||||
|
$this->system->setErrorHandler([$this, self::ERROR_HANDLER]);
|
||||||
|
$this->system->setExceptionHandler([$this, self::EXCEPTION_HANDLER]);
|
||||||
|
$this->system->registerShutdownFunction([$this, self::SHUTDOWN_HANDLER]);
|
||||||
|
|
||||||
|
$this->isRegistered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters all handlers registered by this Whoops\Run instance.
|
||||||
|
*
|
||||||
|
* @return Run
|
||||||
|
*/
|
||||||
|
public function unregister()
|
||||||
|
{
|
||||||
|
if ($this->isRegistered) {
|
||||||
|
$this->system->restoreExceptionHandler();
|
||||||
|
$this->system->restoreErrorHandler();
|
||||||
|
|
||||||
|
$this->isRegistered = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should Whoops allow Handlers to force the script to quit?
|
||||||
|
*
|
||||||
|
* @param bool|int $exit
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function allowQuit($exit = null)
|
||||||
|
{
|
||||||
|
if (func_num_args() == 0) {
|
||||||
|
return $this->allowQuit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->allowQuit = (bool) $exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 Run
|
||||||
|
*/
|
||||||
|
public function silenceErrorsInPaths($patterns, $levels = 10240)
|
||||||
|
{
|
||||||
|
$this->silencedPatterns = array_merge(
|
||||||
|
$this->silencedPatterns,
|
||||||
|
array_map(
|
||||||
|
function ($pattern) use ($levels) {
|
||||||
|
return [
|
||||||
|
"pattern" => $pattern,
|
||||||
|
"levels" => $levels,
|
||||||
|
];
|
||||||
|
},
|
||||||
|
(array) $patterns
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array with silent errors in path configuration.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getSilenceErrorsInPaths()
|
||||||
|
{
|
||||||
|
return $this->silencedPatterns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function sendHttpCode($code = null)
|
||||||
|
{
|
||||||
|
if (func_num_args() == 0) {
|
||||||
|
return $this->sendHttpCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$code) {
|
||||||
|
return $this->sendHttpCode = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($code === true) {
|
||||||
|
$code = 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($code < 400 || 600 <= $code) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
"Invalid status code '$code', must be 4xx or 5xx"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->sendHttpCode = $code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should Whoops exit with a specific code on the CLI if possible?
|
||||||
|
* Whoops will exit with 1 by default, but you can specify something else.
|
||||||
|
*
|
||||||
|
* @param int $code
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function sendExitCode($code = null)
|
||||||
|
{
|
||||||
|
if (func_num_args() == 0) {
|
||||||
|
return $this->sendExitCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($code < 0 || 255 <= $code) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
"Invalid status code '$code', must be between 0 and 254"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->sendExitCode = (int) $code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
{
|
||||||
|
if (func_num_args() == 0) {
|
||||||
|
return $this->sendOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->sendOutput = (bool) $send;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles an exception, ultimately generating a Whoops error page.
|
||||||
|
*
|
||||||
|
* @param Throwable $exception
|
||||||
|
*
|
||||||
|
* @return string Output generated by handlers.
|
||||||
|
*/
|
||||||
|
public function handleException($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.
|
||||||
|
$this->system->startOutputBuffering();
|
||||||
|
|
||||||
|
// Just in case there are no handlers:
|
||||||
|
$handlerResponse = null;
|
||||||
|
$handlerContentType = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
foreach (array_reverse($this->handlerStack) as $handler) {
|
||||||
|
$handler->setRun($this);
|
||||||
|
$handler->setInspector($inspector);
|
||||||
|
$handler->setException($exception);
|
||||||
|
|
||||||
|
// The HandlerInterface does not require an Exception passed to handle()
|
||||||
|
// and neither of our bundled handlers use it.
|
||||||
|
// However, 3rd party handlers may have already relied on this parameter,
|
||||||
|
// and removing it would be possibly breaking for users.
|
||||||
|
$handlerResponse = $handler->handle($exception);
|
||||||
|
|
||||||
|
// Collect the content type for possible sending in the headers.
|
||||||
|
$handlerContentType = method_exists($handler, 'contentType') ? $handler->contentType() : null;
|
||||||
|
|
||||||
|
if (in_array($handlerResponse, [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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$willQuit = $handlerResponse == Handler::QUIT && $this->allowQuit();
|
||||||
|
} finally {
|
||||||
|
$output = $this->system->cleanOutputBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 ($willQuit) {
|
||||||
|
// Cleanup all other output buffers before sending our output:
|
||||||
|
while ($this->system->getOutputBufferLevel() > 0) {
|
||||||
|
$this->system->endOutputBuffering();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send any headers if needed:
|
||||||
|
if (Misc::canSendHeaders() && $handlerContentType) {
|
||||||
|
header("Content-Type: {$handlerContentType}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->writeToOutputNow($output);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($willQuit) {
|
||||||
|
// HHVM fix for https://github.com/facebook/hhvm/issues/4055
|
||||||
|
$this->system->flushOutputBuffer();
|
||||||
|
|
||||||
|
$this->system->stopExecution(
|
||||||
|
$this->sendExitCode()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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|null $file
|
||||||
|
* @param int|null $line
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*
|
||||||
|
* @throws ErrorException
|
||||||
|
*/
|
||||||
|
public function handleError($level, $message, $file = null, $line = null)
|
||||||
|
{
|
||||||
|
if ($level & $this->system->getErrorReportingLevel()) {
|
||||||
|
foreach ($this->silencedPatterns as $entry) {
|
||||||
|
$pathMatches = (bool) preg_match($entry["pattern"], $file);
|
||||||
|
$levelMatches = $level & $entry["levels"];
|
||||||
|
if ($pathMatches && $levelMatches) {
|
||||||
|
// Ignore the error, abort handling
|
||||||
|
// See https://github.com/filp/whoops/issues/418
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX we pass $level for the "code" param only for BC reasons.
|
||||||
|
// see https://github.com/filp/whoops/issues/267
|
||||||
|
$exception = new ErrorException($message, /*code*/ $level, /*severity*/ $level, $file, $line);
|
||||||
|
if ($this->canThrowExceptions) {
|
||||||
|
throw $exception;
|
||||||
|
} else {
|
||||||
|
$this->handleException($exception);
|
||||||
|
}
|
||||||
|
// Do not propagate errors which were already handled by Whoops.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Propagate error to the next handler, allows error_get_last() to
|
||||||
|
// work on silenced errors.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special case to deal with Fatal errors and the like.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handleShutdown()
|
||||||
|
{
|
||||||
|
// If we reached this step, we are in shutdown handler.
|
||||||
|
// An exception thrown in a shutdown handler will not be propagated
|
||||||
|
// to the exception handler. Pass that information along.
|
||||||
|
$this->canThrowExceptions = false;
|
||||||
|
|
||||||
|
// If we are not currently registered, we should not do anything
|
||||||
|
if (!$this->isRegistered) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$error = $this->system->getLastError();
|
||||||
|
if ($error && Misc::isLevelFatal($error['type'])) {
|
||||||
|
// If there was a fatal error,
|
||||||
|
// it was not handled in handleError yet.
|
||||||
|
$this->allowQuit = false;
|
||||||
|
$this->handleError(
|
||||||
|
$error['type'],
|
||||||
|
$error['message'],
|
||||||
|
$error['file'],
|
||||||
|
$error['line']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InspectorFactoryInterface $factory
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setInspectorFactory(InspectorFactoryInterface $factory)
|
||||||
|
{
|
||||||
|
$this->inspectorFactory = $factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addFrameFilter($filterCallback)
|
||||||
|
{
|
||||||
|
if (!is_callable($filterCallback)) {
|
||||||
|
throw new \InvalidArgumentException(sprintf(
|
||||||
|
"A frame filter must be of type callable, %s type given.",
|
||||||
|
gettype($filterCallback)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->frameFilters[] = $filterCallback;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Throwable $exception
|
||||||
|
*
|
||||||
|
* @return InspectorInterface
|
||||||
|
*/
|
||||||
|
private function getInspector($exception)
|
||||||
|
{
|
||||||
|
return $this->inspectorFactory->create($exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the giving handler.
|
||||||
|
*
|
||||||
|
* @param callable|HandlerInterface $handler
|
||||||
|
*
|
||||||
|
* @return HandlerInterface
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
private function resolveHandler($handler)
|
||||||
|
{
|
||||||
|
if (is_callable($handler)) {
|
||||||
|
$handler = new CallbackHandler($handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$handler instanceof HandlerInterface) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
"Handler must be a callable, or instance of "
|
||||||
|
. "Whoops\\Handler\\HandlerInterface"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Echo something to the browser.
|
||||||
|
*
|
||||||
|
* @param string $output
|
||||||
|
*
|
||||||
|
* @return Run
|
||||||
|
*/
|
||||||
|
private function writeToOutputNow($output)
|
||||||
|
{
|
||||||
|
if ($this->sendHttpCode() && Misc::canSendHeaders()) {
|
||||||
|
$this->system->setHttpResponseCode(
|
||||||
|
$this->sendHttpCode()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo $output;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Whoops - php errors for cool kids
|
||||||
|
* @author Filipe Dobreira <http://github.com/filp>
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Whoops\Util;
|
||||||
|
|
||||||
|
class SystemFacade
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Turns on output buffering.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function startOutputBuffering()
|
||||||
|
{
|
||||||
|
return ob_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callable $handler
|
||||||
|
* @param int $types
|
||||||
|
*
|
||||||
|
* @return callable|null
|
||||||
|
*/
|
||||||
|
public function setErrorHandler(callable $handler, $types = 'use-php-defaults')
|
||||||
|
{
|
||||||
|
// Since PHP 5.4 the constant E_ALL contains all errors (even E_STRICT)
|
||||||
|
if ($types === 'use-php-defaults') {
|
||||||
|
$types = E_ALL;
|
||||||
|
}
|
||||||
|
return set_error_handler($handler, $types);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callable $handler
|
||||||
|
*
|
||||||
|
* @return callable|null
|
||||||
|
*/
|
||||||
|
public function setExceptionHandler(callable $handler)
|
||||||
|
{
|
||||||
|
return set_exception_handler($handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function restoreExceptionHandler()
|
||||||
|
{
|
||||||
|
restore_exception_handler();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function restoreErrorHandler()
|
||||||
|
{
|
||||||
|
restore_error_handler();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callable $function
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function registerShutdownFunction(callable $function)
|
||||||
|
{
|
||||||
|
register_shutdown_function($function);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string|false
|
||||||
|
*/
|
||||||
|
public function cleanOutputBuffer()
|
||||||
|
{
|
||||||
|
return ob_get_clean();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getOutputBufferLevel()
|
||||||
|
{
|
||||||
|
return ob_get_level();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function endOutputBuffering()
|
||||||
|
{
|
||||||
|
return ob_end_clean();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function flushOutputBuffer()
|
||||||
|
{
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getErrorReportingLevel()
|
||||||
|
{
|
||||||
|
return error_reporting();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
public function getLastError()
|
||||||
|
{
|
||||||
|
return error_get_last();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $httpCode
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function setHttpResponseCode($httpCode)
|
||||||
|
{
|
||||||
|
if (!headers_sent()) {
|
||||||
|
// Ensure that no 'location' header is present as otherwise this
|
||||||
|
// will override the HTTP code being set here, and mask the
|
||||||
|
// expected error page.
|
||||||
|
header_remove('location');
|
||||||
|
}
|
||||||
|
|
||||||
|
return http_response_code($httpCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $exitStatus
|
||||||
|
*/
|
||||||
|
public function stopExecution($exitStatus)
|
||||||
|
{
|
||||||
|
exit($exitStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,609 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Whoops - php errors for cool kids
|
||||||
|
* @author Filipe Dobreira <http://github.com/filp>
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Whoops;
|
||||||
|
|
||||||
|
use ArrayObject;
|
||||||
|
use Exception;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Mockery as m;
|
||||||
|
use RuntimeException;
|
||||||
|
use Whoops\Handler\Handler;
|
||||||
|
use Whoops\Handler\HandlerInterface;
|
||||||
|
use Whoops\Handler\PrettyPageHandler;
|
||||||
|
use Whoops\Exception\Frame;
|
||||||
|
use Whoops\Util\SystemFacade;
|
||||||
|
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
protected function getException($message = "")
|
||||||
|
{
|
||||||
|
// HHVM does not support mocking exceptions
|
||||||
|
// Since we do not use any additional features of Mockery for exceptions,
|
||||||
|
// we can just use native Exceptions instead.
|
||||||
|
return new \Exception($message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Handler
|
||||||
|
*/
|
||||||
|
protected function getHandler()
|
||||||
|
{
|
||||||
|
return m::mock('Whoops\\Handler\\Handler')
|
||||||
|
->shouldReceive('setRun')
|
||||||
|
->andReturn(null)
|
||||||
|
->mock()
|
||||||
|
|
||||||
|
->shouldReceive('setInspector')
|
||||||
|
->andReturn(null)
|
||||||
|
->mock()
|
||||||
|
|
||||||
|
->shouldReceive('setException')
|
||||||
|
->andReturn(null)
|
||||||
|
->mock();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Whoops\Run::clearHandlers
|
||||||
|
*/
|
||||||
|
public function testClearHandlers()
|
||||||
|
{
|
||||||
|
$run = $this->getRunInstance();
|
||||||
|
$run->clearHandlers();
|
||||||
|
|
||||||
|
$handlers = $run->getHandlers();
|
||||||
|
|
||||||
|
$this->assertEmpty($handlers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Whoops\Run::pushHandler
|
||||||
|
*/
|
||||||
|
public function testPushHandler()
|
||||||
|
{
|
||||||
|
$run = $this->getRunInstance();
|
||||||
|
$run->clearHandlers();
|
||||||
|
|
||||||
|
$handlerOne = $this->getHandler();
|
||||||
|
$handlerTwo = $this->getHandler();
|
||||||
|
|
||||||
|
$run->pushHandler($handlerOne);
|
||||||
|
$run->pushHandler($handlerTwo);
|
||||||
|
|
||||||
|
$handlers = $run->getHandlers();
|
||||||
|
|
||||||
|
$this->assertCount(2, $handlers);
|
||||||
|
$this->assertContains($handlerOne, $handlers);
|
||||||
|
$this->assertContains($handlerTwo, $handlers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Whoops\Run::pushHandler
|
||||||
|
*/
|
||||||
|
public function testPushInvalidHandler()
|
||||||
|
{
|
||||||
|
$run = $this->getRunInstance();
|
||||||
|
|
||||||
|
$this->expectExceptionOfType('InvalidArgumentException');
|
||||||
|
|
||||||
|
$run->pushHandler('actually turnip');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Whoops\Run::pushHandler
|
||||||
|
*/
|
||||||
|
public function testPushClosureBecomesHandler()
|
||||||
|
{
|
||||||
|
$run = $this->getRunInstance();
|
||||||
|
$run->pushHandler(function () {});
|
||||||
|
$this->assertInstanceOf('Whoops\\Handler\\CallbackHandler', $run->popHandler());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Whoops\Run::popHandler
|
||||||
|
* @covers Whoops\Run::getHandlers
|
||||||
|
*/
|
||||||
|
public function testPopHandler()
|
||||||
|
{
|
||||||
|
$run = $this->getRunInstance();
|
||||||
|
|
||||||
|
$handlerOne = $this->getHandler();
|
||||||
|
$handlerTwo = $this->getHandler();
|
||||||
|
$handlerThree = $this->getHandler();
|
||||||
|
|
||||||
|
$run->pushHandler($handlerOne);
|
||||||
|
$run->pushHandler($handlerTwo);
|
||||||
|
$run->pushHandler($handlerThree);
|
||||||
|
|
||||||
|
$this->assertSame($handlerThree, $run->popHandler());
|
||||||
|
$this->assertSame($handlerTwo, $run->popHandler());
|
||||||
|
$this->assertSame($handlerOne, $run->popHandler());
|
||||||
|
|
||||||
|
// Should return null if there's nothing else in
|
||||||
|
// the stack
|
||||||
|
$this->assertNull($run->popHandler());
|
||||||
|
|
||||||
|
// Should be empty since we popped everything off
|
||||||
|
// the stack:
|
||||||
|
$this->assertEmpty($run->getHandlers());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Whoops\Run::removeFirstHandler
|
||||||
|
* @covers Whoops\Run::removeLastHandler
|
||||||
|
* @covers Whoops\Run::getHandlers
|
||||||
|
*/
|
||||||
|
public function testRemoveHandler()
|
||||||
|
{
|
||||||
|
$run = $this->getRunInstance();
|
||||||
|
|
||||||
|
$handlerOne = $this->getHandler();
|
||||||
|
$handlerTwo = $this->getHandler();
|
||||||
|
$handlerThree = $this->getHandler();
|
||||||
|
|
||||||
|
$run->pushHandler($handlerOne);
|
||||||
|
$run->pushHandler($handlerTwo);
|
||||||
|
$run->pushHandler($handlerThree);
|
||||||
|
|
||||||
|
$run->removeLastHandler();
|
||||||
|
$this->assertSame($handlerTwo, $run->getHandlers()[0]);
|
||||||
|
$run->removeFirstHandler();
|
||||||
|
$this->assertSame($handlerTwo, $run->getHandlers()[0]);
|
||||||
|
$this->assertCount(1, $run->getHandlers());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Whoops\Run::register
|
||||||
|
*/
|
||||||
|
public function testRegisterHandler()
|
||||||
|
{
|
||||||
|
// It is impossible to test the Run::register method using phpunit,
|
||||||
|
// as given how every test is always inside a giant try/catch block,
|
||||||
|
// any thrown exception will never hit a global exception handler.
|
||||||
|
// On the other hand, there is not much need in testing
|
||||||
|
// a call to a native PHP function.
|
||||||
|
$this->assertTrue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Whoops\Run::unregister
|
||||||
|
*/
|
||||||
|
public function testUnregisterHandler()
|
||||||
|
{
|
||||||
|
$run = $this->getRunInstance();
|
||||||
|
$run->register();
|
||||||
|
|
||||||
|
$handler = $this->getHandler();
|
||||||
|
$run->pushHandler($handler);
|
||||||
|
|
||||||
|
$run->unregister();
|
||||||
|
|
||||||
|
$this->expectExceptionOfType('Exception');
|
||||||
|
|
||||||
|
throw $this->getException("I'm not supposed to be caught!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Whoops\Run::pushHandler
|
||||||
|
* @covers Whoops\Run::getHandlers
|
||||||
|
*/
|
||||||
|
public function testHandlerHoldsOrder()
|
||||||
|
{
|
||||||
|
$run = $this->getRunInstance();
|
||||||
|
|
||||||
|
$handlerOne = $this->getHandler();
|
||||||
|
$handlerTwo = $this->getHandler();
|
||||||
|
$handlerThree = $this->getHandler();
|
||||||
|
$handlerFour = $this->getHandler();
|
||||||
|
|
||||||
|
$run->pushHandler($handlerOne);
|
||||||
|
$run->prependHandler($handlerTwo);
|
||||||
|
$run->appendHandler($handlerThree);
|
||||||
|
$run->appendHandler($handlerFour);
|
||||||
|
|
||||||
|
$handlers = $run->getHandlers();
|
||||||
|
|
||||||
|
$this->assertSame($handlers[0], $handlerFour);
|
||||||
|
$this->assertSame($handlers[1], $handlerThree);
|
||||||
|
$this->assertSame($handlers[2], $handlerOne);
|
||||||
|
$this->assertSame($handlers[3], $handlerTwo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo possibly split this up a bit and move
|
||||||
|
* some of this test to Handler unit tests?
|
||||||
|
* @covers Whoops\Run::handleException
|
||||||
|
*/
|
||||||
|
public function testHandlersGonnaHandle()
|
||||||
|
{
|
||||||
|
$run = $this->getRunInstance();
|
||||||
|
$exception = $this->getException();
|
||||||
|
$order = new ArrayObject();
|
||||||
|
|
||||||
|
$handlerOne = $this->getHandler();
|
||||||
|
$handlerTwo = $this->getHandler();
|
||||||
|
$handlerThree = $this->getHandler();
|
||||||
|
|
||||||
|
$handlerOne->shouldReceive('handle')
|
||||||
|
->andReturnUsing(function () use ($order) { $order[] = 1; });
|
||||||
|
$handlerTwo->shouldReceive('handle')
|
||||||
|
->andReturnUsing(function () use ($order) { $order[] = 2; });
|
||||||
|
$handlerThree->shouldReceive('handle')
|
||||||
|
->andReturnUsing(function () use ($order) { $order[] = 3; });
|
||||||
|
|
||||||
|
$run->pushHandler($handlerOne);
|
||||||
|
$run->pushHandler($handlerTwo);
|
||||||
|
$run->pushHandler($handlerThree);
|
||||||
|
|
||||||
|
// Get an exception to be handled, and verify that the handlers
|
||||||
|
// are given the handler, and in the inverse order they were
|
||||||
|
// registered.
|
||||||
|
$run->handleException($exception);
|
||||||
|
$this->assertEquals((array) $order, [3, 2, 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Whoops\Run::handleException
|
||||||
|
*/
|
||||||
|
public function testLastHandler()
|
||||||
|
{
|
||||||
|
$run = $this->getRunInstance();
|
||||||
|
|
||||||
|
$handlerOne = $this->getHandler();
|
||||||
|
$handlerTwo = $this->getHandler();
|
||||||
|
$handlerThree = $this->getHandler();
|
||||||
|
$handlerFour = $this->getHandler();
|
||||||
|
|
||||||
|
$run->pushHandler($handlerOne);
|
||||||
|
$run->prependHandler($handlerTwo);
|
||||||
|
$run->appendHandler($handlerThree);
|
||||||
|
$run->appendHandler($handlerFour);
|
||||||
|
|
||||||
|
$test = $this;
|
||||||
|
$handlerFour
|
||||||
|
->shouldReceive('handle')
|
||||||
|
->andReturnUsing(function () use ($test) {
|
||||||
|
$test->fail('$handlerFour should not be called');
|
||||||
|
});
|
||||||
|
|
||||||
|
$handlerThree
|
||||||
|
->shouldReceive('handle')
|
||||||
|
->andReturn(Handler::LAST_HANDLER);
|
||||||
|
|
||||||
|
$twoRan = false;
|
||||||
|
|
||||||
|
$handlerOne
|
||||||
|
->shouldReceive('handle')
|
||||||
|
->andReturnUsing(function () use ($test, &$twoRan) {
|
||||||
|
$test->assertTrue($twoRan);
|
||||||
|
});
|
||||||
|
|
||||||
|
$handlerTwo
|
||||||
|
->shouldReceive('handle')
|
||||||
|
->andReturnUsing(function () use (&$twoRan) {
|
||||||
|
$twoRan = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
$run->handleException($this->getException());
|
||||||
|
|
||||||
|
// Reached the end without errors
|
||||||
|
$this->assertTrue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test error suppression using @ operator.
|
||||||
|
*/
|
||||||
|
public function testErrorSuppression()
|
||||||
|
{
|
||||||
|
$run = $this->getRunInstance();
|
||||||
|
$run->register();
|
||||||
|
|
||||||
|
$handler = $this->getHandler();
|
||||||
|
$run->pushHandler($handler);
|
||||||
|
|
||||||
|
$test = $this;
|
||||||
|
$handler
|
||||||
|
->shouldReceive('handle')
|
||||||
|
->andReturnUsing(function () use ($test) {
|
||||||
|
$test->fail('$handler should not be called, error not suppressed');
|
||||||
|
});
|
||||||
|
|
||||||
|
@trigger_error("Test error suppression");
|
||||||
|
|
||||||
|
// Reached the end without errors
|
||||||
|
$this->assertTrue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testErrorCatching()
|
||||||
|
{
|
||||||
|
$run = $this->getRunInstance();
|
||||||
|
$run->register();
|
||||||
|
|
||||||
|
$handler = $this->getHandler();
|
||||||
|
$run->pushHandler($handler);
|
||||||
|
|
||||||
|
$test = $this;
|
||||||
|
$handler
|
||||||
|
->shouldReceive('handle')
|
||||||
|
->andReturnUsing(function () use ($test) {
|
||||||
|
$test->fail('$handler should not be called error should be caught');
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
trigger_error('foo', E_USER_NOTICE);
|
||||||
|
$this->fail('Should not continue after error thrown');
|
||||||
|
} catch (\ErrorException $e) {
|
||||||
|
// Do nothing
|
||||||
|
$this->assertTrue(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->fail('Should not continue here, should have been caught.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test to make sure that error_reporting is respected.
|
||||||
|
*/
|
||||||
|
public function testErrorReporting()
|
||||||
|
{
|
||||||
|
$run = $this->getRunInstance();
|
||||||
|
$run->register();
|
||||||
|
|
||||||
|
$handler = $this->getHandler();
|
||||||
|
$run->pushHandler($handler);
|
||||||
|
|
||||||
|
$test = $this;
|
||||||
|
$handler
|
||||||
|
->shouldReceive('handle')
|
||||||
|
->andReturnUsing(function () use ($test) {
|
||||||
|
$test->fail('$handler should not be called, error_reporting not respected');
|
||||||
|
});
|
||||||
|
|
||||||
|
$oldLevel = error_reporting(E_ALL ^ E_USER_NOTICE);
|
||||||
|
trigger_error("Test error reporting", E_USER_NOTICE);
|
||||||
|
error_reporting($oldLevel);
|
||||||
|
|
||||||
|
// Reached the end without errors
|
||||||
|
$this->assertTrue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Whoops\Run::silenceErrorsInPaths
|
||||||
|
*/
|
||||||
|
public function testSilenceErrorsInPaths()
|
||||||
|
{
|
||||||
|
$run = $this->getRunInstance();
|
||||||
|
$run->register();
|
||||||
|
|
||||||
|
$handler = $this->getHandler();
|
||||||
|
$run->pushHandler($handler);
|
||||||
|
|
||||||
|
$test = $this;
|
||||||
|
$handler
|
||||||
|
->shouldReceive('handle')
|
||||||
|
->andReturnUsing(function () use ($test) {
|
||||||
|
$test->fail('$handler should not be called, silenceErrorsInPaths not respected');
|
||||||
|
});
|
||||||
|
|
||||||
|
$run->silenceErrorsInPaths('@^'.preg_quote(__FILE__, '@').'$@', E_USER_NOTICE);
|
||||||
|
trigger_error('Test', E_USER_NOTICE);
|
||||||
|
$this->assertTrue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Whoops\Run::handleError
|
||||||
|
* @requires PHP < 8
|
||||||
|
*/
|
||||||
|
public function testGetSilencedError()
|
||||||
|
{
|
||||||
|
$run = $this->getRunInstance();
|
||||||
|
$run->register();
|
||||||
|
|
||||||
|
$handler = $this->getHandler();
|
||||||
|
$run->pushHandler($handler);
|
||||||
|
|
||||||
|
@strpos();
|
||||||
|
|
||||||
|
$error = error_get_last();
|
||||||
|
|
||||||
|
$this->assertTrue($error && strpos($error['message'], 'strpos()') !== false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Whoops\Run::handleError
|
||||||
|
* @see https://github.com/filp/whoops/issues/267
|
||||||
|
*/
|
||||||
|
public function testErrorWrappedInException()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$run = $this->getRunInstance();
|
||||||
|
$run->handleError(E_WARNING, 'my message', 'my file', 99);
|
||||||
|
$this->fail("missing expected exception");
|
||||||
|
} catch (\ErrorException $e) {
|
||||||
|
$this->assertSame(E_WARNING, $e->getSeverity());
|
||||||
|
$this->assertSame(E_WARNING, $e->getCode(), "For BC reasons getCode() should match getSeverity()");
|
||||||
|
$this->assertSame('my message', $e->getMessage());
|
||||||
|
$this->assertSame('my file', $e->getFile());
|
||||||
|
$this->assertSame(99, $e->getLine());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Whoops\Run::handleException
|
||||||
|
* @covers Whoops\Run::writeToOutput
|
||||||
|
*/
|
||||||
|
public function testOutputIsSent()
|
||||||
|
{
|
||||||
|
$run = $this->getRunInstance();
|
||||||
|
$run->pushHandler(function () {
|
||||||
|
echo "hello there";
|
||||||
|
});
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
$run->handleException(new RuntimeException());
|
||||||
|
$this->assertEquals("hello there", ob_get_clean());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Whoops\Run::handleException
|
||||||
|
* @covers Whoops\Run::writeToOutput
|
||||||
|
*/
|
||||||
|
public function testOutputIsNotSent()
|
||||||
|
{
|
||||||
|
$run = $this->getRunInstance();
|
||||||
|
$run->writeToOutput(false);
|
||||||
|
$run->pushHandler(function () {
|
||||||
|
echo "hello there";
|
||||||
|
});
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
$this->assertEquals("hello there", $run->handleException(new RuntimeException()));
|
||||||
|
$this->assertEquals("", ob_get_clean());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Whoops\Run::sendHttpCode
|
||||||
|
*/
|
||||||
|
public function testSendHttpCode()
|
||||||
|
{
|
||||||
|
$run = $this->getRunInstance();
|
||||||
|
$run->sendHttpCode(true);
|
||||||
|
$this->assertEquals(500, $run->sendHttpCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Whoops\Run::sendHttpCode
|
||||||
|
*/
|
||||||
|
public function testSendHttpCodeNullCode()
|
||||||
|
{
|
||||||
|
$run = $this->getRunInstance();
|
||||||
|
$this->assertEquals(false, $run->sendHttpCode(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Whoops\Run::sendHttpCode
|
||||||
|
*/
|
||||||
|
public function testSendHttpCodeWrongCode()
|
||||||
|
{
|
||||||
|
$this->expectExceptionOfType('InvalidArgumentException');
|
||||||
|
|
||||||
|
$this->getRunInstance()->sendHttpCode(1337);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Whoops\Run::sendHttpCode
|
||||||
|
*/
|
||||||
|
public function testSendExitCode()
|
||||||
|
{
|
||||||
|
$run = $this->getRunInstance();
|
||||||
|
$run->sendExitCode(42);
|
||||||
|
$this->assertEquals(42, $run->sendExitCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Whoops\Run::sendExitCode
|
||||||
|
*/
|
||||||
|
public function testSendExitCodeDefaultCode()
|
||||||
|
{
|
||||||
|
$run = $this->getRunInstance();
|
||||||
|
$this->assertEquals(1, $run->sendExitCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Whoops\Run::sendExitCode
|
||||||
|
*/
|
||||||
|
public function testSendExitCodeWrongCode()
|
||||||
|
{
|
||||||
|
$this->expectExceptionOfType('InvalidArgumentException');
|
||||||
|
|
||||||
|
$this->getRunInstance()->sendExitCode(255);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Whoops\Run::addFrameFilter
|
||||||
|
* @covers Whoops\Run::getFrameFilters
|
||||||
|
*/
|
||||||
|
public function testAddFrameFilter()
|
||||||
|
{
|
||||||
|
$run = $this->getRunInstance();
|
||||||
|
|
||||||
|
$filterCallbackOne = function(Frame $frame) {};
|
||||||
|
$filterCallbackTwo = function(Frame $frame) {};
|
||||||
|
|
||||||
|
$run
|
||||||
|
->addFrameFilter($filterCallbackOne)
|
||||||
|
->addFrameFilter($filterCallbackTwo);
|
||||||
|
|
||||||
|
$frameFilters = $run->getFrameFilters();
|
||||||
|
|
||||||
|
$this->assertCount(2, $frameFilters);
|
||||||
|
$this->assertContains($filterCallbackOne, $frameFilters);
|
||||||
|
$this->assertContains($filterCallbackTwo, $frameFilters);
|
||||||
|
$this->assertInstanceOf("Whoops\\RunInterface", $run);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers Whoops\Run::clearFrameFilters
|
||||||
|
* @covers Whoops\Run::getFrameFilters
|
||||||
|
*/
|
||||||
|
public function testClearFrameFilters()
|
||||||
|
{
|
||||||
|
$run = $this->getRunInstance();
|
||||||
|
$run->addFrameFilter(function(Frame $frame) {});
|
||||||
|
|
||||||
|
$run = $run->clearFrameFilters();
|
||||||
|
|
||||||
|
$this->assertEmpty($run->getFrameFilters());
|
||||||
|
$this->assertInstanceOf("Whoops\\RunInterface", $run);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testShutdownHandlerDoesNotRunIfUnregistered()
|
||||||
|
{
|
||||||
|
$system = m::mock('Whoops\Util\SystemFacade');
|
||||||
|
|
||||||
|
$run = new Run($system);
|
||||||
|
$run->writeToOutput(false);
|
||||||
|
$run->allowQuit(false);
|
||||||
|
|
||||||
|
$fatalError = [
|
||||||
|
'type' => E_ERROR,
|
||||||
|
'message' => 'Simulated fatal error for shutdown',
|
||||||
|
'file' => 'somefile.php',
|
||||||
|
'line' => 10,
|
||||||
|
];
|
||||||
|
$system->shouldReceive('getLastError')->andReturn($fatalError)->byDefault();
|
||||||
|
|
||||||
|
$mockHandler = m::mock('Whoops\Handler\HandlerInterface');
|
||||||
|
$mockHandler->shouldNotReceive('handle');
|
||||||
|
$mockHandler->shouldNotReceive('setRun');
|
||||||
|
$mockHandler->shouldNotReceive('setInspector');
|
||||||
|
$mockHandler->shouldNotReceive('setException');
|
||||||
|
$run->pushHandler($mockHandler);
|
||||||
|
|
||||||
|
$run->unregister();
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
$run->handleShutdown();
|
||||||
|
$output = ob_get_clean();
|
||||||
|
|
||||||
|
$this->assertEquals('', $output, "Output buffer should be empty.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
<?php
|
||||||
|
namespace Whoops\Util;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class SystemFacadeTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \Mockery\Mock
|
||||||
|
*/
|
||||||
|
public static $runtime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var SystemFacade
|
||||||
|
*/
|
||||||
|
private $facade;
|
||||||
|
|
||||||
|
public static function delegate($fn, array $args = [])
|
||||||
|
{
|
||||||
|
return self::$runtime
|
||||||
|
? call_user_func_array([self::$runtime, $fn], $args)
|
||||||
|
: call_user_func_array("\\$fn", $args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @before
|
||||||
|
*/
|
||||||
|
public function getReady()
|
||||||
|
{
|
||||||
|
self::$runtime = \Mockery::mock(['ob_start' => true]);
|
||||||
|
$this->facade = new SystemFacade();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @after
|
||||||
|
*/
|
||||||
|
public function finishUp()
|
||||||
|
{
|
||||||
|
self::$runtime = null;
|
||||||
|
\Mockery::close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_it_delegates_output_buffering_to_the_native_implementation()
|
||||||
|
{
|
||||||
|
self::$runtime->shouldReceive('ob_start')->once();
|
||||||
|
|
||||||
|
$this->facade->startOutputBuffering();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_it_delegates_cleaning_output_buffering_to_the_native_implementation()
|
||||||
|
{
|
||||||
|
self::$runtime->shouldReceive('ob_get_clean')->once();
|
||||||
|
|
||||||
|
$this->facade->cleanOutputBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_it_delegates_getting_the_current_buffer_level_to_the_native_implementation()
|
||||||
|
{
|
||||||
|
self::$runtime->shouldReceive('ob_get_level')->once();
|
||||||
|
|
||||||
|
$this->facade->getOutputBufferLevel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_it_delegates_ending_the_current_buffer_to_the_native_implementation()
|
||||||
|
{
|
||||||
|
self::$runtime->shouldReceive('ob_end_clean')->once();
|
||||||
|
|
||||||
|
$this->facade->endOutputBuffering();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_it_delegates_flushing_the_current_buffer_to_the_native_implementation()
|
||||||
|
{
|
||||||
|
self::$runtime->shouldReceive('flush')->once();
|
||||||
|
|
||||||
|
$this->facade->flushOutputBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_it_delegates_error_handling_to_the_native_implementation()
|
||||||
|
{
|
||||||
|
self::$runtime->shouldReceive('set_error_handler')->once();
|
||||||
|
|
||||||
|
$this->facade->setErrorHandler(function(){});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_it_delegates_error_handling_with_level_to_the_native_implementation()
|
||||||
|
{
|
||||||
|
self::$runtime->shouldReceive('set_error_handler')->once();
|
||||||
|
|
||||||
|
$this->facade->setErrorHandler(function(){}, E_CORE_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_it_delegates_exception_handling_to_the_native_implementation()
|
||||||
|
{
|
||||||
|
self::$runtime->shouldReceive('set_exception_handler')->once();
|
||||||
|
|
||||||
|
$this->facade->setExceptionHandler(function(){});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_it_delegates_restoring_the_exception_handler_to_the_native_implementation()
|
||||||
|
{
|
||||||
|
self::$runtime->shouldReceive('restore_exception_handler')->once();
|
||||||
|
|
||||||
|
$this->facade->restoreExceptionHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_it_delegates_restoring_the_error_handler_to_the_native_implementation()
|
||||||
|
{
|
||||||
|
self::$runtime->shouldReceive('restore_error_handler')->once();
|
||||||
|
|
||||||
|
$this->facade->restoreErrorHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_it_delegates_registering_a_shutdown_function_to_the_native_implementation()
|
||||||
|
{
|
||||||
|
self::$runtime->shouldReceive('register_shutdown_function')->once();
|
||||||
|
|
||||||
|
$this->facade->registerShutdownFunction(function(){});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_it_delegates_error_reporting_to_the_native_implementation()
|
||||||
|
{
|
||||||
|
self::$runtime->shouldReceive('error_reporting')->once()->withNoArgs();
|
||||||
|
|
||||||
|
$this->facade->getErrorReportingLevel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_it_delegates_getting_the_last_error_to_the_native_implementation()
|
||||||
|
{
|
||||||
|
self::$runtime->shouldReceive('error_get_last')->once()->withNoArgs();
|
||||||
|
|
||||||
|
$this->facade->getLastError();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_it_delegates_sending_an_http_response_code_to_the_native_implementation()
|
||||||
|
{
|
||||||
|
self::$runtime->shouldReceive('headers_sent')->once()->withNoArgs();
|
||||||
|
self::$runtime->shouldReceive('header_remove')->once()->with('location');
|
||||||
|
self::$runtime->shouldReceive('http_response_code')->once()->with(230);
|
||||||
|
|
||||||
|
$this->facade->setHttpResponseCode(230);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ob_start()
|
||||||
|
{
|
||||||
|
return SystemFacadeTest::delegate('ob_start');
|
||||||
|
}
|
||||||
|
|
||||||
|
function ob_get_clean()
|
||||||
|
{
|
||||||
|
return SystemFacadeTest::delegate('ob_get_clean');
|
||||||
|
}
|
||||||
|
|
||||||
|
function ob_get_level()
|
||||||
|
{
|
||||||
|
return SystemFacadeTest::delegate('ob_get_level');
|
||||||
|
}
|
||||||
|
|
||||||
|
function ob_end_clean()
|
||||||
|
{
|
||||||
|
return SystemFacadeTest::delegate('ob_end_clean');
|
||||||
|
}
|
||||||
|
|
||||||
|
function flush()
|
||||||
|
{
|
||||||
|
return SystemFacadeTest::delegate('flush');
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_error_handler(callable $handler, $types = 'use-php-defaults')
|
||||||
|
{
|
||||||
|
// Workaround for PHP 5.5
|
||||||
|
if ($types === 'use-php-defaults') {
|
||||||
|
$types = E_ALL | E_STRICT;
|
||||||
|
}
|
||||||
|
return SystemFacadeTest::delegate('set_error_handler', func_get_args());
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_exception_handler(callable $handler)
|
||||||
|
{
|
||||||
|
return SystemFacadeTest::delegate('set_exception_handler', func_get_args());
|
||||||
|
}
|
||||||
|
|
||||||
|
function restore_exception_handler()
|
||||||
|
{
|
||||||
|
return SystemFacadeTest::delegate('restore_exception_handler');
|
||||||
|
}
|
||||||
|
|
||||||
|
function restore_error_handler()
|
||||||
|
{
|
||||||
|
return SystemFacadeTest::delegate('restore_error_handler');
|
||||||
|
}
|
||||||
|
|
||||||
|
function register_shutdown_function()
|
||||||
|
{
|
||||||
|
return SystemFacadeTest::delegate('register_shutdown_function', func_get_args());
|
||||||
|
}
|
||||||
|
|
||||||
|
function error_reporting($level = null)
|
||||||
|
{
|
||||||
|
return SystemFacadeTest::delegate('error_reporting', func_get_args());
|
||||||
|
}
|
||||||
|
|
||||||
|
function error_get_last()
|
||||||
|
{
|
||||||
|
return SystemFacadeTest::delegate('error_get_last', func_get_args());
|
||||||
|
}
|
||||||
|
|
||||||
|
function header_remove($header = null)
|
||||||
|
{
|
||||||
|
return SystemFacadeTest::delegate('header_remove', func_get_args());
|
||||||
|
}
|
||||||
|
|
||||||
|
function headers_sent(&$filename = null, &$line = null)
|
||||||
|
{
|
||||||
|
return SystemFacadeTest::delegate('headers_sent', func_get_args());
|
||||||
|
}
|
||||||
|
|
||||||
|
function http_response_code($code = null)
|
||||||
|
{
|
||||||
|
return SystemFacadeTest::delegate('http_response_code', func_get_args());
|
||||||
|
}
|
||||||
Reference in new issue
Block a user