diff --git a/README.md b/README.md index 16bcda30..ad7d19b4 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,8 @@ use Awesomite\ErrorDumper\Handlers\ErrorHandler; use Awesomite\ErrorDumper\Listeners\OnExceptionCallable; use Awesomite\ErrorDumper\Listeners\OnExceptionDevView; use Awesomite\ErrorDumper\Views\ViewFactory; +use Awesomite\ErrorDumper\Views\ViewHtml; +use Awesomite\ErrorDumper\Views\ViewCli; /** * Create new error handler. @@ -56,9 +58,12 @@ $handler = new ErrorHandler(/* optional $mode = null */); /** * Create and push new error listener, * this handler will print programmer-friendly stack trace. + * + * You can use ViewCli for displaying error in cli mode. + * ViewFactory::create() creates ViewCli or ViewHtml depending on php_sapi_name(). */ -$devViewListener = new OnExceptionDevView(ViewFactory::create()); -$handler->pushListener($devViewListener); +$htmlView = new OnExceptionDevView(new ViewHtml()); +$handler->pushListener($htmlView); /** * Create and push new custom error listener. @@ -112,8 +117,8 @@ To run example in terminal, execute `bin/test.php`. ## Content Security Policy -This library uses *.js files hosted on `maxcdn.bootstrapcdn.com` and `code.jquery.com` -(`@see \Awesomite\ErrorDumper\Views\ViewHtml::getResources`). +This library uses *.js files hosted on `maxcdn.bootstrapcdn.com`, `code.jquery.com` +and `cdnjs.cloudflare.com` (`@see \Awesomite\ErrorDumper\Views\ViewHtml::getResources`). Add those domains to your `Content-Security-Policy` header during display errors. ## Symfony integration diff --git a/bin/scripts/exceptionChain.php b/bin/scripts/exceptionChain.php index 4e8bd388..c60049e2 100644 --- a/bin/scripts/exceptionChain.php +++ b/bin/scripts/exceptionChain.php @@ -9,8 +9,11 @@ * file that was distributed with this source code. */ +use Awesomite\ErrorDumper\Serializable\ContextVarsFactory; use Awesomite\ErrorDumper\Serializable\SerializableException; use Awesomite\ErrorDumper\Views\ViewHtml; +use Awesomite\StackTrace\StackTraceFactory; +use Awesomite\VarDumper\LightVarDumper; /** * @internal @@ -56,6 +59,14 @@ public function execute($callable, $arguments) $executor->execute(array(new Calculator(), 'divide'), array(5, 0)); } catch (\Exception $exception) { $view = new ViewHtml(); - $view->display(new SerializableException($exception)); + + $varDumper = new LightVarDumper(); + $varDumper + ->setMaxChildren(100) + ->setMaxDepth(100); + $stackTraceFactory = new StackTraceFactory($varDumper); + $contextFactory = new ContextVarsFactory($varDumper); + + $view->display(new SerializableException($exception, 0, false, true, true, $stackTraceFactory, $contextFactory)); exit; } diff --git a/bin/scripts/symfonyVarDumper.php b/bin/scripts/symfonyVarDumper.php index 7ee0a67a..98927354 100644 --- a/bin/scripts/symfonyVarDumper.php +++ b/bin/scripts/symfonyVarDumper.php @@ -11,6 +11,7 @@ use Awesomite\ErrorDumper\Handlers\ErrorHandler; use Awesomite\ErrorDumper\Listeners\OnExceptionCallable; +use Awesomite\ErrorDumper\Serializable\ContextVarsFactory; use Awesomite\ErrorDumper\Serializable\SerializableException; use Awesomite\ErrorDumper\Views\ViewHtml; use Awesomite\StackTrace\StackTraceFactory; @@ -20,8 +21,10 @@ $handler = new ErrorHandler(); $handler->pushListener(new OnExceptionCallable(function ($exception) { - $stackTraceFactory = new StackTraceFactory(new SymfonyVarDumper(new CliDumper())); - $serializable = new SerializableException($exception, 0, false, true, true, $stackTraceFactory); + $varDumper = new SymfonyVarDumper(new CliDumper()); + $stackTraceFactory = new StackTraceFactory($varDumper); + $contextVarsFactory = new ContextVarsFactory($varDumper); + $serializable = new SerializableException($exception, 0, false, true, true, $stackTraceFactory, $contextVarsFactory); $view = new ViewHtml(); $view->display($serializable); diff --git a/optimizer/OptimizerCommand.php b/optimizer/OptimizerCommand.php index 8db1a82a..c8c90056 100644 --- a/optimizer/OptimizerCommand.php +++ b/optimizer/OptimizerCommand.php @@ -61,6 +61,8 @@ private function optimize($input) $output = $this->removeWhiteSpacesBetween('}}', '{%', $output); $output = $this->removeWhiteSpacesBetween('"', '>', $output); $output = $this->removeWhiteSpacesBetween('"', '/>', $output); + $output = $this->removeWhiteSpacesBetween('}}()', '{%', $output); + $output = $this->removeWhiteSpacesBetween('>', '#{{', $output); // ''; + }, + $output + ); + // "{{ ' ' }}" => ' ' $output = \preg_replace_callback( '/\{\{\s*\'(?\s+)\'\s*\}\}/', diff --git a/src/Views/ViewHtml.php b/src/Views/ViewHtml.php index 68312e04..2039e855 100644 --- a/src/Views/ViewHtml.php +++ b/src/Views/ViewHtml.php @@ -16,8 +16,8 @@ class ViewHtml implements ViewInterface { - const TAG_HTML = ''; - const TAG_UNDER_TITLE = ''; + const TAG_HTML = ''; + const TAG_UNDER_MENU = ''; private static $headers = array( @@ -31,7 +31,7 @@ class ViewHtml implements ViewInterface */ private $editor; - private $contentUnderTitle; + private $contentUnderMenu; private $cacheDirectory; @@ -77,19 +77,19 @@ public function display(SerializableExceptionInterface $exception) 'resources' => $this->getResources(), 'editor' => $this->editor, 'hasEditor' => !\is_null($this->editor), - 'contentUnderTitle' => $this->contentUnderTitle, + 'contentUnderMenu' => $this->contentUnderMenu, 'appendToBody' => $this->appendToBody, )); } /** - * @param object|string $string + * @param object|string $stringable * * @return $this */ - public function setContentUnderTitle($string) + public function setContentUnderMenu($stringable) { - $this->contentUnderTitle = $string; + $this->contentUnderMenu = $stringable; return $this; } @@ -109,13 +109,13 @@ public function setEditor(EditorInterface $editor = null) } /** - * @param object|string $string + * @param object|string $stringable * * @return $this */ - public function appendToBody($string) + public function appendToBody($stringable) { - $this->appendToBody[] = $string; + $this->appendToBody[] = $stringable; return $this; } @@ -198,18 +198,22 @@ private function getResources() return array( 'css' => array( array( - 'link' => '//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css', - 'integrity' => 'sha384-pdapHxIh7EYuwy6K7iE41uXVxGCXY0sAjBzaElYGJUrzwodck3Lx6IE2lA0rFREo', + 'link' => 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css', + 'integrity' => 'sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm', ), ), 'js' => array( array( - 'link' => '//code.jquery.com/jquery-2.1.4.min.js', - 'integrity' => 'sha384-R4/ztc4ZlRqWjqIuvf6RX5yb/v90qNGx6fS48N0tRxiGkqveZETq72KgDVJCp2TC', + 'link' => 'https://code.jquery.com/jquery-3.2.1.slim.min.js', + 'integrity' => 'sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN', ), array( - 'link' => '//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js', - 'integrity' => 'sha384-pPttEvTHTuUJ9L2kCoMnNqCRcaMPMVMsWVO+RLaaaYDmfSP5//dP6eKRusbPcqhZ', + 'link' => 'https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js', + 'integrity' => 'sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q', + ), + array( + 'link' => 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js', + 'integrity' => 'sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl', ), ), ); diff --git a/templates/exception-context.twig b/templates/exception-context.twig deleted file mode 100644 index ed09da97..00000000 --- a/templates/exception-context.twig +++ /dev/null @@ -1,27 +0,0 @@ -{% if exception.getContext|length > 0 %} -

Context

- -
- {% for var in exception.getContext %} - {% set showFull = loop.index0 < 10 %} - {% set varId = 'context-variable-' ~ loop.index %} - - -
-
-
{{ var.dumpAsString() }}
-
-
- {% endfor %} -
-{% endif %} diff --git a/templates/exception-context_vars.twig b/templates/exception-context_vars.twig new file mode 100644 index 00000000..ad8ef23f --- /dev/null +++ b/templates/exception-context_vars.twig @@ -0,0 +1,19 @@ +

Context variables

+ +
+ {% for var in exception.getContext %} + {% set showFull = loop.index0 < 10 %} + {% set varId = 'context-variable-' ~ loop.index %} + +
+ +
+
+
{{ var.dumpAsString }}
+
+
+
+ {% endfor %} +
diff --git a/templates/exception-css.twig b/templates/exception-css.twig index f9b9f4ba..425739d5 100644 --- a/templates/exception-css.twig +++ b/templates/exception-css.twig @@ -11,12 +11,22 @@ {% endfor %} {% endautoescape %} diff --git a/templates/exception-js.twig b/templates/exception-js.twig new file mode 100644 index 00000000..743bb8f2 --- /dev/null +++ b/templates/exception-js.twig @@ -0,0 +1,54 @@ +{% autoescape 'html_attr' %} + {% for js in resources.js %} + + {% endfor %} +{% endautoescape %} + + diff --git a/templates/exception-menu.twig b/templates/exception-menu.twig new file mode 100644 index 00000000..813d0cbb --- /dev/null +++ b/templates/exception-menu.twig @@ -0,0 +1,19 @@ +{% import 'macros.twig' as functions %} + + diff --git a/templates/exception-step-body-arguments.twig b/templates/exception-step-body-arguments.twig index ae83834b..1e8a0795 100644 --- a/templates/exception-step-body-arguments.twig +++ b/templates/exception-step-body-arguments.twig @@ -1,12 +1,12 @@ {% set unknownCounter = 0 %} {% set argIndex = -1 %} -
-
+
+
{% for keyParam, param in arguments %} {% set argIndex = argIndex + 1 %} {% set showFull = argIndex < 5 %} -
-
+
+
{% if param.hasDeclaration() %} {% set declaration = param.getDeclaration() %} @@ -46,9 +46,9 @@
-
+
{% if param.hasValue() %}
{{ param.getValue().dumpAsString() }}
{% else %} diff --git a/templates/exception-step-body-source_code.twig b/templates/exception-step-body-source_code.twig index 132e2eec..329c97e8 100644 --- a/templates/exception-step-body-source_code.twig +++ b/templates/exception-step-body-source_code.twig @@ -1,4 +1,4 @@ -
+
{% if step.hasPlaceInCode() %} {% set placeInCode = step.getPlaceInCode() %} {% set lines = placeInCode.getAdjacentCode(15) %} @@ -20,6 +20,6 @@ {% endfor %}
{% else %} -

Source code does not exist for this step.

+

Source code does not exist for this step.

{% endif %}
diff --git a/templates/exception-step-body.twig b/templates/exception-step-body.twig index 19bb7585..38128fc6 100644 --- a/templates/exception-step-body.twig +++ b/templates/exception-step-body.twig @@ -1,18 +1,16 @@ {% set arguments = step.getArguments() %} -
+
{% set argumentsTab = arguments|length > 0 and not step.hasPlaceInCode() %} {% if arguments|length > 0 %} -
- -
+ {% endif %}
diff --git a/templates/exception-step.twig b/templates/exception-step.twig index 0982fbee..4f9720d8 100644 --- a/templates/exception-step.twig +++ b/templates/exception-step.twig @@ -1,5 +1,5 @@ -
-
+
+
{% if step.hasPlaceInCode() %} {% set placeInCode = step.getPlaceInCode() %} diff --git a/templates/exception.twig b/templates/exception.twig index e9934d07..97f5914b 100644 --- a/templates/exception.twig +++ b/templates/exception.twig @@ -9,33 +9,25 @@
-

Error!

+ {{ constant('Awesomite\\ErrorDumper\\Views\\ViewHtml::TAG_UNDER_MENU')|raw }} - {{ constant('Awesomite\\ErrorDumper\\Views\\ViewHtml::TAG_UNDER_TITLE')|raw }} - - {{ contentUnderTitle|raw }} + {{ contentUnderMenu|raw }} {% set hasContext = exception.getContext|length > 0 %} -
- -
- {{ functions.exceptionChain(exception, editor, hasEditor) }} - {% if hasContext %} -
- {% include 'exception-context.twig' %} -
- {% endif %} -
+ {% include 'exception-menu.twig' %} + +
+ {{ functions.exceptionChain(exception, editor, hasEditor) }} + {% if hasContext %} +
+ {% include 'exception-context_vars.twig' %} +
+ {% endif %}
-

+

Generated by ErrorDumper • PHP {{ constant('PHP_VERSION') }} • {{ memoryUsage() }} @@ -43,21 +35,13 @@

-{% autoescape 'html_attr' %} - {% for js in resources.js %} - - {% endfor %} -{% endautoescape %} - {{ constant('Awesomite\\ErrorDumper\\Views\\ViewHtml::TAG_HTML')|raw }} {% for part in appendToBody %} {{ part|raw }} {% endfor %} + +{% include 'exception-js.twig' %} + diff --git a/templates/macros.twig b/templates/macros.twig index dfa5b87a..79e57b12 100644 --- a/templates/macros.twig +++ b/templates/macros.twig @@ -1,10 +1,11 @@ {% macro exceptionChainPills(exception, _no) %} {% import _self as functions %} + {% set no = _no|default(1) %} -
  • - - {{ exception.getOriginalClass() }} +
  • {{ exception.getOriginalClass() }}{% if no > 1 or exception.hasPrevious() %} #{{ no }}{% endif %}
  • {% if exception.hasPrevious() %}{{ functions.exceptionChainPills(exception.getPrevious(), no+1) }}{% endif %}{% endmacro %}{% macro exception(exception, editor, hasEditor, no) %}

    Exception

    {% set class = exception.getOriginalClass() %}{% set message = exception.getMessage() %}
    {{ class }} (code {{ exception.getCode() }}){{ message != '' ? ' ' ~ message }}

    Stack trace

    {% set stepIndex = -1 %}{% for step in exception.getStackTrace() %}{% set stepIndex = stepIndex + 1 %}{% set stepKey = 'e' ~ no ~ '-s' ~ stepIndex %}{% include 'exception-step.twig' %}{% endfor %}{% endmacro %}{% macro exceptionChain(exception, editor, hasEditor, _no) %}{% import _self as functions %}{% set no = _no|default(1) %}
    {{ functions.exception(exception, editor, hasEditor, no) }}
    {% if exception.hasPrevious() %}{{ functions.exceptionChain(exception.getPrevious(), editor, hasEditor, no+1) }}{% endif %}{% endmacro %} +{% macro exceptionChainPills(exception, _no) %}{% import _self as functions %}{% set no = _no|default(1) %}{% if exception.hasPrevious() %}{{ functions.exceptionChainPills(exception.getPrevious(), no+1) }}{% endif %}{% endmacro %}{% macro exception(exception, editor, hasEditor, no) %}

    Exception

    {% set class = exception.getOriginalClass() %}{% set message = exception.getMessage() %}

    Stack trace

    {% for step in exception.getStackTrace() %}{% set stepIndex = loop.index0 %}{% set stepKey = 'e' ~ no ~ '-s' ~ stepIndex %}{% include 'exception-step.twig' %}{% endfor %}{% endmacro %}{% macro exceptionChain(exception, editor, hasEditor, _no) %}{% import _self as functions %}{% set no = _no|default(1) %}
    {{ functions.exception(exception, editor, hasEditor, no) }}
    {% if exception.hasPrevious() %}{{ functions.exceptionChain(exception.getPrevious(), editor, hasEditor, no+1) }}{% endif %}{% endmacro %} diff --git a/tests/TemplatesTest.php b/tests/TemplatesTest.php index a92995c1..0e954c2d 100644 --- a/tests/TemplatesTest.php +++ b/tests/TemplatesTest.php @@ -27,7 +27,7 @@ public function testSingleLine() $this->assertGreaterThan(0, \count($finder)); foreach ($finder as $file) { - $this->assertSame(1, \mb_substr_count($file->getContents(), "\n")); + $this->assertSame(1, \mb_substr_count($file->getContents(), "\n"), $file->getFilename()); } } } diff --git a/tests/Views/ViewHtmlTest.php b/tests/Views/ViewHtmlTest.php index bf1210ce..720dd534 100644 --- a/tests/Views/ViewHtmlTest.php +++ b/tests/Views/ViewHtmlTest.php @@ -99,25 +99,30 @@ public function providerDisplay() } /** - * @dataProvider providerContentUnderTitle + * @dataProvider providerContentUnderMenu * * @param string $content */ - public function testContentUnderTitle($content) + public function testContentUnderMenu($content) { $view = new ViewHtml(); - $this->assertSame($view, $view->setContentUnderTitle($content)); + $this->assertSame($view, $view->setContentUnderMenu($content)); \ob_start(); $view->display(new SerializableException(new \Exception(), 1)); $output = \ob_get_contents(); \ob_end_clean(); - $this->assertContains($content, $output); + $this->assertContains((string)$content, $output); } - public function providerContentUnderTitle() + public function providerContentUnderMenu() { return array( array('Test'), + array( + new Stringable(function () { + return 'Test'; + }), + ), array('Test'), ); }