Skip to content

Commit

Permalink
✨ Add a collection rule to validate collection data #28
Browse files Browse the repository at this point in the history
  • Loading branch information
elie29 committed Dec 5, 2019
1 parent 7f65f43 commit 07ce0a7
Show file tree
Hide file tree
Showing 3 changed files with 266 additions and 0 deletions.
135 changes: 135 additions & 0 deletions src/Rule/CollectionRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<?php

declare(strict_types = 1);

namespace Elie\Validator\Rule;

/**
* This class validates an array of data (it could be a json array and would be decoded).
*/
class CollectionRule extends AbstractRule
{

/**
* Specific message error code
*/
public const INVALID_VALUE = 'INVALID_VALUE';

/**#@+
* Specific options for CollectionRule
*/
public const RULES = 'rules';
public const JSON = 'json';
/**#@-*/

protected $rules = [];

protected $decode = false; // for json value

/**
* Params could have the following structure:
* [
* 'required' => {bool:optional:false by default},
* 'messages' => {array:optional:key/value message patterns},
* 'rules' => {array:optional:list of rules with their params},
* 'json' => {boolean:optional:false by default},
* ]
* <code>
* $params = [<br/>
* 'required' => true,<br/>
* 'rules' => [<br/>
* ['code', StringRule::class, 'min' => 1, 'max' => 255],<br/>
* ['email', EmailRule::class],<br/>
* ]
* ]
* </code>
*
* Value is considered valid if 'rules' is empty
*/
public function __construct($key, $value, array $params = [])
{
parent::__construct($key, $value, $params);

if (isset($params[self::RULES])) {
$this->rules = $params[self::RULES];
}

if (isset($params[self::JSON])) {
$this->decode = (bool) $params[self::JSON];
}

$this->messages = $this->messages + [
self::INVALID_VALUE => _("%key%: %value% is not in a collection"),
];
}

public function validate(): int
{
$run = parent::validate();

if ($run !== self::CHECK) {
return $run;
}

return $this->rules === [] ? self::VALID : $this->isValid();
}

protected function isValid(): int
{
$this->error = '';

$collection = $this->decode ? json_decode($this->value, true) : $this->value;

if (! is_array($collection)) {
return $this->setAndReturnError(self::INVALID_VALUE);
}

$validatedContext = [];
foreach ($collection as $data) {
$validatedData = [];
foreach ($this->rules as $rule) {
$class = $this->resolve($rule, $data);
if ($class->validate() === RuleInterface::ERROR) {
$this->error = $class->getError();
return RuleInterface::ERROR;
}
$validatedData[$class->getKey()] = $class->getValue();
}
$validatedContext[] = $validatedData;
}

$this->value = $validatedContext;

return RuleInterface::VALID;
}

protected function resolve(array $rule, $data): RuleInterface
{
// The first element must be the key context
$key = $rule[0];
// The second element must be the class validator name
$class = $rule[1];

return new $class($key, $data[$key] ?? null, $rule);
}

public function getValue()
{
// don't change value on error or if it is not empty
if ($this->value || $this->error) {
return $this->value;
}

return [];
}

/**
* Empty value is null or [] only.
*
* @return bool
*/
protected function isEmpty(): bool
{
return $this->value === null || $this->value === [];
}
}
102 changes: 102 additions & 0 deletions tests/Rule/CollectionRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

declare(strict_types = 1);

namespace Elie\Validator\Rule;

use PHPUnit\Framework\TestCase;
use function Composer\Autoload\getData;

class CollectionRuleTest extends TestCase
{

public function testArrayValidate()
{
$rules = [
['code', NumericRule::class, NumericRule::MAX => 80],
['slug', MatchRule::class, MatchRule::PATTERN => '/^[a-z]{1,5}$/i'],
];

$data = [
['code' => 12, 'slug' => 'one'],
['code' => 13, 'slug' => 'two'],
['code' => 15, 'slug' => 'three'],
];

$rule = new CollectionRule('tags', $data, [CollectionRule::RULES => $rules]);

assertThat($rule->validate(), is(true));

$tags = $rule->getValue();

assertThat($tags, arrayWithSize(3));
}

public function testJsonValidate()
{
$rules = [
['code', NumericRule::class, NumericRule::MAX => 80],
['slug', MatchRule::class, MatchRule::PATTERN => '/^[a-z]{1,5}$/i'],
];

$data = json_encode([
['code' => 12, 'slug' => 'one'],
['code' => 13, 'slug' => 'two'],
['code' => 15, 'slug' => 'three'],
]);

$rule = new CollectionRule('tags', $data, [CollectionRule::RULES => $rules, CollectionRule::JSON => true]);

assertThat($rule->validate(), is(true));

$tags = $rule->getValue();

assertThat($tags, arrayWithSize(3));
}

public function testValidateEmptyData()
{
$rules = [
['code', NumericRule::class, NumericRule::MAX => 80],
];

$data = null;

$rule = new CollectionRule('tags', $data, [CollectionRule::RULES => $rules]);

assertThat($rule->validate(), is(true));
assertThat($rule->getValue(), arrayValue()); // value cast to array
}

public function testValidateOnError()
{
$rules = [
['code', NumericRule::class, NumericRule::MAX => 80],
['slug', MatchRule::class, MatchRule::PATTERN => '/^[a-z]{1,3}$/i'],
];

$data = [
['code' => 12, 'slug' => 'one'],
['code' => 13, 'slug' => 'two'],
['code' => 15, 'slug' => 'three'],
];

$rule = new CollectionRule('tags', $data, [CollectionRule::RULES => $rules]);

assertThat($rule->validate(), is(false));
assertThat($rule->getError(), containsString('slug: three does not match /^[a-z]{1,3}$/i'));
}

public function testValidateErrorFormat()
{
$data = '{email: "elie29@gmail.com"}';
$rules = [
['email', EmailRule::class]
];

$rule = new CollectionRule('tags', $data, [CollectionRule::RULES => $rules]);

assertThat($rule->validate(), is(false));
assertThat($rule->getError(), containsString('tags: {email: "elie29@gmail.com"} is not in a collection'));
}
}
29 changes: 29 additions & 0 deletions tests/ValidatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Elie\Validator\Rule\ArrayRule;
use Elie\Validator\Rule\BooleanRule;
use Elie\Validator\Rule\CollectionRule;
use Elie\Validator\Rule\EmailRule;
use Elie\Validator\Rule\JsonRule;
use Elie\Validator\Rule\MatchRule;
Expand Down Expand Up @@ -133,6 +134,34 @@ public function testValidatorWithRawPartialValidation(): void
assertThat($data, arrayWithSize(3));
}

public function testValidatorWithCollection(): void
{
$rules = [
['email', EmailRule::class, EmailRule::REQUIRED => true],
['tags', CollectionRule::class, CollectionRule::RULES => [
['code', NumericRule::class, NumericRule::MAX => 80],
['slug', MatchRule::class, MatchRule::PATTERN => '/^[a-z]{1,5}$/i'],
]],
];

$data = [
'email' => 'elie29@gmail.com',
'tags' => [
['code' => 12, 'slug' => 'one'],
['code' => 13, 'slug' => 'two'],
['code' => 15, 'slug' => 'three'],
],
];

$validator = new Validator($data, $rules);

assertThat($validator->validate(), is(true));

$tags = $validator->getValidatedContext()['tags'];

assertThat($tags, arrayWithSize(3));
}

public function testValidatorGetImplodedErrors(): void
{
$validator = new Validator(['name' => 'Ben'], [
Expand Down

0 comments on commit 07ce0a7

Please sign in to comment.