ErrorHandlers.php 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. <?php
  2. /**
  3. * @author Giancarlo Voci
  4. * @date 25/06/2018
  5. * @time 16:14
  6. */
  7. namespace OpenMind\System;
  8. use Base;
  9. class ErrorHandlers
  10. {
  11. private const API_PATH_PREFIX = '/api/v1';
  12. private const JSON_CONTENT_TYPE = 'application/json; charset=utf-8';
  13. private const LOG_DATE_FORMAT = 'd/m/Y H:i:s';
  14. private Base $f3;
  15. /**
  16. * Initializes the error handler with the current framework instance.
  17. *
  18. * @return void
  19. * @sideEffects Reads the framework singleton.
  20. */
  21. public function __construct()
  22. {
  23. $this->f3 = Base::instance();
  24. }
  25. /**
  26. * Renders an application error in the response format for the request type.
  27. *
  28. * @return null Always returns null for PHP/F3 requests after writing JSON.
  29. * @sideEffects Clears output buffers, writes a log entry, and renders or exits.
  30. */
  31. public function Handler()
  32. {
  33. $error = $this->f3->get('ERROR');
  34. $errorValues = [$error['code'], $error['status'], $error['text']];
  35. while (ob_get_level() > 0) {
  36. ob_end_clean();
  37. }
  38. $this->writeLog(implode(' - ', $errorValues));
  39. if ($this->f3->get('CLI')) {
  40. print_r($errorValues);
  41. exit(1);
  42. }
  43. if ($this->isApiRequest()) {
  44. $this->f3->status((int) $error['code']);
  45. }
  46. header('Content-Type: ' . self::JSON_CONTENT_TYPE);
  47. echo json_encode(['error' => $error['text']], JSON_THROW_ON_ERROR);
  48. return null;
  49. }
  50. /**
  51. * Determine whether the current request belongs to the versioned API.
  52. *
  53. * @return bool True when the request path is rooted at /api/v1.
  54. * @sideEffects Reads the current request URI from the framework.
  55. */
  56. private function isApiRequest(): bool
  57. {
  58. $uri = $this->f3->get('URI');
  59. if (!is_string($uri)) {
  60. return false;
  61. }
  62. $path = parse_url($uri, PHP_URL_PATH);
  63. $basePath = $this->f3->get('BASE');
  64. if (is_string($path) && is_string($basePath)) {
  65. $basePath = rtrim($basePath, '/');
  66. if ($basePath !== '' && $basePath !== '/'
  67. && ($path === $basePath || str_starts_with($path, $basePath . '/'))) {
  68. $path = substr($path, strlen($basePath)) ?: '/';
  69. }
  70. }
  71. return is_string($path)
  72. && ($path === self::API_PATH_PREFIX
  73. || str_starts_with($path, self::API_PATH_PREFIX . '/'));
  74. }
  75. /**
  76. * Writes an error message with the relevant caller context.
  77. *
  78. * @param string $message Error message.
  79. * @param int $traceIndex Caller index in the debug trace.
  80. * @return void
  81. * @sideEffects Writes to the application log.
  82. */
  83. private function writeLog(string $message, int $traceIndex = 1): void
  84. {
  85. $trace = debug_backtrace();
  86. $context = $trace[$traceIndex] ?? [];
  87. $prefix = ($context['line'] ?? '') . ':'
  88. . ($context['class'] ?? '') . ':'
  89. . ($context['function'] ?? '') . ':';
  90. $sanitizedMessage = str_replace(["\r", "\n"], '', $message);
  91. OMUtils::getInstance()->getLogger()->write(
  92. "{$prefix} {$sanitizedMessage}",
  93. self::LOG_DATE_FORMAT,
  94. );
  95. }
  96. }