Skip to content

Commit

Permalink
Validation contex: add raw data and use same context in nested valida…
Browse files Browse the repository at this point in the history
…tions (#426)

* Add raw data to validation context

* In `ValidationContext` change `getValidator()` to `validate()`

* Use same context in nested validations
  • Loading branch information
vjik committed Dec 6, 2022
1 parent bb41954 commit 3270502
Show file tree
Hide file tree
Showing 9 changed files with 62 additions and 20 deletions.
2 changes: 1 addition & 1 deletion src/Rule/CompositeHandler.php
Expand Up @@ -40,6 +40,6 @@ public function validate(mixed $value, object $rule, ValidationContext $context)
throw new UnexpectedRuleException(Composite::class, $rule);
}

return $context->getValidator()->validate($value, $rule->getRules());
return $context->validate($value, $rule->getRules());
}
}
2 changes: 1 addition & 1 deletion src/Rule/EachHandler.php
Expand Up @@ -46,7 +46,7 @@ public function validate(mixed $value, object $rule, ValidationContext $context)
return $result;
}

$itemResult = $context->getValidator()->validate($item, $rules);
$itemResult = $context->validate($item, $rules);
if ($itemResult->isValid()) {
continue;
}
Expand Down
4 changes: 2 additions & 2 deletions src/Rule/NestedHandler.php
Expand Up @@ -61,7 +61,7 @@ public function validate(mixed $value, object $rule, ValidationContext $context)

$dataSet = new ObjectDataSet($value, $rule->getPropertyVisibility());

return $context->getValidator()->validate($dataSet);
return $context->validate($dataSet);
}

