OpenXE/classes/Modules/Api/Error/ErrorHandler.php
2021-05-21 08:49:41 +02:00

201 lines
6.0 KiB
PHP

<?php
namespace Xentral\Modules\Api\Error;
use Exception;
use PDOException;
use Xentral\Core\LegacyConfig\Exception\LegacyConfigExceptionInterface;
/**
* @see /www/api/index.php
*/
class ErrorHandler
{
/** @var array Error types that halts execution */
const THROWABLE_ERROR_TYPES = [
E_ERROR, /** @see http://www.bbminfo.com/Tutor/php_error_e_error.php */
E_PARSE, /** @see http://www.bbminfo.com/Tutor/php_error_e_parse.php */
E_CORE_ERROR, /** @see http://www.bbminfo.com/Tutor/php_error_e_core_error.php */
E_COMPILE_ERROR, /** @see http://www.bbminfo.com/Tutor/php_error_e_compile_error.php */
E_USER_ERROR, /** @see http://www.bbminfo.com/Tutor/php_error_e_user_error.php */
E_RECOVERABLE_ERROR, /** @see http://www.bbminfo.com/Tutor/php_error_e_recoverable_error.php */
];
/** @var array $errorTypeTranslations */
private $errorTypeTranslations = [
E_ERROR => 'Fatal Error',
E_PARSE => 'Parse Error',
E_CORE_ERROR => 'Core Error',
E_COMPILE_ERROR => 'Compile Error',
E_USER_ERROR => 'Fatal User Error',
E_RECOVERABLE_ERROR => 'Recoverable Error',
];
/**
* @return void
*/
public function register()
{
register_shutdown_function([$this, 'onShutdown']);
// Use own error output function
ini_set('display_errors', true);
ini_set('display_startup_errors', true);
set_error_handler([$this, 'handleError']);
set_exception_handler([$this, 'handleException']);
}
/**
* @return void
*/
public function onShutdown()
{
$error = error_get_last();
if ($error === null) {
return;
}
if ($this->isErrorTypeHaltingExecution((int)$error['type'])) {
// Try to free memory; in case of exhausted memory limit
@gc_enable();
@gc_collect_cycles();
$this->handleError((int)$error['type'], $error['message'], $error['file'], $error['line']);
}
}
/**
* @param int $code
* @param string $message
* @param string $file
* @param int $line
*
* @return bool
*/
public function handleError($code, $message, $file, $line)
{
if ($this->isErrorTypeHaltingExecution($code)) {
$content = [
'error' => [
'code' => ApiError::CODE_UNEXPECTED_ERROR,
'message' => 'Unexpected error',
'http_code' => 500,
],
];
if ($this->isDebugModeActive()) {
$errorType = $this->translateErrorType($code);
$content['debug'] = [
'error' => [
'message' => $errorType . ': ' . $message,
'file' => $file,
'line' => $line,
'code' => $code,
],
];
}
header('HTTP/1.1 500 Internal Server Error');
header('Content-Type: application/json; charset=utf-8');
echo json_encode($content);
exit; // Necessary for E_RECOVERABLE_ERROR
}
return true; // Don't execute PHP internal error handler
}
/**
* @param Exception $exception
*/
public function handleException($exception)
{
$errors = [];
if ($exception instanceof PDOException) {
if ($exception->getCode() === 'HY000') {
// "HY000: General error: 1364 Field 'xxxxx' doesn't have a default value"
if (strpos($exception->getMessage(), 'SQLSTATE[HY000]: General error: 1364') !== false) {
$errors[] = str_replace('SQLSTATE[HY000]: General error: 1364 ', '', $exception->getMessage());
}
}
// 42S22: Column not found
if ($exception->getCode() === '42S22') {
$errors[] = str_replace('SQLSTATE[42S22]: Column not found: 1054 ', '', $exception->getMessage());
}
// 1049: Unknown database
if ($exception->getCode() === 1049) {
$errors[] = str_replace('SQLSTATE[HY000] [1049] ', 'DatabaseException: ', $exception->getMessage());
}
}
if ($exception instanceof LegacyConfigExceptionInterface) {
$errors[] = $exception->getMessage();
}
$content = [
'error' => [
'code' => ApiError::CODE_UNEXPECTED_ERROR,
'message' => 'Unexpected error',
'http_code' => 500,
'errors' => $errors,
],
];
if ($this->isDebugModeActive()) {
$content['debug'] = [
'error' => [
'message' => 'Unhandled exception: ' . $exception->getMessage(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'code' => $exception->getCode(),
'trace' => $exception->getTrace(),
],
];
}
header('HTTP/1.1 500 Internal Server Error');
header('Content-Type: application/json; charset=utf-8');
echo json_encode($content);
exit;
}
/**
* @see https://secure.php.net/manual/en/errorfunc.constants.php
*
* @param int $type
*
* @return string|null
*/
private function translateErrorType($type)
{
$type = (int)$type;
if (!isset($this->errorTypeTranslations[$type])) {
return 'Unknown Error';
}
return $this->errorTypeTranslations[$type];
}
/**
* @param int $type
*
* @return bool
*/
private function isErrorTypeHaltingExecution($type)
{
return in_array((int)$type, self::THROWABLE_ERROR_TYPES, true);
}
/**
* @return bool
*/
private function isDebugModeActive()
{
return defined('DEBUG_MODE') && (int)DEBUG_MODE === 1;
}
}