Browse Source

Add Html/Js renderer.

I've removed the context output as we can't collect it in php
8 effectively removing it from future application usage.

I fixed up a test I made a mistake on earlier as well for the log output
format.
Mark Story 4 years ago
parent
commit
1b1d0e08a1

+ 5 - 1
src/Error/Debugger.php

@@ -31,6 +31,7 @@ use Cake\Error\Debug\ReferenceNode;
 use Cake\Error\Debug\ScalarNode;
 use Cake\Error\Debug\SpecialNode;
 use Cake\Error\Debug\TextFormatter;
+use Cake\Error\Renderer\HtmlRenderer;
 use Cake\Error\Renderer\TextRenderer;
 use Cake\Log\Log;
 use Cake\Utility\Hash;
@@ -82,6 +83,7 @@ class Debugger
      */
     protected $_templates = [
         'log' => [
+            // These templates are not actually used, as Debugger::log() is called instead.
             'trace' => '{:reference} - {:path}, line {:line}',
             'error' => '{:error} ({:code}): {:description} in [{:file}, line {:line}]',
         ],
@@ -121,9 +123,11 @@ class Debugger
      * @var array<string, class-string>
      */
     protected $renderers = [
-        // Backwards compatible alias for text
+        // Backwards compatible alias for text that will be deprecated.
         'txt' => TextRenderer::class,
         'text' => TextRenderer::class,
+        // The html alias currently uses no JS and will be deprecated.
+        'js' => HtmlRenderer::class,
     ];
 
     /**

+ 93 - 0
src/Error/Renderer/HtmlRenderer.php

@@ -0,0 +1,93 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * 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.4.0
+ * @license       https://opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Error\Renderer;
+
+use Cake\Error\Debugger;
+use Cake\Error\ErrorRendererInterface;
+use Cake\Error\PhpError;
+
+/**
+ * Interactive HTML error rendering with a stack trace.
+ *
+ * Default output renderer for non CLI SAPI.
+ */
+class HtmlRenderer implements ErrorRendererInterface
+{
+    /**
+     * @inheritDoc
+     */
+    public function render(PhpError $error): string
+    {
+        $id = 'cakeErr' . uniqid();
+
+        // Some of the error data is not HTML safe so we escape everything.
+        $description = h($error->getMessage());
+        $path = h($error->getFile());
+        $line = h($error->getLine());
+        $trace = h($error->getTraceAsString());
+
+        debug($error);
+        $errorMessage = sprintf(
+            '<b>%s</b> (%s)',
+            h($error->getLabel()),
+            h($error->getCode())
+        );
+        $toggle = $this->renderToggle($errorMessage, $id, 'trace');
+        $codeToggle = $this->renderToggle('Code', $id, 'code');
+        $excerpt = Debugger::excerpt($error->getFile(), $error->getLine(), 1);
+        $code = implode("\n", $excerpt);
+
+        $html = <<<HTML
+<pre class="cake-error">
+    {$toggle}: {$description} [in <b>{$path}</b>, line <b>{$line}</b>]
+    <div id="{:id}-trace" class="cake-stack-trace" style="display: none;">
+        {$codeToggle}
+        <pre id="{$id}-code" class="cake-code-dump" style="display: none;">
+            {$code}
+        </pre>
+        <div class="cake-trace">
+            {$trace}
+        </div>
+    </div>
+</pre>
+HTML;
+
+        return $html;
+    }
+
+    /**
+     * Render a toggle link in the error content.
+     *
+     * @param string $text The text to insert. Assumed to be HTML safe.
+     * @param string $id The error id scope.
+     * @param string $suffix The element selector.
+     * @return string
+     */
+    private function renderToggle(string $text, string $id, string $suffix): string
+    {
+        $selector = $id . '-' . $suffix;
+
+        return <<<HTML
+<a
+    href="javascript:void(0);"
+    onclick="document.getElementById('{$selector}').style.display = (document.getElementById('{$selector}').style.display == 'none' ? '' : 'none'"
+>
+    {$text}
+</a>
+HTML;
+    }
+}

+ 2 - 2
tests/TestCase/Error/DebuggerTest.php

@@ -198,7 +198,7 @@ class DebuggerTest extends TestCase
         $debugger->outputError($data);
         $result = ob_get_clean();
 
-        $this->assertMatchesRegularExpression('#^\<span class\="code\-highlight"\>.*outputError.*\</span\>$#m', $result);
+        $this->assertMatchesRegularExpression('#^\<span class\="code\-highlight"\>.*__LINE__.*\</span\>$#m', $result);
     }
 
     /**
@@ -255,7 +255,7 @@ class DebuggerTest extends TestCase
         $this->assertSame('', $output);
         $this->assertCount(1, $logs);
         // This is silly but that's how it works currently.
-        $this->assertStringContainsString("debug: Cake\Error\Debugger::outputError()", $logs[0]);
+        $this->assertStringContainsString("debug: \nCake\Error\Debugger::outputError()", $logs[0]);
 
         $this->assertStringContainsString("'file' => '{$data['file']}'", $logs[0]);
         $this->assertStringContainsString("'line' => (int) {$data['line']}", $logs[0]);