Skip to content

Commit

Permalink
Merge pull request #188 from spiral/pr/checker_condition
Browse files Browse the repository at this point in the history
Pr/checker condition
  • Loading branch information
wolfy-j committed Aug 2, 2017
2 parents 42a41b0 + b259c9e commit 030f876
Show file tree
Hide file tree
Showing 7 changed files with 323 additions and 1 deletion.
21 changes: 21 additions & 0 deletions source/Spiral/Validation/CheckerConditionInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Spiral\Validation;

/**
* Provides ability to execute checker on a certain condition if it is met.
*/
interface CheckerConditionInterface
{
/**
* @param ValidatorInterface $validator
*
* @return CheckerConditionInterface
*/
public function withValidator(ValidatorInterface $validator): CheckerConditionInterface;

/**
* @return bool
*/
public function isMet(): bool;
}
23 changes: 23 additions & 0 deletions source/Spiral/Validation/Prototypes/AbstractCheckerCondition.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace Spiral\Validation\Prototypes;

use Spiral\Validation\CheckerConditionInterface;
use Spiral\Validation\ValidatorInterface;

abstract class AbstractCheckerCondition implements CheckerConditionInterface
{
/** @var ValidatorInterface */
protected $validator;

/**
* {@inheritdoc}
*/
public function withValidator(ValidatorInterface $validator): CheckerConditionInterface
{
$condition = clone $this;
$condition->validator = $validator;

return $condition;
}
}
83 changes: 83 additions & 0 deletions source/Spiral/Validation/Validator.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@ class Validator extends Component implements ValidatorInterface, LoggerAwareInte
*/
protected $container = null;

/**
* Validation context. Not validated.
*
* @var mixed
*/
protected $context;

/**
* {@inheritdoc}
*
Expand Down Expand Up @@ -211,6 +218,22 @@ public function getErrors(): array
return $this->registeredErrors + $this->errors;
}

/**
* {@inheritdoc}
*/
public function setContext($context)
{
$this->context = $context;
}

/**
* {@inheritdoc}
*/
public function getContext()
{
return $this->context;
}

/**
* Receive field from context data or return default value.
*
Expand Down Expand Up @@ -260,6 +283,10 @@ protected function validate()
break;
}

if ($this->skipUnderEmptyCondition($rule)) {
continue;
}

$result = $this->check($field, $this->getValue($field), $condition, $arguments);

if ($result === true) {
Expand Down Expand Up @@ -504,4 +531,60 @@ private function extractData($data): array

return $data;
}

/**
* Does rule have condition.
*
* @param $rule
*
* @return bool
*/
protected function skipUnderEmptyCondition($rule)
{
if (is_array($rule) && !empty($rule['condition']) && $this->hasCondition($rule['condition'])) {
$condition = $this->getCondition($rule['condition']);
if (!$condition->isMet()) {
return true;
}
}

return false;
}

/**
* Does checker condition class exist.
*
* @param string $name
*
* @return bool
*/
protected function hasCondition(string $name): bool
{
if (class_exists($name)) {
$condition = $this->container->get($name);

return $condition instanceof CheckerConditionInterface;
}

return false;
}

/**
* Get or create instance of validation checker condition.
*
* @param string $name
*
* @return CheckerConditionInterface
* @throws ValidationException
*/
protected function getCondition(string $name): CheckerConditionInterface
{
if (!$this->hasCondition($name)) {
throw new ValidationException(
"Unable to create validation checker condition defined by '{$name}' name"
);
}

return $this->container->get($name)->withValidator($this);
}
}
16 changes: 16 additions & 0 deletions source/Spiral/Validation/ValidatorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,20 @@ public function hasErrors(): bool;
* @return array
*/
public function getErrors(): array;

/**
* Get context data (not validated).
*
* @return mixed
*/
public function getContext();

/**
* Set context data (not validated).
*
* @param $context
*
* @return mixed
*/
public function setContext($context);
}
148 changes: 148 additions & 0 deletions tests/Validation/Conditions/IsLoadedConditionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
<?php
/**
* Created by PhpStorm.
* User: Valentin
* Date: 02.08.2017
* Time: 10:58
*/

namespace Spiral\Tests\Validation\Conditions;


use Spiral\Tests\BaseTest;
use Spiral\Tests\Validation\Fixtures\IsLoadedCondition;
use Spiral\Validation\Checkers\AddressChecker;
use Spiral\Validation\Checkers\TypeChecker;
use Spiral\Validation\Configs\ValidatorConfig;
use Spiral\Validation\Validator;
use TestApplication\Database\SampleRecord;

