Move message formatting out of ValidationException

There should not be too much code in the ValidationException. It is hard
to test and debug code in exceptions because PHP does not trace further
than their constructor.

This commit will move the message formatting from ValidationException
into a Formatter class (inside a Message namespace).

Signed-off-by: Henrique Moody <henriquemoody@gmail.com>
This commit is contained in:
Henrique Moody 2019-04-30 00:31:04 +02:00
parent ab602ae1bb
commit 00f61b9bdc
No known key found for this signature in database
GPG key ID: 221E9281655813A6
7 changed files with 98 additions and 69 deletions

View file

@ -14,11 +14,8 @@ declare(strict_types=1);
namespace Respect\Validation\Exceptions;
use InvalidArgumentException;
use function call_user_func;
use function is_string;
use Respect\Validation\Message\Formatter;
use function key;
use function preg_replace_callback;
use function Respect\Stringifier\stringify;
/**
* Default exception class for rule validations.
@ -67,9 +64,9 @@ class ValidationException extends InvalidArgumentException implements Exception
private $params = [];
/**
* @var callable
* @var Formatter
*/
private $translator;
private $formatter;
/**
* @var string
@ -80,12 +77,12 @@ class ValidationException extends InvalidArgumentException implements Exception
* @param mixed $input
* @param mixed[] $params
*/
public function __construct($input, string $id, array $params, callable $translator)
public function __construct($input, string $id, array $params, Formatter $formatter)
{
$this->input = $input;
$this->id = $id;
$this->params = $params;
$this->translator = $translator;
$this->formatter = $formatter;
$this->template = $this->chooseTemplate();
parent::__construct($this->createMessage());
@ -150,43 +147,10 @@ class ValidationException extends InvalidArgumentException implements Exception
private function createMessage(): string
{
$template = $this->createTemplate($this->mode, $this->template);
$params = $this->getParams();
$params['name'] = $params['name'] ?? stringify($this->input);
$params['input'] = $this->input;
return $this->format($template, $params);
}
private function createTemplate(string $mode, string $template): string
{
if (isset($this->defaultTemplates[$mode][$template])) {
$template = $this->defaultTemplates[$mode][$template];
}
return call_user_func($this->translator, $template);
}
/**
* @param mixed[] $vars
*/
private function format(string $template, array $vars = []): string
{
return preg_replace_callback(
'/{{(\w+)}}/',
static function ($match) use ($vars) {
if (!isset($vars[$match[1]])) {
return $match[0];
}
$value = $vars[$match[1]];
if ($match[1] == 'name' && is_string($value)) {
return $value;
}
return stringify($value);
},
$template
return $this->formatter->format(
$this->defaultTemplates[$this->mode][$this->template] ?? $this->template,
$this->input,
$this->params
);
}
}

View file

@ -19,6 +19,7 @@ use ReflectionObject;
use Respect\Validation\Exceptions\ComponentException;
use Respect\Validation\Exceptions\InvalidClassException;
use Respect\Validation\Exceptions\ValidationException;
use Respect\Validation\Message\Formatter;
use function lcfirst;
use function sprintf;
use function trim;
@ -148,7 +149,7 @@ final class Factory
}
}
return new ValidationException($input, $id, $params, $this->translator);
return new ValidationException($input, $id, $params, new Formatter($this->translator));
}
/**
@ -188,7 +189,7 @@ final class Factory
): ValidationException {
/** @var ValidationException $exception */
$exception = $this->createReflectionClass($exceptionName, ValidationException::class)
->newInstance($input, $id, $params, $this->translator);
->newInstance($input, $id, $params, new Formatter($this->translator));
if (isset($params['template'])) {
$exception->updateTemplate($params['template']);
}

View file

@ -0,0 +1,58 @@
<?php
/*
* This file is part of Respect/Validation.
*
* (c) Alexandre Gomes Gaigalas <alexandre@gaigalas.net>
*
* For the full copyright and license information, please view the "LICENSE.md"
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Respect\Validation\Message;
use function call_user_func;
use function is_string;
use function preg_replace_callback;
use function Respect\Stringifier\stringify;
final class Formatter
{
/**
* @var callable
*/
private $translator;
public function __construct(callable $translator)
{
$this->translator = $translator;
}
/**
* @param mixed $input
* @param mixed[] $parameters
*/
public function format(string $template, $input, array $parameters): string
{
$parameters['name'] = $parameters['name'] ?? stringify($input);
return preg_replace_callback(
'/{{(\w+)}}/',
static function ($match) use ($parameters) {
if (!isset($parameters[$match[1]])) {
return $match[0];
}
$value = $parameters[$match[1]];
if ($match[1] == 'name' && is_string($value)) {
return $value;
}
return stringify($value);
},
call_user_func($this->translator, $template)
);
}
}