if (is_array($value)) {
Expand Down Expand Up @@ -108,7 +108,7 @@ public function validate(mixed $value, object $rule, ValidationContext $context)
$validatedValue = ArrayHelper::getValueByPath($data, $valuePath);
$rules = is_iterable($rules) ? $rules : [$rules];

$itemResult = $context->getValidator()->validate($validatedValue, $rules);
$itemResult = $context->validate($validatedValue, $rules);
if ($itemResult->isValid()) {
continue;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Rule/StopOnErrorHandler.php
Expand Up @@ -44,7 +44,7 @@ public function validate(mixed $value, object $rule, ValidationContext $context)
foreach ($rule->getRules() as $relatedRule) {
$rules = [$relatedRule];

$lastResult = $context->getValidator()->validate($value, $rules);
$lastResult = $context->validate($value, $rules);
$results[] = $lastResult;

if (!$lastResult->isValid()) {
Expand Down
40 changes: 38 additions & 2 deletions src/ValidationContext.php
Expand Up @@ -5,28 +5,64 @@
namespace Yiisoft\Validator;

use Yiisoft\Arrays\ArrayHelper;
use Yiisoft\Validator\Helper\DataSetNormalizer;

/**
* Validation context that might be taken into account when performing validation.
*
* @psalm-import-type RulesType from ValidatorInterface
*/
final class ValidationContext
{
/**
* @param DataSetInterface|null $dataSet Data set the attribute belongs to. Null if a single value is validated.
* @param mixed $rawData The raw validated data.
* @param string|null $attribute Validated attribute name. Null if a single value is validated.
* @param array $parameters Arbitrary parameters.
*/
public function __construct(
private ValidatorInterface $validator,
private mixed $rawData,
private ?DataSetInterface $dataSet = null,
private ?string $attribute = null,
private array $parameters = []
) {
}

public function getValidator(): ValidatorInterface
/**
* Validate data in current context.
*
* @param DataSetInterface|mixed|RulesProviderInterface $data Data set to validate. If {@see RulesProviderInterface}
* instance provided and rules are not specified explicitly, they are read from the
* {@see RulesProviderInterface::getRules()}.
* @param callable|iterable|object|string|null $rules Rules to apply. If specified, rules are not read from data set
* even if it is an instance of {@see RulesProviderInterface}.
*
* @psalm-param RulesType $rules
*/
public function validate(mixed $data, callable|iterable|object|string|null $rules = null): Result
{
$currentDataSet = $this->dataSet;
$currentAttribute = $this->attribute;

$dataSet = DataSetNormalizer::normalize($data);
$this->dataSet = $dataSet;
$this->attribute = null;

$result = $this->validator->validate($dataSet, $rules, $this);

$this->dataSet = $currentDataSet;
$this->attribute = $currentAttribute;

return $result;
}

/**
* @return mixed The raw validated data.
*/
public function getRawData(): mixed
{
return $this->validator;
return $this->rawData;
}

/**
Expand Down
14 changes: 7 additions & 7 deletions src/Validator.php
Expand Up @@ -61,26 +61,26 @@ public function validate(
callable|iterable|object|string|null $rules = null,
?ValidationContext $context = null
): Result {
$data = DataSetNormalizer::normalize($data);
$dataSet = DataSetNormalizer::normalize($data);
$rules = RulesNormalizer::normalize(
$rules,
$data,
$dataSet,
$this->defaultSkipOnEmptyCriteria
);

$compoundResult = new Result();
$context ??= new ValidationContext($this, $data);
$context ??= new ValidationContext($this, $data, $dataSet);
$results = [];

foreach ($rules as $attribute => $attributeRules) {
$result = new Result();

if (is_int($attribute)) {
/** @psalm-suppress MixedAssignment */
$validatedData = $data->getData();
$validatedData = $dataSet->getData();
} else {
/** @psalm-suppress MixedAssignment */
$validatedData = $data->getAttributeValue($attribute);
$validatedData = $dataSet->getAttributeValue($attribute);
$context->setAttribute($attribute);
}

Expand All @@ -107,8 +107,8 @@ public function validate(
}
}

if ($data instanceof PostValidationHookInterface) {
$data->processValidationResult($compoundResult);
if ($dataSet instanceof PostValidationHookInterface) {
$dataSet->processValidationResult($compoundResult);
}

return $compoundResult;
Expand Down
2 changes: 1 addition & 1 deletion tests/Rule/LimitHandlerTraitTest.php
Expand Up @@ -81,7 +81,7 @@ public function checkValidateLimits(
$this->validateLimits($rule, $context, $number, $result);
}
};
$context = new ValidationContext(ValidatorFactory::make(), dataSet: null);
$context = new ValidationContext(ValidatorFactory::make(), null);

$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('$rule must implement both LimitInterface and RuleInterface.');
Expand Down
14 changes: 10 additions & 4 deletions tests/ValidationContextTest.php
Expand Up @@ -13,34 +13,40 @@ final class ValidationContextTest extends TestCase
{
public function testDefault(): void
{
$context = new ValidationContext(ValidatorFactory::make());
$validator = ValidatorFactory::make();

$context = new ValidationContext($validator, 7);

$this->assertSame(7, $context->getRawData());
$this->assertNull($context->getDataSet());
$this->assertNull($context->getAttribute());
$this->assertSame([], $context->getParameters());
}

public function testConstructor(): void
{
$data = ['x' => 7];
$dataSet = new ArrayDataSet();

$context = new ValidationContext(ValidatorFactory::make(), $dataSet, 'name', ['key' => 42]);
$context = new ValidationContext(ValidatorFactory::make(), $data, $dataSet, 'name', ['key' => 42]);

$this->assertSame($data, $context->getRawData());
$this->assertSame($dataSet, $context->getDataSet());
$this->assertSame('name', $context->getAttribute());
$this->assertSame(['key' => 42], $context->getParameters());
}

public function testSetParameter(): void
{
$context = new ValidationContext(ValidatorFactory::make());
$context = new ValidationContext(ValidatorFactory::make(), null);
$context->setParameter('key', 42);

$this->assertSame(['key' => 42], $context->getParameters());
}

public function testGetParameter(): void
{
$context = new ValidationContext(ValidatorFactory::make(), null, null, ['key' => 42]);
$context = new ValidationContext(ValidatorFactory::make(), null, parameters: ['key' => 42]);

$this->assertSame(42, $context->getParameter('key'));
$this->assertNull($context->getParameter('non-exists'));
Expand Down
2 changes: 1 addition & 1 deletion tests/ValidatorTest.php
Expand Up @@ -1213,7 +1213,7 @@ public function validate(
?ValidationContext $context = null
): Result {
$dataSet = DataSetNormalizer::normalize($data);
$context ??= new ValidationContext($this, $dataSet);
$context ??= new ValidationContext($this, $data, $dataSet);

$result = $this->validator->validate($data, $rules, $context);

Expand Down

0 comments on commit 3270502

Please sign in to comment.