Browse Source

Refresh development error page

- Simplify page layout and only have the trace.
- Make frames collapsible.
- Make it possible to have multiple frames open at the same time.
- Render chained exceptions well.
Mark Story 3 years ago
parent
commit
0bb3a8a730
2 changed files with 194 additions and 79 deletions
  1. 121 0
      templates/element/dev_error_stacktrace.php
  2. 73 79
      templates/layout/dev_error.php

+ 121 - 0
templates/element/dev_error_stacktrace.php

@@ -0,0 +1,121 @@
+<?php
+/**
+ * Prints a stack trace for an exception
+ *
+ * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ * @link          https://cakephp.org CakePHP(tm) Project
+ * @since         4.5.0
+ * @license       https://opensource.org/licenses/mit-license.php MIT License
+ * @var array $trace
+ */
+use Cake\Error\Debugger;
+
+foreach ($exceptions as $level => $exc):
+    $stackTrace = Debugger::formatTrace($exc->getTrace(), [
+        'format' => 'array',
+        'args' => true,
+    ]);
+
+    if ($level != 0): ?>
+        <div class="stack-exception-header">
+            <span class="stack-exception-caused">Caused by:</span>
+            <span class="stack-exception-type"><?= h(get_class($exc)); ?></span>
+            <span class="stack-exception-message"><?= Debugger::formatHtmlMessage($exc->getMessage()) ?></span>
+        </div>
+    <?php endif; ?>
+    <?php
+    foreach ($stackTrace as $i => $stack):
+        $excerpt = $params = [];
+
+        $line = null;
+        if (isset($stack['file'], $stack['line']) && is_numeric($stack['line'])):
+            $line = $stack['line'];
+            $excerpt = Debugger::excerpt($stack['file'], $line, 4);
+        endif;
+
+        if (isset($stack['file'])):
+            $file = $stack['file'];
+        else:
+            $file = '[internal function]';
+        endif;
+
+        if (isset($stack['function'])):
+            if (!empty($stack['args'])):
+                foreach ((array)$stack['args'] as $arg):
+                    $params[] = Debugger::exportVar($arg, 4);
+                endforeach;
+            else:
+                $params[] = 'No arguments';
+            endif;
+        endif;
+
+        $frameId = "{$level}-{$i}";
+        $activeFrame = $i == 0;
+    ?>
+        <div id="stack-frame-<?= $frameId ?>" class="stack-frame">
+            <div class="stack-frame-header">
+                <div class="stack-frame-header-content">
+                    <span class="stack-frame-file">
+                        <?= h(Debugger::trimPath($file)); ?>
+                    </span>
+                    <span class="stack-function">
+                        <?php if (isset($stack['class']) || isset($stack['function'])): ?>
+                            <span class="stack-frame-label">in</span>
+                        <?php endif ?>
+                        <?php if (isset($stack['class'])): ?>
+                            <?= h($stack['class'] . $stack['type'] . $stack['function']) ?>
+                        <?php elseif (isset($stack['function'])): ?>
+                            <?= h($stack['function']) ?>
+                        <?php endif; ?>
+                    </span>
+
+                    <?php if ($line !== null): ?>
+                        <span class="stack-frame-line">
+                            <span class="stack-frame-label">at line</span><?= h($line) ?>
+                        </span>
+                    <?php endif ?>
+
+                    <?php if ($line !== null): ?>
+                        <?= $this->Html->link('(open)', Debugger::editorUrl($file, $line), ['class' => 'stack-frame-edit']); ?>
+                    <?php endif; ?>
+                </div>
+
+                <button 
+                    data-frame-id="<?= h($frameId) ?>"
+                    class="stack-frame-toggle <?= $activeFrame ? 'active' : '' ?>"
+                >
+                    &#x25BC;
+                </button>
+            </div>
+            <div
+                class="stack-frame-contents"
+                id="stack-frame-details-<?= $frameId ?>"
+                style="display: <?= $activeFrame ? 'block' : 'none' ?>"
+            >
+                <table class="code-excerpt" cellspacing="0" cellpadding="0">
+                <?php $lineno = isset($stack['line']) && is_numeric($stack['line']) ? $stack['line'] - 4 : 0 ?>
+                <?php foreach ($excerpt as $l => $line): ?>
+                    <tr>
+                        <td class="excerpt-number" data-number="<?= $lineno + $l ?>"></td>
+                        <td class="excerpt-line"><?= $line ?></td>
+                    </tr>
+                <?php endforeach; ?>
+                </table>
+
+                <a href="#" class="stack-frame-args" data-target="stack-args-<?= $frameId ?>">Toggle Arguments</a>
+                <div id="stack-args-<?= $frameId ?>" class="stack-args" style="display: none;">
+                    <?php foreach ($params as $param): ?>
+                        <div class="cake-debug"><?= $param ?></div>
+                    <?php endforeach; ?>
+                </div>
+            </div>
+        </div>
+    <?php endforeach; ?>
+<?php endforeach; ?>

