From a303f6e40499fbf44e936513ac51ece42332a1a5 Mon Sep 17 00:00:00 2001 From: brenno-duarte Date: Wed, 1 May 2024 09:36:03 -0300 Subject: [PATCH] Added log support --- .gitignore | 5 +- CHANGELOG.md | 21 + README.md | 36 +- composer.json | 6 +- config.example.yaml | 4 + functions.php | 28 +- src/Console/CliMessage.php | 272 +++++++++ src/Console/MessageTrait.php | 308 ---------- src/Debug.php | 23 +- src/ModernPHPException.php | 47 +- src/Resources/{Dump.php => BrowserDump.php} | 26 +- src/Resources/CliDump.php | 631 ++++++++++++++++++++ src/Trait/RenderTrait.php | 23 +- src/View/templates/index.php | 4 + src/View/templates/info-logs.php | 29 + src/log_files/.gitkeep | 0 16 files changed, 1103 insertions(+), 360 deletions(-) create mode 100644 src/Console/CliMessage.php delete mode 100644 src/Console/MessageTrait.php rename src/Resources/{Dump.php => BrowserDump.php} (96%) create mode 100644 src/Resources/CliDump.php create mode 100644 src/View/templates/info-logs.php delete mode 100644 src/log_files/.gitkeep diff --git a/.gitignore b/.gitignore index 8221f07..c6ee6f5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ vendor/ composer.lock indexTest.php -debug-test.php \ No newline at end of file +debug.php +debug-test.php +tests.php +ModernPHPExceptionLogs.log \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c5ca6ed..cdfff36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Released Notes +## v3.2.0 - (2024-05-01) + +### Added + +- Added logs in `shutdown` method + +### Changed + +- Changed `var_dump_debug` and `dump_die` to accept multiple variables + +### Fixed + +- Fixed special chars in `errorHandler` + +### Removed + +- Removed `codedungeon/php-cli-colors` and `ghostff/dump7` packages +- Removed `MessageTrait` trait + +-------------------------------------------------------------------------- + ## v3.1.2 - (2024-03-16) ### Fixed diff --git a/README.md b/README.md index e12bf41..ab526e6 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,15 @@ error_message: Something wrong! enable_cdn_assets: false ``` +**Enabling Log file** + +```yaml +enable_logs: false + +# Default: sys_get_temp_dir() . "/ModernPHPExceptionLogs/ModernPHPExceptionLogs.log" +dir_logs: C:\wamp64\www\modern-php-exception\ +``` + ## Enable occurrences If you want to have a history of all exceptions and errors that your application displays, you can enable the occurrences using the `enableOccurrences` method: @@ -168,7 +177,32 @@ echo var_dump_buffer() var_dump_debug() ``` -- Dump PHP value and die script +In terminal, you can simple hide or show some object attribute using a Doc block flag: + +| | | +|-------------------------------|---------------------------------------------------| +| `@dumpignore-inheritance` | Hides inherited class properties. | +| `@dumpignore-inherited-class` | Hides the class name from inherited properties. | +| `@dumpignore-private` | Show all properties except the **private** ones. | +| `@dumpignore-protected` | Show all properties except the **protected** ones.| +| `@dumpignore-public` | Show all properties except the **public** ones. | +| `@dumpignore` | Hide the property the Doc comment belongs to. | + +```php +/** +* @dumpignore-inheritance +* @dumpignore-inherited-class +* @dumpignore-private +* @dumpignore-public +* @dumpignore-public +*/ +Class Foo extends Bar { + /** @dumpignore */ + private ?BigObject $foo = null; +} +``` + +- Dump PHP value and die script. This function use `var_dump_debug`. ```php dump_die() diff --git a/composer.json b/composer.json index 7d51f83..be464b0 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "brenno-duarte/modern-php-exception", "description": "PHP errors in a modern way", "license": "MIT", - "version": "3.1.2", + "version": "3.2.0", "keywords": [ "exception", "throwable", @@ -16,9 +16,7 @@ "php": "^8.3", "ext-mbstring": "*", "ext-pdo": "*", - "codedungeon/php-cli-colors": "1.*", - "symfony/yaml": "^7.0", - "ghostff/dump7": "^2.0" + "symfony/yaml": "^7.0" }, "autoload": { "psr-4": { diff --git a/config.example.yaml b/config.example.yaml index 29cb65f..79d3881 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -8,6 +8,10 @@ error_message: # Use `false` only if you have no internet connection enable_cdn_assets: true +# Enable logs register +enable_logs: false +dir_logs: + # Database for Occurrences db_drive: mysql db_host: localhost diff --git a/functions.php b/functions.php index c546897..2c643e9 100644 --- a/functions.php +++ b/functions.php @@ -1,7 +1,8 @@ dump($value); + } } } @@ -62,9 +70,9 @@ function var_dump_debug(mixed $value): void * * @return void */ -function dump_die(mixed $value): void +function dump_die(...$values): void { - var_dump_debug($value); + var_dump_debug($values); exit; } diff --git a/src/Console/CliMessage.php b/src/Console/CliMessage.php new file mode 100644 index 0000000..67cedc9 --- /dev/null +++ b/src/Console/CliMessage.php @@ -0,0 +1,272 @@ +generateColors(); - - self::$message = $message; - - if ($space == true) { - self::$message = " " . self::$color_success . self::$message . self::$color_reset; - } else { - self::$message = self::$color_success . self::$message . self::$color_reset; - } - - return $this; - } - - /** - * @param string $message - * @param bool $space - * - * @return self - */ - public function info(string $message, bool $space = false): self - { - $this->generateColors(); - - self::$message = $message; - - if ($space == true) { - self::$message = " " . self::$color_info . self::$message . self::$color_reset; - } else { - self::$message = self::$color_info . self::$message . self::$color_reset; - } - - return $this; - } - - /** - * @param string $message - * @param bool $space - * - * @return self - */ - public function warning(string $message, bool $space = false): self - { - $this->generateColors(); - - self::$message = $message; - - if ($space == true) { - self::$message = " " . self::$color_warning . self::$message . self::$color_reset; - } else { - self::$message = self::$color_warning . self::$message . self::$color_reset; - } - - return $this; - } - - /** - * @param string $message - * @param bool $space - * - * @return self - */ - public function error(string $message, bool $space = false): self - { - $this->generateColors(); - - self::$message = $message; - - if ($space == true) { - self::$message = " " . self::$color_error . self::$message . self::$color_reset; - } else { - self::$message = self::$color_error . self::$message . self::$color_reset; - } - - return $this; - } - - /** - * @param string $message - * @param bool $space - * - * @return self - */ - public function errorLine(string $message, bool $space = false): self - { - $this->generateColors(); - - self::$message = $message; - - if ($space == true) { - self::$message = " " . self::$color_error_line . self::$message . self::$color_reset; - } else { - self::$message = self::$color_error_line . self::$message . self::$color_reset; - } - - return $this; - } - - /** - * @param string $message - * @param bool $space - * - * @return self - */ - public function line(string $message, bool $space = false): self - { - $this->generateColors(); - - self::$message = $message; - - if ($space == true) { - self::$message = " " . self::$color_line . self::$message . self::$color_reset; - } else { - self::$message = self::$color_line . self::$message . self::$color_reset; - } - - return $this; - } - - /** - * @param string $message - * @param bool $space - * - * @return self - */ - public function gray(string $message, bool $space = false): self - { - $this->generateColors(); - - self::$message = $message; - - if ($space == true) { - self::$message = " " . self::$color_gray . self::$message . self::$color_reset; - } else { - self::$message = self::$color_gray . self::$message . self::$color_reset; - } - - return $this; - } - - /** - * @return self - */ - public function print(): self - { - echo self::$message; - - return $this; - } - - /** - * @param bool $repeat - * - * @return self - */ - public function break($repeat = false): self - { - if ($repeat == true) { - echo PHP_EOL . PHP_EOL; - } else { - echo PHP_EOL; - } - - return $this; - } - - /** - * @return void - */ - public function exit(): void - { - exit; - } - - /** - * @return self - */ - private function generateColors(): self - { - if ($this->colorIsSupported() || $this->are256ColorsSupported()) { - self::$color_reset = Color::RESET; - self::$color_success = Color::light_green(); - self::$color_info = Color::cyan(); - self::$color_warning = Color::light_yellow(); - self::$color_error = Color::bg_light_red(); - self::$color_error_line = Color::light_red(); - self::$color_line = Color::white(); - self::$color_gray = Color::gray(); - } - - return $this; - } - - /** - * @return mixed - */ - public function colorIsSupported(): mixed - { - if (DIRECTORY_SEPARATOR === '\\') { - if (function_exists('sapi_windows_vt100_support') && @sapi_windows_vt100_support(STDOUT)) { - return true; - } - - if (getenv('ANSICON') !== false || getenv('ConEmuANSI') === 'ON') { - return true; - } - - return false; - } else { - return function_exists('posix_isatty') && @posix_isatty(STDOUT); - } - } - - /** - * @return mixed - */ - public function are256ColorsSupported(): mixed - { - if (DIRECTORY_SEPARATOR === '\\') { - return function_exists('sapi_windows_vt100_support') && @sapi_windows_vt100_support(STDOUT); - } - - return str_starts_with(getenv('TERM'), '256color'); - } -} diff --git a/src/Debug.php b/src/Debug.php index cda67fc..a946e44 100644 --- a/src/Debug.php +++ b/src/Debug.php @@ -7,7 +7,16 @@ class Debug /** * @var string */ - private static string $log_folder = DIRECTORY_SEPARATOR . "log_files" . DIRECTORY_SEPARATOR; + private static string $log_folder = ""; + + public static function dirLogger(string $dir_log): void + { + self::$log_folder = $dir_log; + + if (!is_dir(self::$log_folder)) { + mkdir(self::$log_folder); + } + } /** * Add a log in a file @@ -21,6 +30,10 @@ class Debug */ public static function log(string $message, string $log_file, ?string $file = null, ?string $line = null): bool { + if (self::$log_folder == "") { + self::dirLogger(sys_get_temp_dir() . DIRECTORY_SEPARATOR . "ModernPHPExceptionLogs" . DIRECTORY_SEPARATOR); + } + $message = "[" . date('Y-m-d H:i:s') . "] " . $message; if (!is_null($file)) { @@ -31,7 +44,11 @@ public static function log(string $message, string $log_file, ?string $file = nu $message .= " (" . $line . ")"; } - $res = file_put_contents(__DIR__ . self::$log_folder . $log_file . ".log", $message . "\n", FILE_APPEND); + if (!is_dir(self::$log_folder)) { + throw new \Exception("Directory " . self::$log_folder . " not exists"); + } + + $res = file_put_contents(self::$log_folder . $log_file . ".log", $message . "\n", FILE_APPEND); if (is_int($res)) { return true; @@ -49,7 +66,7 @@ public static function log(string $message, string $log_file, ?string $file = nu */ public static function get(string $log_file): string { - $file = __DIR__ . self::$log_folder . $log_file . ".log"; + $file = self::$log_folder . $log_file . ".log"; clearstatcache(true, $file); if (!file_exists($file)) { diff --git a/src/ModernPHPException.php b/src/ModernPHPException.php index 69ce30f..8f541ca 100644 --- a/src/ModernPHPException.php +++ b/src/ModernPHPException.php @@ -15,7 +15,7 @@ class ModernPHPException use HandlerAssetsTrait; use RenderTrait; - public const VERSION = "3.1.2"; + public const VERSION = "3.2.0"; /** * @var Bench @@ -103,6 +103,11 @@ class ModernPHPException */ protected static string $path_to_config_file = ""; + /** + * @var array + */ + private static array $config_yaml = []; + /** * Construct * @@ -127,16 +132,16 @@ public function __construct( private function setConfigFile(string $config_file): void { if (file_exists($config_file)) { - $config = Yaml::parseFile($config_file); + self::$config_yaml = Yaml::parseFile($config_file); self::$path_to_config_file = $config_file; } - if (isset($config)) { - $this->message_production = $config['error_message'] ?? ""; - $this->config['title'] = $config['title'] ?? ""; - $this->config['dark_mode'] = filter_var($config['dark_mode'], FILTER_VALIDATE_BOOLEAN); - $this->config['production_mode'] = filter_var($config['production_mode'], FILTER_VALIDATE_BOOLEAN); - $this->config['enable_cdn_assets'] = filter_var($config['enable_cdn_assets'], FILTER_VALIDATE_BOOLEAN); + if (isset(self::$config_yaml)) { + $this->message_production = self::$config_yaml['error_message'] ?? ""; + $this->config['title'] = self::$config_yaml['title'] ?? ""; + $this->config['dark_mode'] = filter_var(self::$config_yaml['dark_mode'], FILTER_VALIDATE_BOOLEAN); + $this->config['production_mode'] = filter_var(self::$config_yaml['production_mode'], FILTER_VALIDATE_BOOLEAN); + $this->config['enable_cdn_assets'] = filter_var(self::$config_yaml['enable_cdn_assets'], FILTER_VALIDATE_BOOLEAN); } } @@ -149,10 +154,34 @@ public function start(): ModernPHPException { set_error_handler([$this, 'errorHandler']); set_exception_handler([$this, 'exceptionHandler']); + register_shutdown_function([$this, 'shutdown']); return $this; } + /** + * @return void + */ + private function shutdown(): void + { + if (isset(self::$config_yaml)) { + if (isset(self::$config_yaml['enable_logs']) && self::$config_yaml['enable_logs'] == true) { + if (isset(self::$config_yaml['dir_logs']) && self::$config_yaml['dir_logs'] != "") { + Debug::dirLogger(self::$config_yaml['dir_logs']); + } + + if (!empty($this->info_error_exception)) { + Debug::log( + $this->info_error_exception['message'], + 'ModernPHPExceptionLogs', + $this->info_error_exception['file'], + $this->info_error_exception['line'] + ); + } + } + } + } + /** * @param int $code * @@ -246,6 +275,8 @@ protected function setTitle(string $title) */ public function errorHandler(int $code, string $message, string $file, int $line): void { + $message = htmlspecialchars($message); + $this->info_error_exception = [ 'message' => ($message ?? ''), 'code' => ($code ?? ''), diff --git a/src/Resources/Dump.php b/src/Resources/BrowserDump.php similarity index 96% rename from src/Resources/Dump.php rename to src/Resources/BrowserDump.php index e879d13..cb880ba 100644 --- a/src/Resources/Dump.php +++ b/src/Resources/BrowserDump.php @@ -1,8 +1,10 @@ writeRow(''.self::DISPLAY_MAX_CHILDREN.''); break; } - if (substr($key, 0, 3) === self::SEP_OBJECT) { + + if (substr((string)$key, 0, 3) === self::SEP_OBJECT) { $expKey = explode(self::SEP_OBJECT, $key); $result .= $this->writeRow(''.$expKey[1].' \''.$expKey[2].'\' => '.$this->dump($value, false)); } else { $result .= $this->writeRow((is_numeric($key) ? $key : '\''.$key.'\'').' => '.$this->dump($value, false)); } + $children++; } diff --git a/src/Resources/CliDump.php b/src/Resources/CliDump.php new file mode 100644 index 0000000..078bee4 --- /dev/null +++ b/src/Resources/CliDump.php @@ -0,0 +1,631 @@ + ['0000FF', 'blue'], + 'integer' => ['1BAABB', 'light_green'], + 'double' => ['9C6E25', 'cyan'], + 'boolean' => ['bb02ff', 'purple'], + 'keyword' => ['bb02ff', 'purple'], + 'null' => ['6789f8', 'white'], + 'type' => ['AAAAAA', 'dark_gray'], + 'size' => ['5BA415', 'green'], + 'recursion' => ['F00000', 'red'], + 'resource' => ['F00000', 'red'], + + 'array' => ['000000', 'white'], + 'multi_array_key' => ['59829e', 'yellow'], + 'single_array_key' => ['f07b06', 'light_yellow'], + 'multi_array_arrow' => ['e103c4', 'red'], + 'single_array_arrow' => ['f00000', 'red'], + + 'object' => ['000000', 'white'], + 'property_visibility' => ['741515', 'light_red'], + 'property_name' => ['987a00', 'light_cyan'], + 'property_arrow' => ['f00000', 'red'], + ]; + + /** + * Foreground colors map + * + * @var array + */ + private array $foregrounds = [ + 'none' => null, + 'black' => 30, + 'red' => 31, + 'green' => 32, + 'yellow' => 33, + 'blue' => 34, + 'purple' => 35, + 'cyan' => 36, + 'light_gray' => 37, + 'dark_gray' => 90, + 'light_red' => 91, + 'light_green' => 92, + 'light_yellow' => 93, + 'light_blue' => 94, + 'light_magenta' => 95, + 'light_cyan' => 96, + 'white' => 97, + ]; + + /** + * Background colors map + * + * @var array + */ + private array $backgrounds = [ + 'none' => null, + 'black' => 40, + 'red' => 41, + 'green' => 42, + 'yellow' => 43, + 'blue' => 44, + 'purple' => 45, + 'cyan' => 46, + 'light_gray' => 47, + 'dark_gray' => 100, + 'light_red' => 101, + 'light_green' => 102, + 'light_yellow' => 103, + 'light_blue' => 104, + 'light_magenta' => 105, + 'light_cyan' => 106, + 'white' => 107, + ]; + + /** + * Styles map + * + * @var array + */ + private array $styles = [ + 'none' => null, + 'bold' => 1, + 'faint' => 2, + 'italic' => 3, + 'underline' => 4, + 'blink' => 5, + 'negative' => 7, + ]; + + /** + * Set backtrace offset. + * + * @param int $offset + */ + public static function setTraceOffset(int $offset) + { + self::$trace_offset = $offset; + } + + /** + * Dump constructor. + */ + public function __construct(...$args) + { + if (substr(PHP_SAPI, 0, 3) == 'cli') { + $this->isCli = true; + $this->isPosix = $this->isPosix(); + } + + $this->colors = self::$changes + $this->colors; + $this->output($this->evaluate($args)); + } + + /** + * Force debug to use posix, (For window users who are using tools like http://cmder.net/) + */ + public static function safe(...$args) + { + self::$safe = true; + new self(...$args); + } + + /** + * Updates color properties value. + * + * @param string $name + * @param array $value + */ + public static function set(string $name, array $value) + { + self::$changes[$name] = $value; + } + + + /** + * Assert code nesting doesn't surpass specified limit. + * + * @return bool + */ + public function aboveNestLevel(): bool + { + return (count(debug_backtrace()) > $this->nest_level); + } + + /** + * Check if a resource is an interactive terminal + * + * @return bool + */ + private function isPosix(): bool + { + if (self::$safe) { + return false; + } + + // disable posix errors about unknown resource types + if (function_exists('posix_isatty')) { + set_error_handler(function () { + }); + $isPosix = posix_isatty(STDIN); + restore_error_handler(); + + return $isPosix; + } + + return true; + } + + /** + * Format string using ANSI escape sequences + * + * @param string $string + * @param string $format defaults to 'none|none|none' + * + * @return string + */ + private function format(string $string, string $format = null): string + { + // format only for POSIX + if (!$format || !$this->isPosix) { + return $string; + } + + $formats = $format ? explode('|', $format) : []; + + $code = array_filter([ + $this->backgrounds[$formats[1] ?? null] ?? null, + $this->styles[$formats[2] ?? null] ?? null, + $this->foregrounds[$formats[0] ?? null] ?? null, + ]); + + $code = implode(';', $code); + + return "\033[{$code}m{$string}\033[0m"; + } + + /** + * Writes dump to console. + * + * @param $message + */ + public function write(string $message) + { + echo $this->format($message); + } + + /** + * Outputs formatted dump files. + * + * @param string $data + */ + private function output(string $data) + { + # Gets line + $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + foreach ($bt as $key => $value) { + if ($value['file'] != __FILE__) { + unset($bt[$key]); + } else { + $bt = $bt[((int)$key) + self::$trace_offset]; + break; + } + } + + + if (isset($bt['file'])) { + $file = "{$bt['file']}(line:{$bt['line']})"; + } else if ($bt['class']) { + $file = "{$bt['class']}::{$bt['function']}()"; + } else { + $file = 'Unknown'; + } + + if ($this->isCli) { + $this->write("{$file}\n{$data}"); + } else { + echo "at: {$file}
{$data}
"; + } + } + + /** + * Sets string color based on sapi. + * + * @param $value + * @param string $name + * + * @return string + */ + private function color($value, string $name): string + { + if ($this->isCli) { + return $this->format($value, $this->colors[$name][1]); + } + + if ($name == 'type') { + return "colors[$name][0]}\">{$value}"; + } elseif ($name == 'array' || $name == 'object') { + $value = preg_replace('/(\[|\]|array|object)/', '$0', $value); + } + + return "colors[$name][0]}\">{$value}"; + } + + /** + * Formats the data type. + * + * @param string $type + * @param string $before + * + * @return string + */ + private function type(string $type, string $before = ''): string + { + return "{$before}{$this->color($type, 'type')}"; + } + + /** + * Move cursor to next line. + * + * @return string + */ + private function breakLine(): string + { + return $this->isCli ? PHP_EOL : '
'; + } + + /** + * Indents line content. + * + * @param int $pad + * + * @return string + */ + private function indent(int $pad): string + { + return str_repeat($this->isCli ? ' ' : ' ', $pad); + } + + /** + * Adds padding to the line. + * + * @param int $size + * + * @return string + */ + private function pad(int $size): string + { + return str_repeat($this->isCli ? ' ' : ' ', $size < 0 ? 0 : $size); + } + + /** + * Formats array index. + * + * @param $key + * @param bool $parent + * + * @return string + */ + private function arrayIndex(string $key, bool $parent = false): string + { + $key = str_replace(['<', ' '], ['<', ' '], $key); + + return $parent + ? "{$this->color("'{$key}'", 'single_array_key')} {$this->color('=>', 'single_array_arrow')} " + : "{$this->color("'{$key}'", 'multi_array_key')} {$this->color('=', 'multi_array_arrow')} "; + } + + /** + * Formats array elements. + * + * @param array $array + * @param bool $obj_call + * + * @return string + */ + private function formatArray(array $array, bool $obj_call): string + { + $tmp = ''; + $this->indent += $this->pad_size; + $break_line = $this->breakLine(); + $indent = $this->indent($this->indent); + + foreach ($array as $key => $arr) { + if (is_array($arr)) { + $tmp .= "{$break_line}{$indent}{$this->arrayIndex((string)$key)} {$this->color('(size=' . count($arr) . ')', 'size')}"; + $new = $this->formatArray($arr, $obj_call); + $tmp .= ($new != '') ? " {{$new}{$indent}}" : ' {}'; + } else { + $tmp .= "{$break_line}{$indent}{$this->arrayIndex((string)$key, true)}{$this->evaluate([$arr], true)}"; + } + } + + $this->indent -= $this->pad_size; + + if ($tmp != '') { + $tmp .= $break_line; + if ($obj_call) { + $tmp .= $this->indent($this->indent); + } + } + + return $tmp; + } + + /** + * Gets the id of an object. (DIRTY) + * + * @param $object + * + * @return string + */ + private function refcount($object): string + { + ob_start(); + debug_zval_dump($object); + + if (preg_match('/object\(.*?\)#(\d+)\s+\(/', ob_get_clean(), $match)) { + return $match[1]; + } + + return ''; + } + + /** + * Formats object elements. + * + * @param $object + * + * @return string + */ + private function formatObject($object): string + { + if ($this->aboveNestLevel()) { + return $this->color('...', 'recursion'); + } + + $reflection = new \ReflectionObject($object); + $class_name = $reflection->getName(); + $properties = []; + $tmp = ''; + $inherited = []; + $max_indent = 0; + $comments = ''; + + while ($reflection) { + $tmp_class_name = $reflection->getName(); + $max_indent = max($max_indent, strlen($tmp_class_name)); + $comments .= $reflection->getDocComment() ?: ''; + + foreach ($reflection->getProperties() as $prop) { + $prop_name = $prop->getName(); + $properties[$prop_name] = $prop; + $inherited[$prop_name] = $tmp_class_name == $class_name ? null : $tmp_class_name; + } + + if (str_contains($comments, '@dumpignore-inheritance')) { + break; + } + + $reflection = $reflection->getParentClass(); + } + + $indent = $this->indent($this->indent += $this->pad_size); + $private_color = $this->color('private', 'property_visibility'); + $protected_color = $this->color('protected', 'property_visibility'); + $public_color = $this->color('public', 'property_visibility'); + $property_color = $this->color(':', 'property_arrow'); + $arrow_color = $this->color('=>', 'property_arrow'); + $string_pad_2 = $this->pad(2); + $string_pad_3 = $this->pad(3); + $line_break = $this->breakLine(); + $hide_private = str_contains($comments, '@dumpignore-private'); + $hide_protected = str_contains($comments, '@dumpignore-protected'); + $hide_public = str_contains($comments, '@dumpignore-public'); + $hide_in_class = str_contains($comments, '@dumpignore-inherited-class'); + + foreach ($properties as $name => $prop) { + $prop_comment = $prop->getDocComment(); + if ($prop_comment && (str_contains($prop_comment, '@dumpignore'))) { + continue; + } + + $from = ''; + if (!$hide_in_class && isset($inherited[$name])) { + $name = $inherited[$name]; + $from = $this->color("[{$name}]", 'property_arrow'); + $from .= $this->indent($max_indent - strlen($name)); + } + + if ($prop->isPrivate()) { + if ($hide_private) { + continue; + } + + $tmp .= "{$line_break}{$indent}{$private_color}{$string_pad_2} {$property_color} "; + } elseif ($prop->isProtected()) { + if ($hide_protected) { + continue; + } + + $tmp .= "{$line_break}{$indent}{$protected_color} {$property_color} "; + } elseif ($prop->isPublic()) { + if ($hide_public) { + continue; + } + + $tmp .= "{$line_break}{$indent}{$public_color}{$string_pad_3} {$property_color} "; + } + + $prop->setAccessible(true); + + if (version_compare(PHP_VERSION, '7.4.0') >= 0) { + $value = $prop->isInitialized($object) ? $this->getValue($prop, $object, $class_name) : $this->type( + 'uninitialized' + ); + } else { + $value = $this->getValue($prop, $object, $class_name); + } + + $tmp .= "{$from} {$this->color("'{$prop->getName()}'", 'property_name')} {$arrow_color} {$value}"; + } + + if ($tmp != '') { + $tmp .= $this->breakLine(); + } + + $this->indent -= $this->pad_size; + $tmp .= ($tmp != '') ? $this->indent($this->indent) : ''; + + $tmp = str_replace([':name', ':id', ':content'], [ + $class_name, + $this->color("#{$this->refcount($object)}", 'size'), + $tmp, + ], $this->color('object (:name) [:id] [:content]', 'object')); + + return $tmp; + } + + /** + * Formats object property values. + * + * @param \ReflectionProperty $property + * @param $object + * @param string $class_name + * + * @return string + */ + private function getValue(\ReflectionProperty $property, $object, string $class_name): string + { + $value = $property->getValue($object); + # Prevent infinite loop caused by nested object property. e.g. when an object property is pointing to the same + # object. + if (is_object($value) && $value instanceof $object && $value == $object) { + return "{$this->type($class_name)} {$this->color('::self', 'keyword')}"; + } + + return $this->evaluate([$value], true, true); + } + + /** + * Couples all formats. + * + * @param array $args + * @param bool $called + * @param bool $from_obj + * + * @return string + */ + private function evaluate(array $args, bool $called = false, bool $from_obj = false): string + { + $tmp = null; + $null_color = $this->color('null', 'null'); + + foreach ($args as $each) { + $type = gettype($each); + switch ($type) { + case 'string': + if (!$this->isCli) { + $each = nl2br(str_replace(['<', ' '], ['<', ' '], $each)); + } + + $tmp .= "{$this->type("{$type}:" . strlen($each))} {$this->color("'{$each}'",$type)}"; + break; + case 'integer': + case 'double': + $tmp .= "{$this->type($type)} {$this->color((string)$each,$type)}"; + break; + case 'NULL': + $tmp .= "{$this->type($type)} {$null_color}"; + break; + case 'boolean': + $tmp .= "{$this->type($type)} {$this->color($each ? 'true' : 'false',$type)}"; + break; + case 'array': + $tmp .= str_replace([':size', ':content'], [ + $this->color('(size=' . count($each) . ')', 'size'), + $this->formatArray($each, $from_obj), + ], $this->color('array :size [:content]', $type)); + break; + case 'object': + $tmp .= $this->formatObject($each); + break; + case 'resource': + $resource_type = get_resource_type($each); + $resource_id = (int)$each; + $tmp .= $this->color( + "Resource[{$this->color("#{$resource_id}", 'integer')}]({$this->color($resource_type,$type)}) ", + 'object' + ); + break; + } + + if (!$called) { + $tmp .= $this->breakLine(); + } + } + + return $tmp; + } +} diff --git a/src/Trait/RenderTrait.php b/src/Trait/RenderTrait.php index f14b863..50e6277 100644 --- a/src/Trait/RenderTrait.php +++ b/src/Trait/RenderTrait.php @@ -2,14 +2,13 @@ namespace ModernPHPException\Trait; -use ModernPHPException\Console\MessageTrait; +use ModernPHPException\Console\CliMessage; use ModernPHPException\Occurrences; use ModernPHPException\Solution; use ModernPHPException\Resources\{CpuUsage, HtmlTag, MemoryUsage}; trait RenderTrait { - use MessageTrait; use HelpersTrait; use HandlerAssetsTrait; @@ -188,29 +187,29 @@ private function renderCli(): void echo PHP_EOL; if (isset($this->info_error_exception['type_exception'])) { - $this->error($this->info_error_exception['type_exception'])->print(); + CliMessage::error($this->info_error_exception['type_exception'])->print(); } else { - $this->error($this->getError())->print(); + CliMessage::error($this->getError())->print(); } - $this->line(" : " . $this->info_error_exception['message'])->print()->break(true); + CliMessage::line(" : " . $this->info_error_exception['message'])->print()->break(true); $this->renderSolutionCli(); echo "at "; - $this->warning($this->info_error_exception['file'])->print(); + CliMessage::warning($this->info_error_exception['file'])->print(); echo " : "; - $this->warning($this->info_error_exception['line'])->print()->break(true); + CliMessage::warning($this->info_error_exception['line'])->print()->break(true); $this->getLines($this->info_error_exception['file'], $this->info_error_exception['line']); if (!empty($this->trace)) { echo PHP_EOL; - $this->warning("Exception Trace")->print()->break(true); + CliMessage::warning("Exception Trace")->print()->break(true); } foreach ($this->trace as $key => $trace) { echo $key . " "; - $this->info($trace['file'])->print(); + CliMessage::info($trace['file'])->print(); echo " : "; - $this->info($trace['line'])->print()->break(); + CliMessage::info($trace['line'])->print()->break(); } echo PHP_EOL; @@ -278,10 +277,10 @@ private function getLines(string $context, int $line): self foreach ($result as $key => $value) { if ($key == $line) { - $this->errorLine(" -> " . $key . "| " . $value)->print()->break(); + CliMessage::errorLine("↪︎ " . $key . "| " . $value)->print()->break(); } else { $this->gray(" " . $key . "| ")->print(); - $this->info($value)->print()->break(); + CliMessage::info($value)->print()->break(); } } diff --git a/src/View/templates/index.php b/src/View/templates/index.php index 9243587..c18307f 100644 --- a/src/View/templates/index.php +++ b/src/View/templates/index.php @@ -30,6 +30,9 @@ Benchmark + + Logs + " data-bs-toggle="modal" data-bs-target="#occurrencesid"> Occurrences @@ -62,6 +65,7 @@ include_once 'code-error-exception.php'; include_once 'info-server.php'; include_once 'info-request.php'; + include_once 'info-logs.php'; ?> diff --git a/src/View/templates/info-logs.php b/src/View/templates/info-logs.php new file mode 100644 index 0000000..83e6949 --- /dev/null +++ b/src/View/templates/info-logs.php @@ -0,0 +1,29 @@ + + \ No newline at end of file diff --git a/src/log_files/.gitkeep b/src/log_files/.gitkeep deleted file mode 100644 index e69de29..0000000