Skip to content

Commit

Permalink
Add DataWrapperInteface (#464)
Browse files Browse the repository at this point in the history
* base implementation

* improve

* const

* [ci-review] Apply changes from Rector action.

* `DataWrapperInterface`

* Apply fixes from StyleCI

* tests

* improve

* rename

* Apply fixes from StyleCI

* docs

* Apply fixes from StyleCI

* rename const

* Apply fixes from StyleCI

* fix count

* more test

* [ci-review] Apply changes from Rector action.

* more test

* Apply fixes from StyleCI

* Add DataWrapperInterface docs

* Fix merge issue

* fix

Co-authored-by: rector-bot <rector@yiiframework.com>
Co-authored-by: StyleCI Bot <bot@styleci.io>
Co-authored-by: Alexander Makarov <sam@rmcreative.ru>
  • Loading branch information
4 people committed Jan 8, 2023
1 parent 62cd3fa commit b14e96c
Show file tree
Hide file tree
Showing 25 changed files with 153 additions and 101 deletions.
10 changes: 7 additions & 3 deletions src/DataSet/ArrayDataSet.php
Expand Up @@ -4,8 +4,7 @@

namespace Yiisoft\Validator\DataSet;

use Yiisoft\Validator\DataSetInterface;

use Yiisoft\Validator\DataWrapperInterface;
use Yiisoft\Validator\Helper\DataSetNormalizer;

use function array_key_exists;
Expand All @@ -21,7 +20,7 @@
* When using validator, there is no need to wrap your data manually. Array will be automatically wrapped with
* {@see ArrayDataSet} by {@see DataSetNormalizer} during validation.
*/
final class ArrayDataSet implements DataSetInterface
final class ArrayDataSet implements DataWrapperInterface
{
public function __construct(
/**
Expand Down Expand Up @@ -56,6 +55,11 @@ public function getData(): array
return $this->data;
}

public function getSource(): array
{
return $this->data;
}

/**
* Whether this data set has the attribute with a given name. Note that this means existence only and attributes
* with empty values are treated as present too.
Expand Down
17 changes: 12 additions & 5 deletions src/DataSet/ObjectDataSet.php
Expand Up @@ -8,6 +8,7 @@
use Yiisoft\Validator\AttributeTranslatorInterface;
use Yiisoft\Validator\AttributeTranslatorProviderInterface;
use Yiisoft\Validator\DataSetInterface;
use Yiisoft\Validator\DataWrapperInterface;
use Yiisoft\Validator\Helper\ObjectParser;
use Yiisoft\Validator\RulesProvider\AttributesRulesProvider;
use Yiisoft\Validator\RulesProviderInterface;
Expand Down Expand Up @@ -142,7 +143,7 @@
*
* @link https://www.php.net/manual/en/language.attributes.overview.php
*/
final class ObjectDataSet implements RulesProviderInterface, DataSetInterface, AttributeTranslatorProviderInterface
final class ObjectDataSet implements RulesProviderInterface, DataWrapperInterface, AttributeTranslatorProviderInterface
{
/**
* @var bool Whether an {@see $object} provided a data set by implementing {@see DataSetInterface}.
Expand Down Expand Up @@ -259,12 +260,13 @@ public function hasAttribute(string $attribute): bool
}

/**
* Returns the validated data as a whole.
* Returns the validated data as array.
*
* @return mixed Validated data, has mixed type if it was provided via {@see DataSetInterface::getData()}
* implementation, otherwise it's always an associative array - a mapping between property names and their values.
* @return array|null Result of object {@see DataSetInterface::getData()} method, if it was implemented
* {@see DataSetInterface}, otherwise returns the validated data as an associative array - a mapping between
* property names and their values.
*/
public function getData(): mixed
public function getData(): ?array
{
if ($this->dataSetProvided) {
/** @var DataSetInterface $object */
Expand All @@ -275,6 +277,11 @@ public function getData(): mixed
return $this->parser->getData();
}

public function getSource(): object
{
return $this->object;
}

/**
* An optional attribute names translator. It's taken from the {@see $object} when
* {@see AttributeTranslatorProviderInterface} is implemented. In case of it's missing, a `null` value is returned.
Expand Down
16 changes: 9 additions & 7 deletions src/DataSet/SingleValueDataSet.php
Expand Up @@ -4,7 +4,7 @@

namespace Yiisoft\Validator\DataSet;

use Yiisoft\Validator\DataSetInterface;
use Yiisoft\Validator\DataWrapperInterface;

/**
* A data set used for a single value of any (mixed) data type. Does not support attributes.
Expand All @@ -26,7 +26,7 @@
*
* For arrays and objects {@see ArrayDataSet} and {@see ObjectDataSet} can be used accordingly.
*/
final class SingleValueDataSet implements DataSetInterface
final class SingleValueDataSet implements DataWrapperInterface
{
public function __construct(
/**
Expand All @@ -50,12 +50,14 @@ public function getAttributeValue(string $attribute): mixed
}

/**
* A getter for {@see $data} property. Returns the validated data as a whole. In this case the single value itself
* is returned as a data because it can not be decoupled.
*
* @return mixed Single value of any (mixed) data type.
* Returns the validated data as array.
*/
public function getData(): mixed
public function getData(): ?array
{
return null;
}

public function getSource(): mixed
{
return $this->value;
}
Expand Down
7 changes: 4 additions & 3 deletions src/DataSetInterface.php
Expand Up @@ -19,11 +19,12 @@ interface DataSetInterface
public function getAttributeValue(string $attribute): mixed;

/**
* Returns the validated data as a whole.
* Returns the validated data as an associative array, where keys are attribute names and values are their
* corresponding values. `null` means that implementation does not support getting an array of attributes.
*
* @return mixed Validated data.
* @return array|null Validated data as array of attributes or `null` when does not support this.
*/
public function getData(): mixed;
public function getData(): ?array;

/**
* Whether a data set has the attribute with a given name. Note that this means existence only and attributes with
Expand Down
20 changes: 20 additions & 0 deletions src/DataWrapperInterface.php
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Validator;

/**
* Data wrapper interface provides access to raw data behind a data set.
*
* @internal
*/
interface DataWrapperInterface extends DataSetInterface
{
/**
* Get raw data that is wrapped.
*
* @return mixed Raw data.
*/
public function getSource(): mixed;
}
3 changes: 3 additions & 0 deletions src/Rule/AtLeastHandler.php
Expand Up @@ -27,6 +27,9 @@ public function validate(mixed $value, object $rule, ValidationContext $context)
throw new UnexpectedRuleException(AtLeast::class, $rule);
}

/** @var mixed $value */
$value = $context->getParameter(ValidationContext::PARAMETER_VALUE_AS_ARRAY) ?? $value;

$result = new Result();

if (!is_array($value) && !is_object($value)) {
Expand Down
21 changes: 0 additions & 21 deletions src/Rule/Callback.php
Expand Up @@ -36,11 +36,6 @@ final class Callback implements
use SkipOnErrorTrait;
use WhenTrait;

/**
* @var object|null The object being validated. `null` if PHP attributes aren't used.
*/
private ?object $validatedObject = null;

/**
* @param callable|null $callback Callable with the `function ($value, $rule, $context): Result` signature that
* performs the validation. Mutually exclusive with {@see $method}.
Expand Down Expand Up @@ -102,24 +97,8 @@ public function getMethod(): string|null
return $this->method;
}

/**
* Get object being validated.
*
* @return object|null Object being validated. Null if PHP attributes aren't used.
*
* @see $validatedObject
*/
public function getValidatedObject(): ?object
{
return $this->validatedObject;
}

public function afterInitAttribute(object $object, int $target): void
{
if ($target === Attribute::TARGET_CLASS) {
$this->validatedObject = $object;
}

if ($this->method === null) {
return;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Rule/CallbackHandler.php
Expand Up @@ -32,7 +32,7 @@ public function validate(mixed $value, object $rule, ValidationContext $context)
throw new InvalidArgumentException('Using method outside of attribute scope is prohibited.');
}

$result = $callback($rule->getValidatedObject() ?? $value, $rule, $context);
$result = $callback($value, $rule, $context);
if (!$result instanceof Result) {
throw new InvalidCallbackReturnTypeException($result);
}
Expand Down
28 changes: 1 addition & 27 deletions src/Rule/Count.php
Expand Up @@ -7,7 +7,6 @@
use Attribute;
use Closure;
use Countable;
use Yiisoft\Validator\AfterInitAttributeEventInterface;
use Yiisoft\Validator\LimitInterface;
use Yiisoft\Validator\Rule\Trait\LimitTrait;
use Yiisoft\Validator\Rule\Trait\SkipOnEmptyTrait;
Expand All @@ -32,19 +31,13 @@ final class Count implements
SkipOnErrorInterface,
WhenInterface,
SkipOnEmptyInterface,
LimitInterface,
AfterInitAttributeEventInterface
LimitInterface
{
use LimitTrait;
use SkipOnEmptyTrait;
use SkipOnErrorTrait;
use WhenTrait;

/**
* @var object|null Object being validated.
*/
private ?object $objectValidated = null;

/**
* @param int|null $exactly Exact number of items. `null` means no strict comparison. Mutually exclusive with
* {@see $min} and {@see $max}.
Expand Down Expand Up @@ -130,18 +123,6 @@ public function getIncorrectInputMessage(): string
return $this->incorrectInputMessage;
}

/**
* Get object being validated.
*
* @return object|null Object being validated.
*
* @see $objectValidated
*/
public function getObjectValidated(): ?object
{
return $this->objectValidated;
}

public function getOptions(): array
{
return array_merge($this->getLimitOptions(), [
Expand All @@ -158,11 +139,4 @@ public function getHandler(): string
{
return CountHandler::class;
}

public function afterInitAttribute(object $object, int $target): void
{
if ($target === Attribute::TARGET_CLASS) {
$this->objectValidated = $object;
}
}
}
1 change: 0 additions & 1 deletion src/Rule/CountHandler.php
Expand Up @@ -32,7 +32,6 @@ public function validate(mixed $value, object $rule, ValidationContext $context)
$result = new Result();

/** @var mixed $value */
$value = $rule->getObjectValidated() ?? $value;
if (!is_countable($value)) {
$result->addError($rule->getIncorrectInputMessage(), [
'attribute' => $context->getTranslatedAttribute(),
Expand Down
3 changes: 3 additions & 0 deletions src/Rule/EachHandler.php
Expand Up @@ -23,6 +23,9 @@ public function validate(mixed $value, object $rule, ValidationContext $context)
throw new UnexpectedRuleException(Each::class, $rule);
}

/** @var mixed $value */
$value = $context->getParameter(ValidationContext::PARAMETER_VALUE_AS_ARRAY) ?? $value;

$rules = $rule->getRules();

$result = new Result();
Expand Down
3 changes: 3 additions & 0 deletions src/Rule/NestedHandler.php
Expand Up @@ -49,6 +49,9 @@ public function validate(mixed $value, object $rule, ValidationContext $context)
throw new UnexpectedRuleException(Nested::class, $rule);
}

/** @var mixed $value */
$value = $context->getParameter(ValidationContext::PARAMETER_VALUE_AS_ARRAY) ?? $value;

$compoundResult = new Result();

if ($rule->getRules() === null) {
Expand Down
2 changes: 2 additions & 0 deletions src/ValidationContext.php
Expand Up @@ -14,6 +14,8 @@
*/
final class ValidationContext
{
public const PARAMETER_VALUE_AS_ARRAY = 'yii-validator-value-as-array';

/**
* @var ValidatorInterface|null A validator instance. `null` means context data was not set
* with {@see setContextDataOnce()} yet.
Expand Down
4 changes: 3 additions & 1 deletion src/Validator.php
Expand Up @@ -113,11 +113,13 @@ public function validate(

if (is_int($attribute)) {
/** @psalm-suppress MixedAssignment */
$validatedData = $dataSet->getData();
$validatedData = $dataSet instanceof DataWrapperInterface ? $dataSet->getSource() : $data;
$context->setParameter(ValidationContext::PARAMETER_VALUE_AS_ARRAY, $dataSet->getData());
$context->setAttribute(null);
} else {
/** @psalm-suppress MixedAssignment */
$validatedData = $dataSet->getAttributeValue($attribute);
$context->setParameter(ValidationContext::PARAMETER_VALUE_AS_ARRAY, null);
$context->setAttribute($attribute);
}

Expand Down
4 changes: 2 additions & 2 deletions tests/DataSet/SingleValueDataSetTest.php
Expand Up @@ -13,8 +13,8 @@ public function testBase(): void
{
$data = new SingleValueDataSet(['test' => 'hello']);

$this->assertSame(['test' => 'hello'], $data->getData());

$this->assertNull($data->getData());
$this->assertSame(['test' => 'hello'], $data->getSource());
$this->assertFalse($data->hasAttribute('test'));
$this->assertNull($data->getAttributeValue('test'));
}
Expand Down
7 changes: 7 additions & 0 deletions tests/Rule/AtLeastTest.php
Expand Up @@ -110,6 +110,13 @@ public function dataValidationPassed(): array
},
[new AtLeast(['attr2'])],
],
[
new class () {
private int $attr1 = 1;
private $attr2 = null;
},
[new AtLeast(['attr1', 'attr2'])],
],
[
['attr1' => 1, 'attr2' => null],
[new AtLeast(['attr1', 'attr2'])],
Expand Down
7 changes: 5 additions & 2 deletions tests/Rule/Base/RuleTestCase.php
Expand Up @@ -27,8 +27,11 @@ abstract public function dataValidationFailed(): array;
/**
* @dataProvider dataValidationFailed
*/
public function testValidationFailed(mixed $data, array|RuleInterface|null $rules, array $errorMessagesIndexedByPath): void
{
public function testValidationFailed(
mixed $data,
array|RuleInterface|null $rules,
array $errorMessagesIndexedByPath
): void {
$result = (new Validator())->validate($data, $rules);

$this->assertFalse($result->isValid());
Expand Down
11 changes: 8 additions & 3 deletions tests/Rule/CompareToTest.php
Expand Up @@ -6,7 +6,7 @@

use InvalidArgumentException;
use stdClass;
use Yiisoft\Validator\DataSetInterface;
use Yiisoft\Validator\DataWrapperInterface;
use Yiisoft\Validator\Rule\Compare;
use Yiisoft\Validator\Rule\CompareTo;
use Yiisoft\Validator\Tests\Rule\Base\RuleTestCase;
Expand Down Expand Up @@ -433,13 +433,18 @@ public function dataValidationPassed(): array

public function dataValidationFailed(): array
{
$incorrectDataSet = new class () implements DataSetInterface {
$incorrectDataSet = new class () implements DataWrapperInterface {
public function getAttributeValue(string $attribute): mixed
{
return new stdClass();
}

public function getData(): mixed
public function getData(): ?array
{
return null;
}

public function getSource(): mixed
{
return false;
}
Expand Down

0 comments on commit b14e96c

Please sign in to comment.