Skip to content

Commit

Permalink
Merge pull request #5 from PackageFactory/task/improveErrorRendering
Browse files Browse the repository at this point in the history
FEATURE: Improve error rendering
  • Loading branch information
mficzel authored Nov 22, 2018
2 parents f9bf281 + d966794 commit 8c99026
Showing 9 changed files with 162 additions and 19 deletions.
19 changes: 4 additions & 15 deletions Classes/Aspects/PropTypeValidationAspect.php
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
use Neos\Error\Messages\Error;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Aop\JoinPointInterface;
use Neos\Fusion\Exception as FusionException;
use PackageFactory\AtomicFusion\PropTypes\Error\Exception\PropTypeException;
use Neos\Error\Messages\Result;
use Neos\Flow\Validation\Validator\ValidatorInterface;
use Neos\Utility\ObjectAccess;
@@ -60,21 +60,10 @@ protected function validateFusionPropTypes(JoinPointInterface $joinPoint)
}

if ($result->hasErrors()) {
$message = '';
$flattenedErrors = $result->getFlattenedErrors();
foreach ($flattenedErrors as $path => $errors) {
$message .= sprintf('%s: %s', $path, implode(', ', $errors));
}

$prototypeName = ObjectAccess::getProperty($fusionComponentImplementation, 'fusionObjectName', true);
$path = ObjectAccess::getProperty($fusionComponentImplementation, 'path', true);

throw new FusionException(sprintf(
'The @propTypes-validation for %s at path "%s" returned the following errors: %s',
$prototypeName,
$path,
$message
));
$exception = new PropTypeException(sprintf('The PropType validation for prototype %s failed', $prototypeName));
$exception->setResult($result);
throw $exception;
}
return $joinPoint->getAdviceChain()->proceed($joinPoint);
}
32 changes: 32 additions & 0 deletions Classes/Error/Exception/PropTypeException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php
namespace PackageFactory\AtomicFusion\PropTypes\Error\Exception;

use Neos\Fusion\Exception as FusionException;
use Neos\Error\Messages\Result;
use Throwable;

class PropTypeException extends FusionException
{
/**
* @var Result $result
*/
protected $result;

/**
* @param Result $result
*/
public function setResult(Result $result): void
{
$this->result = $result;
}

/**
* @return Result
*/
public function getResult(): Result
{
return $this->result;
}


}
108 changes: 108 additions & 0 deletions Classes/Error/ExceptionHandler/PropTypeExceptionHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php

namespace PackageFactory\AtomicFusion\PropTypes\Error\ExceptionHandler;

use Neos\Error\Messages\Error;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Log\SystemLoggerInterface;
use Neos\Fusion\Core\ExceptionHandlers\BubblingHandler;
use PackageFactory\AtomicFusion\PropTypes\Error\Exception\PropTypeException;

