Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <?php
- declare(strict_types=1);
- /**************************************************************************************
- *
- * Catalyst PHP Framework
- * PHP Version 8.3 (Required).
- *
- * @see https://github.com/arcanisgk/catalyst
- *
- * @author Walter Nuñez (arcanisgk/original founder) <[email protected]>
- * @copyright 2023 - 2024
- * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
- * @note This program is distributed in the hope that it will be useful
- * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE.
- *
- */
- namespace Catalyst\Helpers\Debug;
- use Catalyst\Framework\Traits\SingletonTrait;
- /**************************************************************************************
- * Dumper class for debugging variables
- *
- * @package Catalyst\Helpers\Debug;
- */
- class Dumper
- {
- use SingletonTrait;
- /**
- * Maximum string length in output
- */
- private int $maxStrLength = 150;
- /**
- * Maximum array/object children to show
- */
- private int $maxChildren = 50;
- /**
- * Maximum nesting level
- */
- private int $maxDepth = 5;
- /**
- * Counter for generating unique IDs for collapsible elements
- */
- private int $collapseCounter = 0;
- /**
- * Counter for generating unique IDs for dump modals
- */
- private static int $dumpCounter = 0;
- /**
- * Dump variables with formatting
- *
- * @param array $options Options array with 'data' containing variables to dump
- * @return void
- */
- public static function dump(array $options): void
- {
- $instance = self::getInstance();
- $data = $options['data'] ?? [];
- $caller = $options['caller'] ?? null;
- if (empty($data)) {
- return;
- }
- $isHtml = !IS_CLI;
- if (!$isHtml) {
- // CLI output - proceed as normal
- $width = min(80, TW);
- // Display caller information if available
- if ($caller) {
- $callerText = "Called from: " . $caller['file'] . " (line " . $caller['line'] . ")";
- echo str_repeat('=', $width) . PHP_EOL;
- echo "\033[1;36m" . $callerText . "\033[0m" . PHP_EOL;
- echo str_repeat('=', $width) . PHP_EOL;
- }
- // Reset collapse counter for each dump call
- $instance->collapseCounter = 0;
- foreach ($data as $var) {
- $instance->dumpVar($var, 'Output', $isHtml);
- echo str_repeat('-', $width) . PHP_EOL;
- }
- return;
- }
- // HTML output - create a modal
- $dumpId = 'catalyst-dump-' . (++self::$dumpCounter);
- $modalId = $dumpId . '-modal';
- $btnId = $dumpId . '-btn';
- // Add CSS and JavaScript for modal functionality
- echo '<style>
- .' . $dumpId . '-modal {
- display: none;
- position: fixed;
- z-index: 9999;
- left: 0;
- top: 0;
- width: 100%;
- height: 100%;
- overflow: hidden;
- background-color: rgba(0, 0, 0, 0.7);
- backdrop-filter: blur(2px);
- }
- .' . $dumpId . '-modal-content {
- position: relative;
- background-color: #1d1e22;
- margin: 30px auto;
- padding: 0;
- width: 90%;
- max-width: 1200px;
- max-height: 90vh;
- border-radius: 8px;
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
- display: flex;
- flex-direction: column;
- overflow: hidden;
- }
- .' . $dumpId . '-close {
- position: absolute;
- top: 50%;
- right: 15px;
- transform: translateY(-50%);
- width: 14px;
- height: 14px;
- background-color: #ff5f57;
- border-radius: 50%;
- cursor: pointer;
- z-index: 1;
- display: flex;
- align-items: center;
- justify-content: center;
- box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
- }
- .' . $dumpId . '-close:hover::before {
- content: "×";
- font-size: 12px;
- color: rgba(0, 0, 0, 0.5);
- line-height: 1;
- }
- .' . $dumpId . '-btn {
- position: fixed;
- bottom: 20px;
- left: 20px;
- background-color: #2d2d30;
- color: #80deea;
- border: none;
- border-radius: 50%;
- width: 50px;
- height: 50px;
- font-size: 24px;
- cursor: pointer;
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
- z-index: 9998;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.2s ease;
- }
- .' . $dumpId . '-btn:hover {
- background-color: #3d3d40;
- transform: scale(1.05);
- }
- .' . $dumpId . '-btn-badge {
- position: absolute;
- top: -5px;
- right: -5px;
- background-color: #ef5350;
- color: white;
- border-radius: 50%;
- width: 20px;
- height: 20px;
- font-size: 12px;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .' . $dumpId . '-modal-header {
- padding: 15px 40px 15px 15px;
- background-color: #2d2d30;
- border-bottom: 1px solid #3d3d40;
- border-radius: 8px 8px 0 0;
- flex-shrink: 0;
- position: relative;
- }
- .' . $dumpId . '-modal-body {
- padding: 15px;
- overflow: auto;
- max-height: calc(90vh - 60px);
- }
- </style>';
- echo '<script>
- // Function to toggle collapsible sections
- function toggleCollapse(id) {
- const content = document.getElementById("content-" + id);
- const chevron = document.getElementById("chevron-" + id);
- if (content.style.display === "none") {
- content.style.display = "block";
- chevron.innerHTML = "▼"; // Down chevron
- chevron.title = "Collapse";
- } else {
- content.style.display = "none";
- chevron.innerHTML = "►"; // Right chevron
- chevron.title = "Expand";
- }
- }
- // Function to toggle modal visibility
- function toggleModal' . self::$dumpCounter . '() {
- const modal = document.getElementById("' . $modalId . '");
- if (modal.style.display === "block") {
- modal.style.display = "none";
- } else {
- modal.style.display = "block";
- }
- }
- // Close modal when clicking outside of it
- document.addEventListener("DOMContentLoaded", function() {
- const modal = document.getElementById("' . $modalId . '");
- window.addEventListener("click", function(event) {
- if (event.target === modal) {
- modal.style.display = "none";
- }
- });
- });
- </script>';
- // Create the modal structure
- echo '<div id="' . $modalId . '" class="' . $dumpId . '-modal">
- <div class="' . $dumpId . '-modal-content">
- <div class="' . $dumpId . '-modal-header">
- <span class="' . $dumpId . '-close" onclick="toggleModal' . self::$dumpCounter . '()"></span>';
- // Display caller information in modal header if available
- if ($caller) {
- $callerText = "Called from: " . $caller['file'] . " (line " . $caller['line'] . ")";
- echo '<span style="color:#80deea;font-weight:bold;">' . htmlspecialchars($callerText) . '</span>';
- } else {
- echo '<span style="color:#80deea;font-weight:bold;">Debug Information</span>';
- }
- echo '</div>
- <div class="' . $dumpId . '-modal-body">
- <pre style="background-color:#1d1e22; color:#e6e6e6; padding:0; margin:0; font-family:monospace;">';
- // Reset collapse counter for each dump call
- $instance->collapseCounter = 0;
- foreach ($data as $var) {
- $instance->dumpVar($var, 'Output', $isHtml);
- echo '<hr style="border:1px dashed #505050; margin:10px 0;">';
- }
- echo '</pre>
- </div>
- </div>
- </div>';
- // Create the floating button
- echo '<button id="' . $btnId . '" class="' . $dumpId . '-btn" onclick="toggleModal' . self::$dumpCounter . '()" title="Show Debug Information">
- <span style="font-size:20px;">🔎</span>
- <span class="' . $dumpId . '-btn-badge">' . count($data) . '</span>
- </button>';
- }
- /**
- * Format and output the variable
- *
- * @param mixed $var Variable to dump
- * @param string $label Variable label
- * @param bool $isHtml Whether to format for HTML output
- * @param int $depth Current depth level
- * @return void
- */
- private function dumpVar(mixed $var, string $label = '', bool $isHtml = true, int $depth = 0): void
- {
- $indent = str_repeat(' ', $depth);
- $type = gettype($var);
- $valueDisplay = match ($type) {
- 'string' => $this->formatString($var, $isHtml),
- 'integer', 'double' => $this->formatNumber($var, $isHtml),
- 'boolean' => $this->formatBoolean($var, $isHtml),
- 'NULL' => $this->formatNull($isHtml),
- 'array' => $this->formatArray($var, $isHtml, $depth),
- 'object' => $this->formatObject($var, $isHtml, $depth),
- 'resource' => $this->formatResource($var, $isHtml),
- default => "($type)"
- };
- $typeColor = $this->getTypeColor($type, $isHtml);
- $labelOutput = $label ? $this->colorText($label . ' ', 'label', $isHtml) : '';
- echo $indent . $labelOutput . $this->colorText("($type)", $typeColor, $isHtml) . ' ' . $valueDisplay . PHP_EOL;
- }
- /**
- * Format string for output
- *
- * @param string $var
- * @param bool $isHtml
- * @return string
- */
- private function formatString(string $var, bool $isHtml): string
- {
- $length = strlen($var);
- // Handle multiline strings
- if (str_contains($var, "\n")) {
- $lines = explode("\n", $var);
- $firstLine = htmlspecialchars($lines[0], ENT_QUOTES | ENT_HTML5);
- $result = $this->colorText('"' . $firstLine, 'string', $isHtml);
- // Indent and append remaining lines
- for ($i = 1; $i < count($lines); $i++) {
- $line = htmlspecialchars($lines[$i], ENT_QUOTES | ENT_HTML5);
- $result .= "\n" . str_repeat(' ', 8) . $this->colorText($line, 'string', $isHtml);
- }
- $result .= $this->colorText('"', 'string', $isHtml) .
- $this->colorText(" (length=" . $length . ", multiline)", 'meta', $isHtml);
- return $result;
- }
- // Handle regular strings
- $var = htmlspecialchars($var, ENT_QUOTES | ENT_HTML5);
- if ($length > $this->maxStrLength) {
- $var = substr($var, 0, $this->maxStrLength) . '...';
- }
- return $this->colorText('"' . $var . '"', 'string', $isHtml) .
- $this->colorText(" (length=" . $length . ")", 'meta', $isHtml);
- }
- /**
- * Format numeric value for output
- *
- * @param int|float $var
- * @param bool $isHtml
- * @return string
- */
- private function formatNumber(int|float $var, bool $isHtml): string
- {
- return $this->colorText((string)$var, 'number', $isHtml);
- }
- /**
- * Format boolean for output
- *
- * @param bool $var
- * @param bool $isHtml
- * @return string
- */
- private function formatBoolean(bool $var, bool $isHtml): string
- {
- return $this->colorText($var ? 'true' : 'false', 'boolean', $isHtml);
- }
- /**
- * Format null for output
- *
- * @param bool $isHtml
- * @return string
- */
- private function formatNull(bool $isHtml): string
- {
- return $this->colorText('null', 'null', $isHtml);
- }
- /**
- * Create a collapsible section with chevron toggle
- *
- * @param string $header Header content
- * @param string $content Content to be collapsed/expanded
- * @param bool $isHtml Whether to format for HTML output
- * @param bool $initiallyExpanded Whether the content should be initially expanded
- * @param int $depth Current nesting depth for indentation
- * @return string Formatted output with collapsible functionality
- */
- private function createCollapsible(string $header, string $content, bool $isHtml, bool $initiallyExpanded = true, int $depth = 0): string
- {
- $indent = str_repeat(' ', $depth);
- if (!$isHtml) {
- // For CLI, just return the content without collapsible functionality
- return $header . " {" . PHP_EOL . $content . PHP_EOL . $indent . "}";
- }
- // Generate a unique ID for this collapsible section
- $id = ++$this->collapseCounter;
- // Determine initial state
- $displayStyle = $initiallyExpanded ? 'block' : 'none';
- $chevronChar = $initiallyExpanded ? '▼' : '►';
- $chevronTitle = $initiallyExpanded ? 'Collapse' : 'Expand';
- // Create the collapsible HTML structure
- $result = '<span style="cursor:pointer;" onclick="toggleCollapse(' . $id . ')">';
- $result .= '<span id="chevron-' . $id . '" title="' . $chevronTitle . '" style="display:inline-block;width:15px;text-align:center;color:#9e9e9e;">' . $chevronChar . '</span>';
- $result .= $header . ' {</span>' . PHP_EOL;
- $result .= '<div id="content-' . $id . '" style="display:' . $displayStyle . ';">' . $content . '</div>';
- $result .= $indent . '}';
- return $result;
- }
- /**
- * Format array for output
- *
- * @param array $var
- * @param bool $isHtml
- * @param int $depth
- * @return string
- */
- private function formatArray(array $var, bool $isHtml, int $depth): string
- {
- $count = count($var);
- if ($depth >= $this->maxDepth) {
- return $this->colorText("Array", 'array', $isHtml) .
- $this->colorText(" (items=" . $count . ")", 'meta', $isHtml) .
- $this->colorText(" [MAX DEPTH REACHED]", 'error', $isHtml);
- }
- $header = $this->colorText("Array", 'array', $isHtml) .
- $this->colorText(" (items=" . $count . ")", 'meta', $isHtml);
- // If array is empty, don't make it collapsible
- if ($count === 0) {
- return $header . " {}";
- }
- $contentBuffer = '';
- $i = 0;
- foreach ($var as $key => $value) {
- if ($i >= $this->maxChildren) {
- $indent = str_repeat(' ', $depth + 1);
- $contentBuffer .= $indent . $this->colorText("... +" . ($count - $this->maxChildren) . " more items", 'meta', $isHtml) . PHP_EOL;
- break;
- }
- $keyDisplay = is_string($key) ?
- $this->colorText("\"$key\"", 'key', $isHtml) :
- $this->colorText((string)$key, 'key', $isHtml);
- $contentBuffer .= str_repeat(' ', $depth + 1) .
- "[" . $keyDisplay . "] => ";
- // Capture output from recursive call
- ob_start();
- $this->dumpVar($value, '', $isHtml, $depth + 1);
- $contentBuffer .= trim(ob_get_clean());
- $contentBuffer .= PHP_EOL;
- $i++;
- }
- // Make the array collapsible
- return $this->createCollapsible($header, $contentBuffer, $isHtml, true, $depth);
- }
- /**
- * Format object for output
- *
- * @param object $var
- * @param bool $isHtml
- * @param int $depth
- * @return string
- */
- private function formatObject(object $var, bool $isHtml, int $depth): string
- {
- $class = get_class($var);
- $props = (array)$var;
- $count = count($props);
- if ($depth >= $this->maxDepth) {
- return $this->colorText($class, 'object', $isHtml) .
- $this->colorText(" (properties=" . $count . ")", 'meta', $isHtml) .
- $this->colorText(" [MAX DEPTH REACHED]", 'error', $isHtml);
- }
- $header = $this->colorText($class, 'object', $isHtml) .
- $this->colorText(" (properties=" . $count . ")", 'meta', $isHtml);
- // If object has no properties, don't make it collapsible
- if ($count === 0) {
- return $header . " {}";
- }
- $contentBuffer = '';
- $i = 0;
- foreach ($props as $key => $value) {
- if ($i >= $this->maxChildren) {
- $indent = str_repeat(' ', $depth + 1);
- $contentBuffer .= $indent . $this->colorText("... +" . ($count - $this->maxChildren) . " more properties", 'meta', $isHtml) . PHP_EOL;
- break;
- }
- // Handle property name with potential visibility indicator
- $keyString = (string)$key;
- $keyParts = explode("\0", $keyString);
- $propName = end($keyParts);
- $visibility = 'public';
- if (count($keyParts) > 1) {
- $visibility = $keyParts[1] === '*' ? 'protected' : 'private';
- }
- $visColor = match ($visibility) {
- 'private' => 'private',
- 'protected' => 'protected',
- default => 'public'
- };
- $contentBuffer .= str_repeat(' ', $depth + 1) .
- $this->colorText("[$visibility]", $visColor, $isHtml) . " " .
- $this->colorText("$propName", 'key', $isHtml) . " => ";
- // Capture output from recursive call
- ob_start();
- $this->dumpVar($value, '', $isHtml, $depth + 1);
- $contentBuffer .= trim(ob_get_clean());
- $contentBuffer .= PHP_EOL;
- $i++;
- }
- // Make the object collapsible
- return $this->createCollapsible($header, $contentBuffer, $isHtml, true, $depth);
- }
- /**
- * Format resource for output
- *
- * @param $var
- * @param bool $isHtml
- * @return string
- */
- private function formatResource($var, bool $isHtml): string
- {
- $type = get_resource_type($var);
- return $this->colorText("resource($type)", 'resource', $isHtml) .
- $this->colorText(" id=" . (int)$var, 'meta', $isHtml);
- }
- /**
- * Get color associated with type
- *
- * @param string $type
- * @param bool $isHtml
- * @return string
- */
- private function getTypeColor(string $type, bool $isHtml): string
- {
- return match ($type) {
- 'string' => 'string',
- 'integer', 'double' => 'number',
- 'boolean' => 'boolean',
- 'NULL' => 'null',
- 'array' => 'array',
- 'object' => 'object',
- 'resource' => 'resource',
- default => 'default'
- };
- }
- /**
- * Apply color to text based on context
- *
- * @param string $text
- * @param string $context
- * @param bool $isHtml
- * @return string
- */
- private function colorText(string $text, string $context, bool $isHtml): string
- {
- if (!$isHtml) {
- // ANSI color codes for CLI
- return match ($context) {
- 'string' => "\033[0;32m" . $text . "\033[0m", // Green
- 'number' => "\033[0;34m" . $text . "\033[0m", // Blue
- 'boolean' => "\033[0;35m" . $text . "\033[0m", // Magenta
- 'null' => "\033[0;31m" . $text . "\033[0m", // Red
- 'array' => "\033[0;33m" . $text . "\033[0m", // Yellow
- 'object' => "\033[0;36m" . $text . "\033[0m", // Cyan
- 'resource' => "\033[0;95m" . $text . "\033[0m", // Light magenta
- 'key' => "\033[0;33m" . $text . "\033[0m", // Yellow
- 'meta' => "\033[0;90m" . $text . "\033[0m", // Dark gray
- 'error' => "\033[0;91m" . $text . "\033[0m", // Light red
- 'label' => "\033[1;37m" . $text . "\033[0m", // Bold white
- 'public' => "\033[0;92m" . $text . "\033[0m", // Light green
- 'protected' => "\033[0;93m" . $text . "\033[0m", // Light yellow
- 'private' => "\033[0;91m" . $text . "\033[0m", // Light red
- 'chevron' => "\033[0;90m" . $text . "\033[0m", // Dark gray
- default => $text
- };
- }
- // HTML colors
- return match ($context) {
- 'string' => '<span style="color:#a5d6a7;">' . $text . '</span>',
- 'number' => '<span style="color:#90caf9;">' . $text . '</span>',
- 'boolean' => '<span style="color:#ce93d8;">' . $text . '</span>',
- 'null' => '<span style="color:#ef9a9a;">' . $text . '</span>',
- 'array' => '<span style="color:#ffcc80;">' . $text . '</span>',
- 'object' => '<span style="color:#80deea;">' . $text . '</span>',
- 'resource' => '<span style="color:#ea80fc;">' . $text . '</span>',
- 'key' => '<span style="color:#ffe082;">' . $text . '</span>',
- 'meta' => '<span style="color:#9e9e9e;">' . $text . '</span>',
- 'error' => '<span style="color:#ef5350;">' . $text . '</span>',
- 'label' => '<span style="color:#ffffff;font-weight:bold;">' . $text . '</span>',
- 'public' => '<span style="color:#81c784;">' . $text . '</span>',
- 'protected' => '<span style="color:#fff176;">' . $text . '</span>',
- 'private' => '<span style="color:#ef5350;">' . $text . '</span>',
- 'chevron' => '<span style="color:#9e9e9e;">' . $text . '</span>',
- default => $text
- };
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement