-
Notifications
You must be signed in to change notification settings - Fork 771
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
There are a few "problems" with the current engine: - Allowing each rule to execute assert() and check() means duplication in some cases. - Because we use exceptions to assert/check, we can only invert a validation (with Not) if there are errors. That means that we have limited granularity control. - There is a lot of logic in the exceptions. That means that even after it throws an exception, something could still happen. We're stable on that front, but I want to simplify them. Besides, debugging exception code is painful because the stack trace does not go beyond the exception. Apart from that, there are many limitations with templating, and working that out in the current implementation makes it much harder. These changes will improve the library in many aspects, but they will also change the behavior and break backward compatibility. However, that's a price I'm willing to pay for the improvements we'll have. Signed-off-by: Henrique Moody <[email protected]>
- Loading branch information
1 parent
3a7ac02
commit 238f2d5
Showing
62 changed files
with
2,381 additions
and
213 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<?php | ||
|
||
/* | ||
* Copyright (c) Alexandre Gomes Gaigalas <[email protected]> | ||
* SPDX-License-Identifier: MIT | ||
*/ | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Respect\Validation\Exceptions; | ||
|
||
final class ValidatorException extends \Exception implements Exception | ||
{ | ||
/** @param array<string, mixed> $messages */ | ||
public function __construct( | ||
string $message, | ||
private readonly string $fullMessage, | ||
private readonly array $messages, | ||
) { | ||
parent::__construct($message); | ||
} | ||
|
||
public function getFullMessage(): string | ||
{ | ||
return $this->fullMessage; | ||
} | ||
|
||
/** @return array<string, mixed> */ | ||
public function getMessages(): array | ||
{ | ||
return $this->messages; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<?php | ||
|
||
/* | ||
* Copyright (c) Alexandre Gomes Gaigalas <[email protected]> | ||
* SPDX-License-Identifier: MIT | ||
*/ | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Respect\Validation\Helpers; | ||
|
||
use Respect\Validation\Result; | ||
use Respect\Validation\Validatable; | ||
|
||
trait CanBindEvaluateRule | ||
{ | ||
private function bindEvaluate(Validatable $binded, Validatable $binder, mixed $input): Result | ||
{ | ||
if ($binder->getName() !== null && $binded->getName() === null) { | ||
$binded->setName($binder->getName()); | ||
} | ||
|
||
if ($binder->getTemplate() !== null && $binded->getTemplate() === null) { | ||
$binded->setTemplate($binder->getTemplate()); | ||
} | ||
|
||
return $binded->evaluate($input); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<?php | ||
|
||
/* | ||
* Copyright (c) Alexandre Gomes Gaigalas <[email protected]> | ||
* SPDX-License-Identifier: MIT | ||
*/ | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Respect\Validation\Message; | ||
|
||
use Respect\Validation\Result; | ||
|
||
interface Formatter | ||
{ | ||
/** | ||
* @param array<string, mixed> $templates | ||
*/ | ||
public function main(Result $result, array $templates): string; | ||
|
||
/** | ||
* @param array<string, mixed> $templates | ||
*/ | ||
public function full(Result $result, array $templates, int $depth = 0): string; | ||
|
||
/** | ||
* @param array<string, mixed> $templates | ||
* | ||
* @return array<string, mixed> | ||
*/ | ||
public function array(Result $result, array $templates): array; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<?php | ||
|
||
/* | ||
* Copyright (c) Alexandre Gomes Gaigalas <[email protected]> | ||
* SPDX-License-Identifier: MIT | ||
*/ | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Respect\Validation\Message; | ||
|
||
use Respect\Validation\Result; | ||
|
||
interface Renderer | ||
{ | ||
public function render(Result $result, ?string $template = null): string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
<?php | ||
|
||
/* | ||
* Copyright (c) Alexandre Gomes Gaigalas <[email protected]> | ||
* SPDX-License-Identifier: MIT | ||
*/ | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Respect\Validation\Message; | ||
|
||
use Respect\Validation\Exceptions\ComponentException; | ||
use Respect\Validation\Result; | ||
|
||
use function array_filter; | ||
use function array_key_exists; | ||
use function array_values; | ||
use function count; | ||
use function current; | ||
use function is_array; | ||
use function is_string; | ||
use function Respect\Stringifier\stringify; | ||
use function rtrim; | ||
use function sprintf; | ||
use function str_repeat; | ||
|
||
use const PHP_EOL; | ||
|
||
final class StandardFormatter implements Formatter | ||
{ | ||
public function __construct( | ||
private readonly Renderer $renderer, | ||
) { | ||
} | ||
|
||
/** | ||
* @param array<string, mixed> $templates | ||
*/ | ||
public function main(Result $result, array $templates): string | ||
{ | ||
$selectedTemplates = $this->selectTemplates($result, $templates); | ||
if (!$this->isFinalTemplate($result, $selectedTemplates)) { | ||
foreach ($this->extractDeduplicatedChildren($result) as $child) { | ||
return $this->main($child, $selectedTemplates); | ||
} | ||
} | ||
|
||
return $this->renderer->render($this->getTemplated($result, $selectedTemplates)); | ||
} | ||
|
||
/** | ||
* @param array<string, mixed> $templates | ||
*/ | ||
public function full(Result $result, array $templates, int $depth = 0): string | ||
{ | ||
$selectedTemplates = $this->selectTemplates($result, $templates); | ||
$isFinalTemplate = $this->isFinalTemplate($result, $selectedTemplates); | ||
|
||
$rendered = ''; | ||
if ($result->isAlwaysVisible() || $isFinalTemplate) { | ||
$indentation = str_repeat(' ', $depth * 2); | ||
$rendered .= sprintf( | ||
'%s- %s' . PHP_EOL, | ||
$indentation, | ||
$this->renderer->render($this->getTemplated($result, $selectedTemplates)), | ||
); | ||
$depth++; | ||
} | ||
|
||
if (!$isFinalTemplate) { | ||
foreach ($this->extractDeduplicatedChildren($result) as $child) { | ||
$rendered .= $this->full($child, $selectedTemplates, $depth); | ||
$rendered .= PHP_EOL; | ||
} | ||
} | ||
|
||
return rtrim($rendered, PHP_EOL); | ||
} | ||
|
||
/** | ||
* @param array<string, mixed> $templates | ||
* | ||
* @return array<string, mixed> | ||
*/ | ||
public function array(Result $result, array $templates): array | ||
{ | ||
$selectedTemplates = $this->selectTemplates($result, $templates); | ||
$deduplicatedChildren = $this->extractDeduplicatedChildren($result); | ||
if (count($deduplicatedChildren) === 0 || $this->isFinalTemplate($result, $selectedTemplates)) { | ||
return [$result->id => $this->renderer->render($this->getTemplated($result, $selectedTemplates))]; | ||
} | ||
|
||
$messages = []; | ||
foreach ($deduplicatedChildren as $child) { | ||
$messages[$child->id] = $this->array($child, $this->selectTemplates($child, $selectedTemplates)); | ||
if (count($messages[$child->id]) !== 1) { | ||
continue; | ||
} | ||
|
||
$messages[$child->id] = current($messages[$child->id]); | ||
} | ||
|
||
return $messages; | ||
} | ||
|
||
/** @param array<string, mixed> $templates */ | ||
private function getTemplated(Result $result, array $templates): Result | ||
{ | ||
if ($result->hasCustomTemplate()) { | ||
return $result; | ||
} | ||
|
||
if (!isset($templates[$result->id]) && isset($templates['__self__'])) { | ||
return $result->withTemplate($templates['__self__']); | ||
} | ||
|
||
if (!isset($templates[$result->id])) { | ||
return $result; | ||
} | ||
|
||
$template = $templates[$result->id]; | ||
if (is_string($template)) { | ||
return $result->withTemplate($template); | ||
} | ||
|
||
throw new ComponentException( | ||
sprintf('Template for "%s" must be a string, %s given', $result->id, stringify($template)) | ||
); | ||
} | ||
|
||
/** | ||
* @param array<string, mixed> $templates | ||
*/ | ||
private function isFinalTemplate(Result $result, array $templates): bool | ||
{ | ||
if (isset($templates[$result->id]) && is_string($templates[$result->id])) { | ||
return true; | ||
} | ||
|
||
if (count($templates) !== 1) { | ||
return false; | ||
} | ||
|
||
return isset($templates['__self__']) || isset($templates[$result->id]); | ||
} | ||
|
||
/** | ||
* @param array<string, mixed> $templates | ||
* | ||
* @return array<string, mixed> | ||
*/ | ||
private function selectTemplates(Result $message, array $templates): array | ||
{ | ||
if (isset($templates[$message->id]) && is_array($templates[$message->id])) { | ||
return $templates[$message->id]; | ||
} | ||
|
||
return $templates; | ||
} | ||
|
||
/** @return array<Result> */ | ||
private function extractDeduplicatedChildren(Result $result): array | ||
{ | ||
/** @var array<string, Result> $deduplicatedResults */ | ||
$deduplicatedResults = []; | ||
$duplicateCounters = []; | ||
foreach ($result->children as $child) { | ||
$id = $child->id; | ||
if (isset($duplicateCounters[$id])) { | ||
$id .= '.' . ++$duplicateCounters[$id]; | ||
} elseif (array_key_exists($id, $deduplicatedResults)) { | ||
$deduplicatedResults[$id . '.1'] = $deduplicatedResults[$id]?->withId($id . '.1'); | ||
unset($deduplicatedResults[$id]); | ||
$duplicateCounters[$id] = 2; | ||
$id .= '.2'; | ||
} | ||
$deduplicatedResults[$id] = $child->isValid ? null : $child->withId($id); | ||
} | ||
|
||
return array_values(array_filter($deduplicatedResults)); | ||
} | ||
} |
Oops, something went wrong.