Skip to content

Commit

Permalink
Merge pull request #189 from spiral/develop
Browse files Browse the repository at this point in the history
nested checkers
  • Loading branch information
wolfy-j authored Aug 3, 2017
2 parents 76830ff + 4834bbc commit d57007e
Show file tree
Hide file tree
Showing 10 changed files with 371 additions and 3 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,6 @@ $post->save();
$this->transaction->store($post);
```

> This example uses IoC scope to properly resolve ORM connection manager, "no magic" way - `$postSource->create([]);`
And much more: <b>[Skeleton App](https://github.com/spiral-php/application)</b> | [Guide](https://github.com/spiral-php/guide)

Tests
Expand Down
20 changes: 20 additions & 0 deletions source/Spiral/Http/Request/RequestFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,24 @@ protected function getSchema(): array

return static::SCHEMA;
}

/**
* Pass context to validator.
*
* @param mixed $context
*/
public function setContext($context)
{
$this->getValidator()->setContext($context);
}

/**
* Get context from validator.
*
* @return mixed
*/
public function getContext()
{
return $this->getValidator()->getContext();
}
}
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);
}
28 changes: 28 additions & 0 deletions tests/Http/RequestFilters/DemoRequestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -207,4 +207,32 @@ public function testValid()
$request->name = $name;
$this->assertTrue($request->isValid(true));
}

public function testContext()
{
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withParsedBody([
'name' => 'Anton',
'address' => [
'countryCode' => '',
'city' => 'San Francisco',
'address' => 'Some street'
],
'files' => [
//Iterating over data
0 => [
'label' => 'Some label'
]
]
]);
$this->container->bind(ServerRequestInterface::class, $serverRequest);
/** @var DemoRequest $request */
$request = $this->container->get(DemoRequest::class);

$context = new \stdClass();
$context->data = 'some context';
$request->setContext($context);

$this->assertEquals($context, $request->getContext());
}
}
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());
}
}
Loading

0 comments on commit d57007e

Please sign in to comment.