+ 73 - 79
templates/layout/dev_error.php

@@ -43,7 +43,7 @@ use Cake\Error\Debugger;
         flex: 1;
         background-color: #D33C47;
         color: #ffffff;
-        padding: 10px;
+        padding: 30px;
     }
     .header-title {
         display: flex;
@@ -73,21 +73,8 @@ use Cake\Error\Debugger;
     .header-help a {
         color: #fff;
     }
-
     .error-content {
-        display: flex;
-    }
-    .col-left,
-    .col-right {
-        overflow-y: auto;
-        padding: 10px;
-    }
-    .col-left {
-        background: #ececec;
-        flex: 0 0 30%;
-    }
-    .col-right {
-        flex: 1;
+        padding: 30px;
     }
 
     .toggle-vendor-frames {
@@ -146,20 +133,36 @@ use Cake\Error\Debugger;
         margin: 0;
         padding: 0;
     }
-    .stack-previous {
-        margin: 24px 0 12px 8px;
+
+    /* Previous exception blocks */
+    .stack-exception-header {
+        margin: 36px 0 12px 8px;
+    }
+    .stack-exception-caused {
+        font-size: 1.4em;
+        display: block;
+    }
+    .stack-exception-type {
+        font-family: consolas, monospace;
+    }
+    .stack-exception-message {
+        margin-top: 12px;
     }
+
     .stack-frame {
         background: #e5e5e5;
         padding: 10px;
         margin-bottom: 10px;
+        background: #ececec;
+        border-radius: 4px;
+        padding: 10px;
+        margin-bottom: 10px;
     }
     .stack-frame:last-child {
         border-bottom: none;
         margin-bottom: 0;
     }
     .stack-frame a {
-        display: block;
         color: #212121;
         text-decoration: none;
     }
@@ -169,48 +172,22 @@ use Cake\Error\Debugger;
     .stack-frame a:hover {
         text-decoration: underline;
     }
+
+    /* Stack frame headers */
     .stack-frame-header {
         display: flex;
-        align-items: center;
-    }
-    .stack-frame-file a {
-        color: #212121;
-    }
-
-    .stack-frame-args {
-        flex: 0 0 150px;
-        display: block;
-        padding: 8px 14px;
-        text-decoration: none;
-        background-color: #606c76;
-        border-radius: 4px;
-        cursor: pointer;
-        color: #fff;
-        text-align: center;
-        margin-bottom: 10px;
-    }
-    .stack-frame-args:hover {
-        background-color: #D33C47;
-    }
-
-    .stack-frame-file {
-        flex: 1;
-        word-break:break-all;
-        margin-right: 10px;
-        font-size: 16px;
+        justify-content: space-between;
     }
-    .stack-file,
-    .stack-function {
-        display: block;
+    .stack-frame-header-content {
+        display: flex;
+        gap: 8px;
     }
-
+    .stack-function,
     .stack-frame-file,
+    .stack-frame-line,
     .stack-file {
         font-family: consolas, monospace;
     }
-    .stack-function {
-        font-weight: bold;
-    }
     .stack-file {
         font-size: 0.9em;
         white-space: nowrap;
@@ -218,14 +195,42 @@ use Cake\Error\Debugger;
         overflow: hidden;
         direction: rtl;
     }
+    .stack-frame-file {
+        word-break: break-all;
+    }
+    .stack-frame-label {
+        font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
+        font-weight: normal;
+        margin: 0 5px 0 0;
+        font-size: 0.9em;
+    }
+    .stack-frame-edit {
+        margin: 0 5px 0 0;
+    }
+    .stack-frame-toggle {
+        border: 1px solid #7a7a7a;
+        border-radius: 5px;
+        height: 28px;
+        width: 28px;
+        background: #F5F7FA;
+        line-height: 1.5;
+    }
+    .stack-frame-toggle.active {
+        transform: rotate(180deg);
+    }
 
-    .stack-details {
-        background: #ececec;
-        border-radius: 4px;
-        padding: 10px;
-        margin-bottom: 18px;
+    .stack-frame-args {
+        display: block;
+        margin: 10px 0 0 0;
+    }
+    .stack-frame-args:hover {
+        color: #D33C47;
+    }
+    .stack-args h4 {
+        margin-top: 0;
     }
 
+    /* Code excerpts */
     .code-excerpt {
         width: 100%;
         margin: 10px 0 0 0;
@@ -249,10 +254,6 @@ use Cake\Error\Debugger;
     .excerpt-number:after {
         content: attr(data-number);
     }
-    .cake-debug {
-        margin-top: 10px;
-    }
-
     table {
         text-align: left;
     }
@@ -262,6 +263,10 @@ use Cake\Error\Debugger;
     th {
         border-bottom: 1px solid #ccc;
     }
+
+    .cake-debug {
+        margin-top: 10px;
+    }
     </style>
     <?php require CAKE . 'Error/Debug/dumpHeader.html'; ?>
 </head>
@@ -282,29 +287,24 @@ use Cake\Error\Debugger;
         <span class="header-type"><?= get_class($error) ?></span>
     </header>
     <div class="error-content">
-        <div class="col-left">
-            <?= $this->element('exception_stack_trace_nav') ?>
-        </div>
-        <div class="col-right">
             <?php if ($this->fetch('subheading')): ?>
             <p class="error-subheading">
                 <?= $this->fetch('subheading') ?>
             </p>
             <?php endif; ?>
 
-            <?= $this->element('exception_stack_trace'); ?>
-
             <div class="error-suggestion">
                 <?= $this->fetch('file') ?>
             </div>
 
+            <?= $this->element('dev_error_stacktrace'); ?>
+
             <?php if ($this->fetch('templateName')): ?>
             <p class="customize">
                 If you want to customize this error message, create
                 <em><?= 'templates' . DIRECTORY_SEPARATOR . 'Error' . DIRECTORY_SEPARATOR . $this->fetch('templateName') ?></em>
             </p>
             <?php endif; ?>
-        </div>
     </div>
 
     <script type="text/javascript">
@@ -340,18 +340,12 @@ use Cake\Error\Debugger;
 
             var details = document.querySelectorAll('.stack-details');
             var frames = document.querySelectorAll('.stack-frame');
-            bindEvent('.stack-frame a', 'click', function(event) {
-                each(frames, function(el) {
-                    el.classList.remove('active');
-                });
-                this.parentNode.classList.add('active');
-
-                each(details, function(el) {
-                    el.style.display = 'none';
-                });
+            bindEvent('.stack-frame-toggle', 'click', function(event) {
+                this.classList.toggle('active');
 
-                var target = document.getElementById(this.dataset['target']);
-                toggleElement(target);
+                var frameId = this.dataset.frameId;
+                var frame = document.getElementById('stack-frame-details-' + frameId);
+                toggleElement(frame);
                 event.preventDefault();
             });