class PropTypeExceptionHandler extends BubblingHandler
{

/**
* @var SystemLoggerInterface
* @Flow\Inject
*/
protected $systemLogger;

/**
* Whether or not to render technical details (i.e. the Fusion stacktrace) in the exception message
*
* @var bool
*/
private $renderTechnicalDetails;

/**
* @param bool $renderTechnicalDetails whether or not to render technical details (i.e. the Fusion stacktrace) in the exception message
*/
public function __construct(bool $renderTechnicalDetails = true)
{
$this->renderTechnicalDetails = $renderTechnicalDetails;
}

/**
* In all aspects other than handling of PropTypeException this behaves
* exactly as the BubblingHandler which is the internal fusion exception handler
* by default.
*
* @param array $fusionPath
* @param \Exception $exception
* @return string
* @throws \Neos\Flow\Configuration\Exception\InvalidConfigurationException
* @throws \Neos\Flow\Mvc\Exception\StopActionException
*/
public function handleRenderingException($fusionPath, \Exception $exception)
{
if ($exception instanceof PropTypeException) {
return $this->handlePropTypeException($fusionPath, $exception, $exception->getReferenceCode());
} else {
parent::handleRenderingException($fusionPath, $exception);
}
}

/**
* Renders the exception in HTML for display
*
* @param string $fusionPath path causing the exception
* @param \Exception $exception exception to handle
* @param integer $referenceCode
* @return string
*/
protected function handlePropTypeException($fusionPath, PropTypeException $exception, $referenceCode)
{
$messageBody = sprintf('<p class="neos-message-content">%s</p>', htmlspecialchars($exception->getMessage()));

if ($this->renderTechnicalDetails) {
$result = $exception->getResult();
if ($result && $result->hasErrors()) {
$errors = $result->getFlattenedErrors();
$renderedErrors = [];
foreach ($errors as $path => $pathErrors) {
foreach ($pathErrors as $error) {
$renderedErrors[] = sprintf('%s: %s', $path, $error->render());
}
}
$messageBody .= sprintf('<p class="neos-message-stacktrace">The following errors were detected:<br/><code style="white-space: pre-wrap"> - %s</code></p>', implode(chr(10) . ' - ', $renderedErrors));
$messageBody .= sprintf('<p class="neos-message-stacktrace"><code>FusionPath: %s</code></p>', htmlspecialchars($fusionPath));

}
}

if ($referenceCode) {
$messageBody .= sprintf('<p class="neos-reference-code">%s</p>', $this->formatErrorCodeMessage($referenceCode));
}

$message = sprintf(
'<div class="neos-message-header"><div class="neos-message-icon"><i class="icon-warning-sign"></i></div><h1>An exception was thrown while Neos tried to render your page</h1></div>' .
'<div class="neos-message-wrapper">%s</div>',
$messageBody
);

$this->systemLogger->logException($exception);
return $message;
}

/**
* Renders a message depicting the user where to find further information
* for the given reference code.
*
* @param integer $referenceCode
* @return string A rendered message with the reference code containing HTML
*/
protected function formatErrorCodeMessage($referenceCode)
{
return ($referenceCode ? 'For a full stacktrace, open <code>Data/Logs/Exceptions/' . $referenceCode . '.txt</code>' : '');
}
}
2 changes: 1 addition & 1 deletion Classes/Validators/ArrayOfValidator.php
Original file line number Diff line number Diff line change
@@ -39,7 +39,7 @@ protected function isValid($value)
foreach ($value as $key => $item) {
$itemResult = $itemValidator->validate($item);
if ($itemResult->hasErrors()) {
$this->addError('Item %s is not valid', 1515003545, [$key]);
$this->result->forProperty($key)->merge($itemResult);
}
}
} else {
2 changes: 1 addition & 1 deletion Classes/Validators/DataStructureValidator.php
Original file line number Diff line number Diff line change
@@ -49,7 +49,7 @@ protected function isValid($value)
if ($subValidator instanceof ValidatorInterface) {
$subResult = $subValidator->validate($subValue);
if ($subResult->hasErrors()) {
$this->addError('DataStructure-Property %s is not valid', 1515003533, [$key]);
$this->result->forProperty($key)->merge($subResult);
}
}
}
2 changes: 1 addition & 1 deletion Classes/Validators/IntegerValidator.php
Original file line number Diff line number Diff line change
@@ -22,6 +22,6 @@ protected function isValid($value)
if (is_null($value) || is_int($value)) {
return;
}
$this->addError('A valid float is expected.', 1514998717);
$this->addError('A valid integer is expected.', 1514998717);
}
}
2 changes: 1 addition & 1 deletion Classes/Validators/ShapeValidator.php
Original file line number Diff line number Diff line change
@@ -49,7 +49,7 @@ protected function isValid($value)
if ($subValidator instanceof ValidatorInterface) {
$subResult = $subValidator->validate($subValue);
if ($subResult->hasErrors()) {
$this->addError('Shape-Property %s is not valid', 1515003533, [$key]);
$this->result->forProperty($key)->merge($subResult);
}
}
}
6 changes: 6 additions & 0 deletions Configuration/Development/Settings.yaml
Original file line number Diff line number Diff line change
@@ -2,3 +2,9 @@ PackageFactory:
AtomicFusion:
PropTypes:
enable: true

Neos:
Neos:
fusion:
autoInclude:
'PackageFactory.AtomicFusion.PropTypes': true
8 changes: 8 additions & 0 deletions Resources/Private/Fusion/Root.fusion
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// this is only included in dev context
prototype(Neos.Fusion:Component) {
@exceptionHandler = 'PackageFactory\\AtomicFusion\\PropTypes\\Error\\ExceptionHandler\\PropTypeExceptionHandler'
}

prototype(PackageFactory.AtomicFusion:Component) {
@exceptionHandler = 'PackageFactory\\AtomicFusion\\PropTypes\\Error\\ExceptionHandler\\PropTypeExceptionHandler'
}

0 comments on commit 8c99026

Please sign in to comment.