ArcaniSGK507

Untitled

Mar 16th, 2025
59
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 13.56 KB | None | 0 0
  1. <?php
  2.  
  3. declare(strict_types=1);
  4.  
  5. /**
  6.  * Last Hammer Framework 2.0
  7.  * PHP Version 8.3 (Requiered).
  8.  *
  9.  * @see https://github.com/arcanisgk/LH-Framework
  10.  *
  11.  * @author    Walter Nuñez (arcanisgk/original founder) <[email protected]>
  12.  * @copyright 2017 - 2024
  13.  * @license   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
  14.  * @note      This program is distributed in the hope that it will be useful
  15.  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  16.  * or FITNESS FOR A PARTICULAR PURPOSE.
  17.  */
  18.  
  19. namespace Asset\Framework\Error;
  20.  
  21. use Closure;
  22. use JetBrains\PhpStorm\NoReturn;
  23. use Throwable;
  24. use Asset\Framework\ToolBox\DrawBoxCLI;
  25.  
  26. /**
  27.  * Class that handles capturing and displaying errors in the application.
  28.  *
  29.  * @package Asset\Helper\DevTool\Error
  30.  */
  31. class BugCatcher
  32. {
  33.     /**
  34.      * Directory separator based on the operating system.
  35.      *
  36.      * @var string
  37.      */
  38.     private const string DS = DIRECTORY_SEPARATOR;
  39.  
  40.     /**
  41.      * @var BugCatcher|null Unique instance of the BugCatcher class.
  42.      */
  43.     private static ?self $instance = null;
  44.  
  45.     /**
  46.      * Get a unique instance of BugCatcher.
  47.      *
  48.      * @return BugCatcher Unique instance of BugCatcher.
  49.      */
  50.     public static function getInstance(): self
  51.     {
  52.         if (!self::$instance instanceof self) {
  53.             self::$instance = new self();
  54.         }
  55.  
  56.         return self::$instance;
  57.     }
  58.  
  59.     /**
  60.      * Line separator used in the application.
  61.      *
  62.      * @var string
  63.      */
  64.     private string $lineSeparator;
  65.  
  66.     /**
  67.      * Determine if errors should be displayed on screen.
  68.      *
  69.      * @var bool
  70.      */
  71.     private bool $displayError;
  72.  
  73.     /**
  74.      * Constructor for the BugCatcher class. Configures error and exception handling.
  75.      */
  76.     public function __construct()
  77.     {
  78.         $this
  79.             ->setLineSeparator($this->detectLineSeparator())
  80.             ->setDisplayError($this->isDisplayErrors());
  81.         register_shutdown_function([$this, "shutdownHandler"]);
  82.         set_exception_handler([$this, "exceptionHandler"]);
  83.         set_error_handler([$this, "errorHandler"]);
  84.     }
  85.  
  86.     /**
  87.      * Get the line separator used in the application.
  88.      *
  89.      * @return string The line separator.
  90.      */
  91.     public function getLineSeparator(): string
  92.     {
  93.         return $this->lineSeparator;
  94.     }
  95.  
  96.     /**
  97.      * Set the line separator used in the application.
  98.      *
  99.      * @param string $lineSeparator The line separator.
  100.      *
  101.      * @return BugCatcher This BugCatcher instance.
  102.      */
  103.     public function setLineSeparator(string $lineSeparator): self
  104.     {
  105.         $this->lineSeparator = $lineSeparator;
  106.  
  107.         return $this;
  108.     }
  109.  
  110.     /**
  111.      * Get whether errors should be displayed on screen.
  112.      *
  113.      * @return bool True if errors should be displayed, false otherwise.
  114.      */
  115.     public function getDisplayError(): bool
  116.     {
  117.         return $this->displayError;
  118.     }
  119.  
  120.     /**
  121.      * Set whether errors should be displayed on screen.
  122.      *
  123.      * @param bool $displayError True to display errors, false otherwise.
  124.      *
  125.      * @return BugCatcher This BugCatcher instance.
  126.      */
  127.     public function setDisplayError(bool $displayError): self
  128.     {
  129.         $this->displayError = $displayError;
  130.  
  131.         return $this;
  132.     }
  133.  
  134.     /**
  135.      * Check if the application is running in a command-line environment.
  136.      *
  137.      * @return bool True if running in CLI, false otherwise.
  138.      */
  139.     private function isCLI(): bool
  140.     {
  141.         return defined('STDIN')
  142.             || php_sapi_name() === "cli"
  143.             || (stristr(PHP_SAPI, 'cgi') && getenv('TERM'))
  144.             || (empty($_SERVER['REMOTE_ADDR']) && !isset($_SERVER['HTTP_USER_AGENT']) && count($_SERVER['argv']) > 0);
  145.     }
  146.  
  147.     /**
  148.      * Detect the appropriate line separator based on the environment (CLI or browser).
  149.      *
  150.      * @return string The line separator.
  151.      */
  152.     private function detectLineSeparator(): string
  153.     {
  154.         return ($this->isCLI()) ? PHP_EOL : '<br>';
  155.     }
  156.  
  157.     /**
  158.      * Check if errors should be displayed on screen.
  159.      *
  160.      * @return bool True if errors should be displayed, false otherwise.
  161.      */
  162.     private function isDisplayErrors(): bool
  163.     {
  164.         return in_array(ini_get('display_errors'), [1, 'On', 'on']);
  165.     }
  166.  
  167.     /**
  168.      * Shutdown handler. Captures errors at the end of execution.
  169.      */
  170.     public function shutdownHandler(): void
  171.     {
  172.         $error = error_get_last();
  173.         if (!is_null($error)) {
  174.             $this->cleanOutput();
  175.             $trace = array_reverse(debug_backtrace());
  176.             array_pop($trace);
  177.             if (!strpos('Stack trace:', $error['message'])) {
  178.                 $errorClean       = explode('Stack trace:', $error['message']);
  179.                 $error['message'] = $errorClean[0];
  180.             }
  181.             $errorArray              = [
  182.                 'class'       => 'ShutdownHandler',
  183.                 'type'        => $error['type'],
  184.                 'description' => $error['message'],
  185.                 'file'        => $error['file'],
  186.                 'line'        => $error['line'],
  187.                 'trace'       => $trace,
  188.             ];
  189.             $errorArray['trace_msg'] = $this->getBacktrace($errorArray);
  190.             $this->output($errorArray);
  191.         }
  192.     }
  193.  
  194.     /**
  195.      * Exception handler. Captures and handles exceptions thrown in the application.
  196.      *
  197.      * @param Throwable $e The captured exception.
  198.      */
  199.     #[NoReturn] public function exceptionHandler(Throwable $e): void
  200.    {
  201.         $this->cleanOutput();
  202.         $errorArray              = [
  203.             'class'       => 'ExceptionHandler',
  204.             'type'        => ($e->getCode() == 0 ? 'Not Set' : $e->getCode()),
  205.             'description' => $e->getMessage(),
  206.             'file'        => $e->getFile(),
  207.             'line'        => $e->getLine(),
  208.             'trace'       => $e->getTrace(),
  209.         ];
  210.         $errorArray['trace_msg'] = $this->getBacktrace($errorArray);
  211.         $this->output($errorArray);
  212.     }
  213.  
  214.     /**
  215.      * Error handler. Captures and handles errors generated in the application.
  216.      *
  217.      * @param mixed|null $errorLevel Error level.
  218.      * @param mixed|null $errorDesc Error description.
  219.      * @param mixed|null $errorFile File where the error occurred.
  220.      * @param mixed|null $errorLine Line where the error occurred.
  221.      */
  222.     #[NoReturn] public function errorHandler(
  223.        mixed $errorLevel = null,
  224.         mixed $errorDesc = null,
  225.         mixed $errorFile = null,
  226.         mixed $errorLine = null,
  227.     ): void {
  228.         $this->cleanOutput();
  229.         $trace = array_reverse(debug_backtrace());
  230.         array_pop($trace);
  231.         $trace                   = array_reverse($trace);
  232.         $errorArray              = [
  233.             'class'       => 'ErrorHandler',
  234.             'type'        => $errorLevel,
  235.             'description' => $errorDesc,
  236.             'file'        => $errorFile,
  237.             'line'        => $errorLine,
  238.             'trace'       => $trace,
  239.         ];
  240.         $errorArray['trace_msg'] = $this->getBacktrace($errorArray);
  241.         $this->output($errorArray);
  242.     }
  243.  
  244.     /**
  245.      * Clean the output buffer if active.
  246.      */
  247.     private function cleanOutput(): void
  248.     {
  249.         if (ob_get_contents() || ob_get_length()) {
  250.             ob_end_clean();
  251.             flush();
  252.         }
  253.     }
  254.  
  255.     /**
  256.      * Generate a backtrace message from error information.
  257.      *
  258.      * @param array $errorArray Error information.
  259.      *
  260.      * @return string The backtrace message.
  261.      */
  262.     private function getBacktrace(array $errorArray): string
  263.     {
  264.         $backtraceMessage = [];
  265.  
  266.         if (!empty($errorArray['trace'])) {
  267.             foreach ($errorArray['trace'] as $track) {
  268.                 $args = '';
  269.                 if (isset($track['args']) && !empty($track['args'])) {
  270.                     $args = $this->formatArguments($track['args']);
  271.                 }
  272.  
  273.                 $route              = $this->getRouteDescription($track);
  274.                 $backtraceMessage[] = sprintf('%s%s(%s)', $route, $track['function'], $args);
  275.             }
  276.         } else {
  277.             $backtraceMessage[] = sprintf('No backtrace data in the %s.', $errorArray['class']);
  278.         }
  279.  
  280.         return implode($this->getLineSeparator(), $backtraceMessage);
  281.     }
  282.  
  283.     /**
  284.      * Format arguments for display.
  285.      *
  286.      * @param array $args Arguments array.
  287.      *
  288.      * @return string Formatted arguments.
  289.      */
  290.     private function formatArguments(array $args): string
  291.     {
  292.         $formattedArgs = [];
  293.  
  294.         foreach ($args as $arg) {
  295.             if (is_array($arg)) {
  296.                 $formattedArgs[] = 'Array';
  297.             } elseif (is_object($arg)) {
  298.                 if ($arg instanceof Closure) {
  299.                     $formattedArgs[] = 'Closure';
  300.                 } else {
  301.                     $formattedArgs[] = get_class($arg);
  302.                 }
  303.             } else {
  304.                 $formattedArgs[] = is_string($arg) ? "'".$arg."'" : (string)$arg;
  305.             }
  306.         }
  307.  
  308.         return implode(',', $formattedArgs);
  309.     }
  310.  
  311.     /**
  312.      * Get description of the route (file and line) or magic call method.
  313.      *
  314.      * @param array $track Stack trace information.
  315.      *
  316.      * @return string Route description.
  317.      */
  318.     private function getRouteDescription(array $track): string
  319.     {
  320.         if (!isset($track['file']) && !isset($track['line'])) {
  321.             return sprintf('Magic Call Method: (%s)->', $track['class']);
  322.         }
  323.  
  324.         return sprintf('%s %s calling Method: ', $track['file'], $track['line']);
  325.     }
  326.  
  327.     /**
  328.      * Generate and display the output corresponding to the error.
  329.      *
  330.      * @param array $errorArray Error information.
  331.      */
  332.     #[NoReturn] private function output(array $errorArray): void
  333.    {
  334.         $errorArray['micro_time'] = $this->toLog($errorArray);
  335.         if ($this->isCLI()) {
  336.             echo $this->getCLIOutput($errorArray);
  337.         } else {
  338.             $ouput = $this->getWebOutput($errorArray);
  339.             file_put_contents($_SERVER["DOCUMENT_ROOT"].'/error.html', $ouput);
  340.             echo $ouput;
  341.         }
  342.         $this->clearLastError();
  343.     }
  344.  
  345.  
  346.     /**
  347.      * Generate output for CLI environment.
  348.      *
  349.      * @param array $errorArray
  350.      *
  351.      * @return string The generated output.
  352.      */
  353.     private function getCLIOutput(array $errorArray): string
  354.     {
  355.         $output = '';
  356.         $nl     = $this->getLineSeparator();
  357.         if ($this->getDisplayError()) {
  358.             $output .= "Class: {$errorArray['class']}".$nl
  359.                 ."Description:".$nl."{$errorArray['description']}".$nl.$nl
  360.                 ."File: {$errorArray['file']}".$nl
  361.                 ."Line: {$errorArray['line']}".' '."Type: {$errorArray['type']}".' '."Time: {$errorArray['micro_time']}".$nl.$nl
  362.                 ."Backtrace:".$nl."{$errorArray['trace_msg']}".$nl
  363.                 ."Development by: W. Nunez";
  364.         } else {
  365.             $output .= "Micro Time: {$errorArray['micro_time']}";
  366.         }
  367.         //require_once 'DrawBoxCLI.php';
  368.         $drawBox = DrawBoxCLI::getInstance();
  369.  
  370.         return $drawBox->drawBoxes($output, 1, 1, true, 0, 2);
  371.     }
  372.  
  373.     /**
  374.      * Get the path to the error template file based on whether a handler is available.
  375.      *
  376.      * @param bool $hasHandler True if a handler is available, false otherwise.
  377.      *
  378.      * @return string The path to the error template file.
  379.      */
  380.     private function getErrorTemplatePath(bool $hasHandler): string
  381.     {
  382.         $templateFolder = __DIR__.self::DS.'template'.self::DS;
  383.         $templateFile   = $hasHandler ? 'handler_error.php' : 'no_handler_error.php';
  384.  
  385.         return $templateFolder.$templateFile;
  386.     }
  387.  
  388.     /**
  389.      * Generate output for web environment.
  390.      *
  391.      * @param array $errorArray Error information.
  392.      *
  393.      * @return string The generated output.
  394.      */
  395.     private function getWebOutput(array $errorArray): string
  396.     {
  397.         $errorSkin   = $this->getErrorTemplatePath($this->getDisplayError());
  398.         $fileContent = file_get_contents($errorArray['file']);
  399.         $lines       = explode("\n", $fileContent);
  400.  
  401.         if (isset($lines[$errorArray['line'] - 1])) {
  402.             $lines[$errorArray['line'] - 1] .= '    // error detected in this line!!!';
  403.         }
  404.  
  405.         $source = implode("\n", $lines);
  406.         $source = highlight_string($source, true);
  407.  
  408.         ob_start();
  409.         require_once $errorSkin;
  410.  
  411.         return ob_get_clean();
  412.     }
  413.  
  414.     /**
  415.      * Log the error to the log file and return the timestamp.
  416.      *
  417.      * @param array $errorArray Error information.
  418.      *
  419.      * @return int The log timestamp.
  420.      */
  421.     private function toLog(array $errorArray): int
  422.     {
  423.         $description = preg_replace("/(\r\n|\n|\r|\t|<br>)/", '', $errorArray['description']);
  424.         $microTime   = time();
  425.         $smgError    = $microTime.' '.date('Y-m-d H:i:s').' '.$description.PHP_EOL;
  426.         $logPath     = dirname(__FILE__)
  427.             .self::DS.'..'
  428.             .self::DS.'..'
  429.             .self::DS.'resource'
  430.             .self::DS.'log'
  431.             .self::DS.'error_log.log';
  432.         error_log($smgError, 3, $logPath);
  433.  
  434.         return $microTime;
  435.     }
  436.  
  437.     /**
  438.      * Clear the last recorded error and exit execution.
  439.      */
  440.     #[NoReturn] private function clearLastError(): void
  441.    {
  442.         error_clear_last();
  443.         exit();
  444.     }
  445. }
  446.  
  447. BugCatcher::getInstance();
  448. ob_start();
  449.  
Add Comment
Please, Sign In to add comment