julienmonnerie/kirby/src/Cms/AppErrors.php
2022-12-19 14:56:05 +01:00

208 lines
4.7 KiB
PHP

<?php
namespace Kirby\Cms;
use Closure;
use Kirby\Exception\Exception;
use Kirby\Filesystem\F;
use Kirby\Http\Response;
use Kirby\Toolkit\I18n;
use Throwable;
use Whoops\Handler\CallbackHandler;
use Whoops\Handler\Handler;
use Whoops\Handler\PlainTextHandler;
use Whoops\Handler\PrettyPageHandler;
use Whoops\Run as Whoops;
/**
* PHP error handling using the Whoops library
*
* @package Kirby Cms
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
*/
trait AppErrors
{
/**
* Whoops instance cache
*
* @var \Whoops\Run
*/
protected $whoops;
/**
* Registers the PHP error handler for CLI usage
*
* @return void
*/
protected function handleCliErrors(): void
{
$this->setWhoopsHandler(new PlainTextHandler());
}
/**
* Registers the PHP error handler
* based on the environment
*
* @return void
*/
protected function handleErrors(): void
{
if ($this->environment()->cli() === true) {
$this->handleCliErrors();
return;
}
if ($this->visitor()->prefersJson() === true) {
$this->handleJsonErrors();
return;
}
$this->handleHtmlErrors();
}
/**
* Registers the PHP error handler for HTML output
*
* @return void
*/
protected function handleHtmlErrors(): void
{
$handler = null;
if ($this->option('debug') === true) {
if ($this->option('whoops', true) === true) {
$handler = new PrettyPageHandler();
$handler->setPageTitle('Kirby CMS Debugger');
$handler->setResourcesPath(dirname(__DIR__, 2) . '/assets');
$handler->addCustomCss('whoops.css');
if ($editor = $this->option('editor')) {
$handler->setEditor($editor);
}
}
} else {
$handler = new CallbackHandler(function ($exception, $inspector, $run) {
$fatal = $this->option('fatal');
if ($fatal instanceof Closure) {
echo $fatal($this, $exception);
} else {
include $this->root('kirby') . '/views/fatal.php';
}
return Handler::QUIT;
});
}
if ($handler !== null) {
$this->setWhoopsHandler($handler);
} else {
$this->unsetWhoopsHandler();
}
}
/**
* Registers the PHP error handler for JSON output
*
* @return void
*/
protected function handleJsonErrors(): void
{
$handler = new CallbackHandler(function ($exception, $inspector, $run) {
if ($exception instanceof Exception) {
$httpCode = $exception->getHttpCode();
$code = $exception->getCode();
$details = $exception->getDetails();
} elseif ($exception instanceof Throwable) {
$httpCode = 500;
$code = $exception->getCode();
$details = null;
} else {
$httpCode = 500;
$code = 500;
$details = null;
}
if ($this->option('debug') === true) {
echo Response::json([
'status' => 'error',
'exception' => get_class($exception),
'code' => $code,
'message' => $exception->getMessage(),
'details' => $details,
'file' => F::relativepath($exception->getFile(), $this->environment()->get('DOCUMENT_ROOT', '')),
'line' => $exception->getLine(),
], $httpCode);
} else {
echo Response::json([
'status' => 'error',
'code' => $code,
'details' => $details,
'message' => I18n::translate('error.unexpected'),
], $httpCode);
}
return Handler::QUIT;
});
$this->setWhoopsHandler($handler);
$this->whoops()->sendHttpCode(false);
}
/**
* Enables Whoops with the specified handler
*
* @param Callable|\Whoops\Handler\HandlerInterface $handler
* @return void
*/
protected function setWhoopsHandler($handler): void
{
$whoops = $this->whoops();
$whoops->clearHandlers();
$whoops->pushHandler($handler);
$whoops->pushHandler($this->getAdditionalWhoopsHandler());
$whoops->register(); // will only do something if not already registered
}
/**
* Whoops callback handler for additional error handling
* (`system.exception` hook and output to error log)
*/
protected function getAdditionalWhoopsHandler(): CallbackHandler
{
return new CallbackHandler(function ($exception, $inspector, $run) {
$this->trigger('system.exception', compact('exception'));
error_log($exception);
return Handler::DONE;
});
}
/**
* Clears the Whoops handlers and disables Whoops
*
* @return void
*/
protected function unsetWhoopsHandler(): void
{
$whoops = $this->whoops();
$whoops->clearHandlers();
$whoops->unregister(); // will only do something if currently registered
}
/**
* Returns the Whoops error handler instance
*
* @return \Whoops\Run
*/
protected function whoops()
{
if ($this->whoops !== null) {
return $this->whoops;
}
return $this->whoops = new Whoops();
}
}