View file

@ -14,6 +14,7 @@ declare(strict_types=1);
namespace Respect\Validation\Test;
use Respect\Validation\Exceptions\ValidationException;
use Respect\Validation\Message\Formatter;
use Respect\Validation\Validatable;
use function realpath;
use function sprintf;
@ -99,7 +100,7 @@ abstract class RuleTestCase extends TestCase
'validatable',
'input',
[],
'trim'
new Formatter('strval')
);
$checkException->updateTemplate(sprintf('Exception for %s:check() method', $mockClassName));
$validatableMocked
@ -110,7 +111,7 @@ abstract class RuleTestCase extends TestCase
'validatable',
'input',
[],
'trim'
new Formatter('strval')
);
$assertException->updateTemplate(sprintf('Exception for %s:assert() method', $mockClassName));
$validatableMocked

View file

@ -13,6 +13,7 @@ declare(strict_types=1);
namespace Respect\Validation\Exceptions;
use Respect\Validation\Message\Formatter;
use Respect\Validation\Test\TestCase;
/**
@ -29,8 +30,8 @@ final class NestedValidationExceptionTest extends TestCase
*/
public function getChildrenShouldReturnExceptionAddedByAddRelated(): void
{
$composite = new AttributeException('input', 'id', [], 'trim');
$node = new IntValException('input', 'id', [], 'trim');
$composite = new AttributeException('input', 'id', [], new Formatter('strval'));
$node = new IntValException('input', 'id', [], new Formatter('strval'));
$composite->addChild($node);
self::assertCount(1, $composite->getChildren());
self::assertContainsOnly(IntValException::class, $composite->getChildren());
@ -41,8 +42,8 @@ final class NestedValidationExceptionTest extends TestCase
*/
public function addingTheSameInstanceShouldAddJustOneSingleReference(): void
{
$composite = new AttributeException('input', 'id', [], 'trim');
$node = new IntValException('input', 'id', [], 'trim');
$composite = new AttributeException('input', 'id', [], new Formatter('strval'));
$node = new IntValException('input', 'id', [], new Formatter('strval'));
$composite->addChild($node);
$composite->addChild($node);
$composite->addChild($node);

View file

@ -13,6 +13,7 @@ declare(strict_types=1);
namespace Respect\Validation\Exceptions;
use Respect\Validation\Message\Formatter;
use Respect\Validation\Test\TestCase;
use function trim;
@ -32,7 +33,7 @@ final class ValidationExceptionTest extends TestCase
*/
public function itShouldImplementException(): void
{
$sut = new ValidationException('input', 'id', [], 'trim');
$sut = new ValidationException('input', 'id', [], new Formatter('strval'));
self::assertInstanceOf(Exception::class, $sut);
}
@ -43,7 +44,7 @@ final class ValidationExceptionTest extends TestCase
public function itShouldRetrieveId(): void
{
$id = 'my id';
$sut = new ValidationException('input', $id, [], 'trim');
$sut = new ValidationException('input', $id, [], new Formatter('strval'));
self::assertSame($id, $sut->getId());
}
@ -55,7 +56,7 @@ final class ValidationExceptionTest extends TestCase
{
$params = ['foo' => true, 'bar' => 23];
$sut = new ValidationException('input', 'id', $params, 'trim');
$sut = new ValidationException('input', 'id', $params, new Formatter('strval'));
self::assertSame($params, $sut->getParams());
}
@ -68,7 +69,7 @@ final class ValidationExceptionTest extends TestCase
$name = 'any name';
$value = 'any value';
$sut = new ValidationException('input', 'id', [$name => $value], 'trim');
$sut = new ValidationException('input', 'id', [$name => $value], new Formatter('strval'));
self::assertSame($value, $sut->getParam($name));
}
@ -78,7 +79,7 @@ final class ValidationExceptionTest extends TestCase
*/
public function itShouldReturnNullWhenParameterCanNotBeFound(): void
{
$sut = new ValidationException('input', 'id', [], 'trim');
$sut = new ValidationException('input', 'id', [], new Formatter('strval'));
self::assertNull($sut->getParam('foo'));
}
@ -88,7 +89,7 @@ final class ValidationExceptionTest extends TestCase
*/
public function itShouldHaveTemplateByDefault(): void
{
$sut = new ValidationException('input', 'id', [], 'trim');
$sut = new ValidationException('input', 'id', [], new Formatter('strval'));
self::assertSame('"input" must be valid', $sut->getMessage());
}
@ -98,7 +99,7 @@ final class ValidationExceptionTest extends TestCase
*/
public function itShouldUpdateMode(): void
{
$sut = new ValidationException('input', 'id', [], 'trim');
$sut = new ValidationException('input', 'id', [], new Formatter('strval'));
$sut->updateMode(ValidationException::MODE_NEGATIVE);
self::assertSame('"input" must not be valid', $sut->getMessage());
@ -111,7 +112,7 @@ final class ValidationExceptionTest extends TestCase
{
$template = 'This is my new template';
$sut = new ValidationException('input', 'id', [], 'trim');
$sut = new ValidationException('input', 'id', [], new Formatter('strval'));
$sut->updateTemplate($template);
self::assertEquals($template, $sut->getMessage());
@ -122,7 +123,7 @@ final class ValidationExceptionTest extends TestCase
*/
public function itShouldTellWhenHasAsCustomUpdateTemplate(): void
{
$sut = new ValidationException('input', 'id', [], 'trim');
$sut = new ValidationException('input', 'id', [], new Formatter('strval'));
self::assertFalse($sut->hasCustomTemplate());
@ -134,12 +135,12 @@ final class ValidationExceptionTest extends TestCase
/**
* @test
*/
public function itShouldUseTranslator(): void
public function itShouldUseFormatter(): void
{
$template = ' This is my new template ';
$expected = trim($template);
$sut = new ValidationException('input', 'id', [], 'trim');
$sut = new ValidationException('input', 'id', [], new Formatter('trim'));
$sut->updateTemplate($template);
self::assertEquals($expected, $sut->getMessage());
@ -150,7 +151,7 @@ final class ValidationExceptionTest extends TestCase
*/
public function itShouldReplacePlaceholders(): void
{
$sut = new ValidationException('foo', 'id', ['bar' => 1, 'baz' => 2], 'trim');
$sut = new ValidationException('foo', 'id', ['bar' => 1, 'baz' => 2], new Formatter('strval'));
$sut->updateTemplate('{{name}} {{bar}} {{baz}}');
self::assertEquals(
@ -164,7 +165,7 @@ final class ValidationExceptionTest extends TestCase
*/
public function itShouldKeepPlaceholdersThatCanNotReplace(): void
{
$sut = new ValidationException('foo', 'id', ['foo' => 1], 'trim');
$sut = new ValidationException('foo', 'id', ['foo' => 1], new Formatter('strval'));
$sut->updateTemplate('{{name}} {{foo}} {{bar}}');
self::assertEquals(
@ -178,7 +179,7 @@ final class ValidationExceptionTest extends TestCase
*/
public function itShouldUpdateParams(): void
{
$sut = new ValidationException('input', 'id', ['foo' => 1], 'trim');
$sut = new ValidationException('input', 'id', ['foo' => 1], new Formatter('strval'));
$sut->updateTemplate('{{foo}}');
$sut->updateParams(['foo' => 2]);
@ -190,7 +191,7 @@ final class ValidationExceptionTest extends TestCase
*/
public function itShouldConvertToString(): void
{
$sut = new ValidationException('input', 'id', [], 'trim');
$sut = new ValidationException('input', 'id', [], new Formatter('strval'));
self::assertSame('"input" must be valid', (string) $sut);
}

View file

@ -14,6 +14,7 @@ declare(strict_types=1);
namespace Respect\Validation\Rules;
use Respect\Validation\Exceptions\ValidationException;
use Respect\Validation\Message\Formatter;
use Respect\Validation\Test\TestCase;
/**
@ -118,7 +119,9 @@ final class AbstractRuleTest extends TestCase
->expects(self::once())
->method('reportError')
->with($input)
->will(self::throwException(new ValidationException($input, 'abstract', [], 'trim')));
->will(self::throwException(
new ValidationException($input, 'abstract', [], new Formatter('strval'))
));
$abstractRuleMock->assert($input);
}