class IsLoadedConditionTest extends BaseTest
{
protected function getConfig()
{
return new ValidatorConfig([
'emptyConditions' => [
'notEmpty',
'type::notEmpty',
],
'checkers' => [
'type' => TypeChecker::class,
'address' => AddressChecker::class,
],
'aliases' => [
'notEmpty' => 'type::notEmpty',
'email' => 'address::email',
'url' => 'address::url',
],
]);
}

public function setUp()
{
parent::setUp(); // TODO: Change the autogenerated stub

$this->commands->run('orm:schema', [
'--alter' => true
]);
}

public function testIsMet()
{
$validator = new Validator();

/** @var \Spiral\Validation\CheckerConditionInterface $condition */
$condition = $this->container->get(IsLoadedCondition::class)->withValidator($validator);

$this->assertFalse($condition->isMet());

$validator->setContext(['context']);
$this->assertFalse($condition->isMet());

$entity = new SampleRecord();
$validator->setContext($entity);
$this->assertFalse($condition->isMet());

$entity->save();
$this->assertTrue($condition->isMet());
}

public function testWithConditions()
{
//Validator works
$validator = new Validator(
['email' => ['notEmpty', 'address::email']],
['email' => '[email protected]'],
$this->getConfig(),
$this->container
);
$this->assertTrue($validator->isValid());

$validator = new Validator(
['email' => ['notEmpty', 'address::email']],
['email' => null],
$this->getConfig(),
$this->container
);
$this->assertFalse($validator->isValid());

//Condition will not met because no context
$validator = new Validator(
[
'email' => [
['notEmpty', 'condition' => IsLoadedCondition::class],
['address::email', 'condition' => IsLoadedCondition::class],
]
],
['email' => null],
$this->getConfig(),
$this->container
);
$this->assertTrue($validator->isValid());

//Condition will not met because context should be entity
$validator = new Validator(
[
'email' => [
['notEmpty', 'condition' => IsLoadedCondition::class],
['address::email', 'condition' => IsLoadedCondition::class],
]
],
['email' => null],
$this->getConfig(),
$this->container
);
$validator->setContext(['some', 'context']);
$this->assertTrue($validator->isValid());

//Condition will not met because context should be loaded entity
$entity = new SampleRecord();
$validator->setContext($entity);
$this->assertTrue($validator->isValid());

//Condition will met and validator will fail check
$entity = new SampleRecord();
$entity->save();
$validator->setContext($entity);
$this->assertFalse($validator->isValid());

//Validator will fail because condition should exist and be instance of \Spiral\Validation\CheckerConditionInterface::class
$validator = new Validator(
[
'email' => [
['notEmpty', 'condition' => 'Some\Condition'],
['address::email', 'condition' => 'Some\Condition'],
]
],
['email' => null],
$this->getConfig(),
$this->container
);

$entity = new SampleRecord();
$entity->save();

$validator->setContext($entity);
$this->assertFalse($validator->isValid());
}
}
21 changes: 21 additions & 0 deletions tests/Validation/Fixtures/IsLoadedCondition.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Spiral\Tests\Validation\Fixtures;

use Spiral\ORM\Record;
use Spiral\Validation\Prototypes\AbstractCheckerCondition;

class IsLoadedCondition extends AbstractCheckerCondition
{
/**
* This current condition tells that it will met if passed validator context is a loaded Record entity.
*
* @return bool
*/
public function isMet(): bool
{
$entity = $this->validator->getContext();

return !empty($entity) && ($entity instanceof Record) && $entity->isLoaded();
}
}
12 changes: 11 additions & 1 deletion tests/Validation/ValidatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
use Psr\Log\LoggerInterface;
use Spiral\Debug\LogsInterface;
use Spiral\Models\DataEntity;
use Spiral\Tests\Validation\Fixtures\SimpleTestChecker;
use Spiral\Translator\TranslatorInterface;
use Spiral\Validation\Checkers\AddressChecker;
use Spiral\Validation\Checkers\TypeChecker;
Expand Down Expand Up @@ -308,4 +307,15 @@ public function testCustomErrors()
$this->assertEquals(substr(AddressChecker::MESSAGES['email'], 2, -2),
$validator->getErrors()['email']);
}

public function testContext()
{
$context = new \StdClass();
$context->data = 'some context';

$validator = new Validator([], [], $this->config, $this->container);
$validator->setContext($context);

$this->assertEquals($context, $validator->getContext());
}
}

0 comments on commit 030f876

Please sign in to comment.