Skip to content

Commit

Permalink
First implementation (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
vjik committed May 12, 2023
1 parent d6d3c99 commit 712eabc
Show file tree
Hide file tree
Showing 20 changed files with 510 additions and 25 deletions.
27 changes: 14 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,32 @@
<a href="https://github.com/yiisoft" target="_blank">
<img src="https://yiisoft.github.io/docs/images/yii_logo.svg" height="100px">
</a>
<h1 align="center">Yii _____</h1>
<h1 align="center">Yii Validating Hydrator</h1>
<br>
</p>

[![Latest Stable Version](https://poser.pugx.org/yiisoft/_____/v/stable.png)](https://packagist.org/packages/yiisoft/_____)
[![Total Downloads](https://poser.pugx.org/yiisoft/_____/downloads.png)](https://packagist.org/packages/yiisoft/_____)
[![Build status](https://github.com/yiisoft/_____/workflows/build/badge.svg)](https://github.com/yiisoft/_____/actions?query=workflow%3Abuild)
[![Code Coverage](https://codecov.io/gh/yiisoft/_____/branch/master/graph/badge.svg)](https://codecov.io/gh/yiisoft/_____)
[![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fyiisoft%2F_____%2Fmaster)](https://dashboard.stryker-mutator.io/reports/github.com/yiisoft/_____/master)
[![static analysis](https://github.com/yiisoft/_____/workflows/static%20analysis/badge.svg)](https://github.com/yiisoft/_____/actions?query=workflow%3A%22static+analysis%22)
[![type-coverage](https://shepherd.dev/github/yiisoft/_____/coverage.svg)](https://shepherd.dev/github/yiisoft/_____)
[![psalm-level](https://shepherd.dev/github/yiisoft/_____/level.svg)](https://shepherd.dev/github/yiisoft/_____)
[![Latest Stable Version](https://poser.pugx.org/yiisoft/hydrator-validator/v/stable.png)](https://packagist.org/packages/yiisoft/hydrator-validator)
[![Total Downloads](https://poser.pugx.org/yiisoft/hydrator-validator/downloads.png)](https://packagist.org/packages/yiisoft/hydrator-validator)
[![Build status](https://github.com/yiisoft/hydrator-validator/workflows/build/badge.svg)](https://github.com/yiisoft/hydrator-validator/actions?query=workflow%3Abuild)
[![Code Coverage](https://codecov.io/gh/yiisoft/hydrator-validator/branch/master/graph/badge.svg)](https://codecov.io/gh/yiisoft/hydrator-validator)
[![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fyiisoft%2Fhydrator-validator%2Fmaster)](https://dashboard.stryker-mutator.io/reports/github.com/yiisoft/hydrator-validator/master)
[![static analysis](https://github.com/yiisoft/hydrator-validator/workflows/static%20analysis/badge.svg)](https://github.com/yiisoft/hydrator-validator/actions?query=workflow%3A%22static+analysis%22)
[![type-coverage](https://shepherd.dev/github/yiisoft/hydrator-validator/coverage.svg)](https://shepherd.dev/github/yiisoft/hydrator-validator)
[![psalm-level](https://shepherd.dev/github/yiisoft/hydrator-validator/level.svg)](https://shepherd.dev/github/yiisoft/hydrator-validator)

The package ...
The package provides a hydrator that also does validation, including raw data. It is useful when input data comes from
a user and needs to be put into DTOs.

## Requirements

- PHP 8.1 or higher.
- PHP 8.0 or higher.

## Installation

The package could be installed with composer:

```shell
composer require yiisoft/_____
composer require yiisoft/hydrator-validator
```

## General usage
Expand Down Expand Up @@ -74,7 +75,7 @@ Use [ComposerRequireChecker](https://github.com/maglnet/ComposerRequireChecker)

## License

The Yii _____ is free software. It is released under the terms of the BSD License.
The Yii Validating Hydrator is free software. It is released under the terms of the BSD License.
Please see [`LICENSE`](./LICENSE.md) for more information.

Maintained by [Yii Software](https://www.yiiframework.com/).
Expand Down
25 changes: 15 additions & 10 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
{
"name": "yiisoft/_____",
"name": "yiisoft/hydrator-validator",
"type": "library",
"description": "_____",
"description": "Validating hydrator with raw data validation support",
"keywords": [
"_____"
"input",
"validation"
],
"homepage": "https://www.yiiframework.com/",
"license": "BSD-3-Clause",
"support": {
"issues": "https://github.com/yiisoft/_____/issues?state=open",
"issues": "https://github.com/yiisoft/hydrator-validator/issues?state=open",
"forum": "https://www.yiiframework.com/forum/",
"wiki": "https://www.yiiframework.com/wiki/",
"irc": "ircs://irc.libera.chat:6697/yii",
"chat": "https://t.me/yii3en",
"source": "https://github.com/yiisoft/_____"
"source": "https://github.com/yiisoft/hydrator-validator"
},
"funding": [
{
Expand All @@ -28,23 +29,27 @@
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
"php": "^8.1"
"php": "^8.0",
"yiisoft/hydrator": "dev-master",
"yiisoft/validator": "^1.1"
},
"require-dev": {
"maglnet/composer-require-checker": "^4.4",
"phpunit/phpunit": "^9.5",
"rector/rector": "^0.15.19",
"rector/rector": "^0.16.0",
"roave/infection-static-analysis-plugin": "^1.16",
"spatie/phpunit-watcher": "^1.23",
"vimeo/psalm": "^4.30|^5.7"
"vimeo/psalm": "^4.30|^5.11",
"yiisoft/test-support": "^3.0"
},
"autoload": {
"psr-4": {
"Yiisoft\\_____\\": "src"
"Yiisoft\\Hydrator\\Validator\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Yiisoft\\_____\\Tests\\": "tests"
"Yiisoft\\Hydrator\\Validator\\Tests\\": "tests"
}
},
"config": {
Expand Down
7 changes: 6 additions & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@
</php>

<testsuites>
<testsuite name="Yii _____ tests">
<testsuite name="Yii Validating Hydrator tests">
<directory>./tests</directory>
<exclude>./tests/TestEnvironments</exclude>
</testsuite>

<testsuite name="Yii Validating Hydrator PHP 8.1 tests">
<directory phpVersion="8.1" phpVersionOperator=">=">./tests/TestEnvironments/Php81</directory>
</testsuite>
</testsuites>

Expand Down
2 changes: 1 addition & 1 deletion rector.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

// define sets of rules
$rectorConfig->sets([
LevelSetList::UP_TO_PHP_81,
LevelSetList::UP_TO_PHP_80,
]);

$rectorConfig->skip([
Expand Down
Empty file removed src/.gitkeep
Empty file.
38 changes: 38 additions & 0 deletions src/Attribute/Validate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Hydrator\Validator\Attribute;

use Attribute;
use Yiisoft\Hydrator\ParameterAttributeInterface;
use Yiisoft\Validator\RuleInterface;

#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER | Attribute::IS_REPEATABLE)]
final class Validate implements ParameterAttributeInterface
{
/**
* @var RuleInterface[] $rules
* @psalm-var list<RuleInterface> $rules
*/
private array $rules;

public function __construct(RuleInterface ...$rules)
{
$this->rules = array_values($rules);
}

/**
* @return RuleInterface[]
* @psalm-return list<RuleInterface>
*/
public function getRules(): array
{
return $this->rules;
}

public function getResolver(): string
{
return ValidateResolver::class;
}
}
53 changes: 53 additions & 0 deletions src/Attribute/ValidateResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Hydrator\Validator\Attribute;

use Yiisoft\Hydrator\Context;
use Yiisoft\Hydrator\NotResolvedException;
use Yiisoft\Hydrator\ParameterAttributeInterface;
use Yiisoft\Hydrator\ParameterAttributeResolverInterface;
use Yiisoft\Hydrator\UnexpectedAttributeException;
use Yiisoft\Validator\Result;
use Yiisoft\Validator\ValidatorInterface;

final class ValidateResolver implements ParameterAttributeResolverInterface
{
private ?Result $result = null;

public function __construct(
private ValidatorInterface $validator,
) {
}

public function setResult(?Result $result): void
{
$this->result = $result;
}

public function getParameterValue(ParameterAttributeInterface $attribute, Context $context): mixed
{
if (!$attribute instanceof Validate) {
throw new UnexpectedAttributeException(Validate::class, $attribute);
}

if ($this->result !== null) {
$parameterName = $context->getParameter()->getName();
$result = $this->validator->validate(
$context->isResolved() ? [$parameterName => $context->getResolvedValue()] : [],

Check warning on line 38 in src/Attribute/ValidateResolver.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.2-ubuntu-latest

Escaped Mutant for Mutator "ArrayItem": --- Original +++ New @@ @@ } if ($this->result !== null) { $parameterName = $context->getParameter()->getName(); - $result = $this->validator->validate($context->isResolved() ? [$parameterName => $context->getResolvedValue()] : [], [$parameterName => $attribute->getRules()]); + $result = $this->validator->validate($context->isResolved() ? [$parameterName > $context->getResolvedValue()] : [], [$parameterName => $attribute->getRules()]); foreach ($result->getErrors() as $error) { $this->result->addError($error->getMessage(), $error->getParameters(), $error->getValuePath()); }

Check warning on line 38 in src/Attribute/ValidateResolver.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.2-ubuntu-latest

Escaped Mutant for Mutator "ArrayItemRemoval": --- Original +++ New @@ @@ } if ($this->result !== null) { $parameterName = $context->getParameter()->getName(); - $result = $this->validator->validate($context->isResolved() ? [$parameterName => $context->getResolvedValue()] : [], [$parameterName => $attribute->getRules()]); + $result = $this->validator->validate($context->isResolved() ? [] : [], [$parameterName => $attribute->getRules()]); foreach ($result->getErrors() as $error) { $this->result->addError($error->getMessage(), $error->getParameters(), $error->getValuePath()); }
[$parameterName => $attribute->getRules()],
);

foreach ($result->getErrors() as $error) {
$this->result->addError(
$error->getMessage(),
$error->getParameters(),
$error->getValuePath(),
);
}
}

throw new NotResolvedException();
}
}
13 changes: 13 additions & 0 deletions src/ValidatedInputInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Hydrator\Validator;

use Yiisoft\Validator\PostValidationHookInterface;
use Yiisoft\Validator\Result;

interface ValidatedInputInterface extends PostValidationHookInterface
{
public function getValidationResult(): ?Result;
}
24 changes: 24 additions & 0 deletions src/ValidatedInputTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Hydrator\Validator;

use Yiisoft\Hydrator\Attribute\SkipHydration;
use Yiisoft\Validator\Result;

trait ValidatedInputTrait
{
#[SkipHydration]
private ?Result $validationResult = null;

public function processValidationResult(Result $result): void
{
$this->validationResult = $result;
}

public function getValidationResult(): ?Result
{
return $this->validationResult;
}
}
61 changes: 61 additions & 0 deletions src/ValidatingHydrator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Hydrator\Validator;

use Yiisoft\Hydrator\HydratorInterface;
use Yiisoft\Hydrator\Validator\Attribute\ValidateResolver;
use Yiisoft\Validator\Result;
use Yiisoft\Validator\ValidatorInterface;

final class ValidatingHydrator implements HydratorInterface
{
public function __construct(
private HydratorInterface $hydrator,
private ValidatorInterface $validator,
private ValidateResolver $validateResolver,
) {
}

public function hydrate(
object $object,
array $data = [],
array $map = [],
bool $strict = false
): void {
$result = $this->beforeAction();
$this->hydrator->hydrate($object, $data, $map, $strict);
$this->afterAction($object, $result);
}

public function create(
string $class,
array $data = [],
array $map = [],
bool $strict = false
): object {
$result = $this->beforeAction();
$object = $this->hydrator->create($class, $data, $map, $strict);
$this->afterAction($object, $result);
return $object;
}

private function beforeAction(): Result
{
$result = new Result();
$this->validateResolver->setResult($result);
return $result;
}

private function afterAction(object $object, Result $result): void
{
if (!$object instanceof ValidatedInputInterface) {
return;
}

$result->isValid()
? $this->validator->validate($object)
: $object->processValidationResult($result);
}
}
Empty file removed tests/.gitkeep
Empty file.
28 changes: 28 additions & 0 deletions tests/Attribute/ValidateResolverTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Hydrator\Validator\Tests\Attribute;

use PHPUnit\Framework\TestCase;
use Yiisoft\Hydrator\UnexpectedAttributeException;
use Yiisoft\Hydrator\Validator\Attribute\Validate;
use Yiisoft\Hydrator\Validator\Tests\Support\Attribute\IncorrectValidateResolver;
use Yiisoft\Hydrator\Validator\Tests\Support\TestHelper;

final class ValidateResolverTest extends TestCase
{
public function testInvalidAttribute(): void
{
$object = new class () {
#[IncorrectValidateResolver]
public int $a;
};

$hydrator = TestHelper::createValidatingHydrator();

$this->expectException(UnexpectedAttributeException::class);
$this->expectExceptionMessage('Expected "' . Validate::class . '", but');
$hydrator->hydrate($object, ['a' => 7]);
}
}
37 changes: 37 additions & 0 deletions tests/Attribute/ValidateTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Hydrator\Validator\Tests\Attribute;

use PHPUnit\Framework\TestCase;
use Yiisoft\Hydrator\Validator\Attribute\Validate;
use Yiisoft\Hydrator\Validator\Attribute\ValidateResolver;
use Yiisoft\Validator\Rule\Length;
use Yiisoft\Validator\Rule\Required;

final class ValidateTest extends TestCase
{
public function testBase(): void
{
$sourceRules = [new Required(), new Length(min: 3)];

$attribute = new Validate(...$sourceRules);

$rules = $attribute->getRules();
$resolver = $attribute->getResolver();

$this->assertSame($sourceRules, $rules);
$this->assertSame(ValidateResolver::class, $resolver);
}

public function testNamedArguments(): void
{
$rule1 = new Required();
$rule2 = new Length(min: 3);

$attribute = new Validate(a: $rule1, b: $rule2);

$this->assertSame([$rule1, $rule2], $attribute->getRules());
}
}

0 comments on commit 712eabc

Please sign in to comment.