Apply contribution guidelines to "Attribute" rule

Signed-off-by: Emmerson Siqueira <emmersonsiqueira@gmail.com>
This commit is contained in:
Emmerson Siqueira 2018-03-16 16:29:21 +01:00
parent 51ec5e1b95
commit 0a031649a8
No known key found for this signature in database
GPG key ID: C6404BA651DA5B69
10 changed files with 118 additions and 160 deletions

View file

@ -4,7 +4,7 @@
- `Attribute(string $name, Validatable $rule)`
- `Attribute(string $name, Validatable $rule, bool $mandatory)`
Validates an object attribute.
Validates an object attribute, even private ones.
```php
$obj = new stdClass;

View file

@ -13,11 +13,20 @@ declare(strict_types=1);
namespace Respect\Validation\Exceptions;
class AttributeException extends NestedValidationException implements NonOmissibleException
/**
* Exceptions to be thrown by the Attribute Rule.
*
* @author Emmerson Siqueira <emmersonsiqueira@gmail.com>
* @author Henrique Moody <henriquemoody@gmail.com>
*/
final class AttributeException extends NestedValidationException implements NonOmissibleException
{
public const NOT_PRESENT = 'not_present';
public const INVALID = 'invalid';
/**
* {@inheritdoc}
*/
public static $defaultTemplates = [
self::MODE_DEFAULT => [
self::NOT_PRESENT => 'Attribute {{name}} must be present',
@ -29,6 +38,9 @@ class AttributeException extends NestedValidationException implements NonOmissib
],
];
/**
* {@inheritdoc}
*/
protected function chooseTemplate(): string
{
return $this->getParam('hasReference') ? static::INVALID : static::NOT_PRESENT;

View file

@ -13,8 +13,20 @@ declare(strict_types=1);
namespace Respect\Validation\Exceptions;
class KeyException extends AttributeException implements NonOmissibleException
/**
* Exceptions to be thrown by the Attribute Rule.
*
* @author Emmerson Siqueira <emmersonsiqueira@gmail.com>
* @author Henrique Moody <henriquemoody@gmail.com>
*/
final class KeyException extends NestedValidationException implements NonOmissibleException
{
public const NOT_PRESENT = 'not_present';
public const INVALID = 'invalid';
/**
* {@inheritdoc}
*/
public static $defaultTemplates = [
self::MODE_DEFAULT => [
self::NOT_PRESENT => 'Key {{name}} must be present',
@ -25,4 +37,12 @@ class KeyException extends AttributeException implements NonOmissibleException
self::INVALID => 'Key {{name}} must not be valid',
],
];
/**
* {@inheritdoc}
*/
protected function chooseTemplate(): string
{
return $this->getParam('hasReference') ? static::INVALID : static::NOT_PRESENT;
}
}

View file

@ -13,8 +13,20 @@ declare(strict_types=1);
namespace Respect\Validation\Exceptions;
class KeyNestedException extends AttributeException implements NonOmissibleException
/**
* Exceptions to be thrown by the Attribute Rule.
*
* @author Emmerson Siqueira <emmersonsiqueira@gmail.com>
* @author Henrique Moody <henriquemoody@gmail.com>
*/
final class KeyNestedException extends NestedValidationException implements NonOmissibleException
{
public const NOT_PRESENT = 'not_present';
public const INVALID = 'invalid';
/**
* {@inheritdoc}
*/
public static $defaultTemplates = [
self::MODE_DEFAULT => [
self::NOT_PRESENT => 'No items were found for key chain {{name}}',
@ -25,4 +37,12 @@ class KeyNestedException extends AttributeException implements NonOmissibleExcep
self::INVALID => 'Key chain {{name}} must not be valid',
],
];
/**
* {@inheritdoc}
*/
protected function chooseTemplate(): string
{
return $this->getParam('hasReference') ? static::INVALID : static::NOT_PRESENT;
}
}

View file

@ -23,7 +23,7 @@ abstract class AbstractRelated extends AbstractRule
public $reference = '';
public $validator;
abstract public function hasReference($input);
abstract public function hasReference($input): bool;
abstract public function getReferenceValue($input);
@ -52,13 +52,6 @@ abstract class AbstractRelated extends AbstractRule
return $this;
}
private function decision($type, $hasReference, $input)
{
return (!$this->mandatory && !$hasReference)
|| (is_null($this->validator)
|| $this->validator->$type($this->getReferenceValue($input)));
}
public function assert($input): void
{
$hasReference = $this->hasReference($input);
@ -94,4 +87,11 @@ abstract class AbstractRelated extends AbstractRule
return $this->decision('validate', $hasReference, $input);
}
private function decision(string $type, bool $hasReference, $input)
{
return (!$this->mandatory && !$hasReference)
|| (is_null($this->validator)
|| $this->validator->$type($this->getReferenceValue($input)));
}
}

View file

@ -13,21 +13,31 @@ declare(strict_types=1);
namespace Respect\Validation\Rules;
use ReflectionException;
use ReflectionProperty;
use Respect\Validation\Exceptions\ComponentException;
use Respect\Validation\Validatable;
class Attribute extends AbstractRelated
/**
* Validates an object attribute, event private ones.
*
* @author Alexandre Gomes Gaigalas <alexandre@gaigalas.net>
* @author Emmerson Siqueira <emmersonsiqueira@gmail.com>
* @author Henrique Moody <henriquemoody@gmail.com>
*/
final class Attribute extends AbstractRelated
{
public function __construct($reference, Validatable $validator = null, $mandatory = true)
public function __construct(string $reference, Validatable $validator = null, $mandatory = true)
{
if (!is_string($reference) || empty($reference)) {
throw new ComponentException('Invalid attribute/property name');
}
parent::__construct($reference, $validator, $mandatory);
}
/**
* @param object $input
*
* @throws ReflectionException
*
* @return mixed
*/
public function getReferenceValue($input)
{
$propertyMirror = new ReflectionProperty($input, $this->reference);
@ -36,7 +46,10 @@ class Attribute extends AbstractRelated
return $propertyMirror->getValue($input);
}
public function hasReference($input)
/**
* @param object $input
*/
public function hasReference($input): bool
{
return is_object($input) && property_exists($input, $this->reference);
}

View file

@ -20,7 +20,7 @@ class Call extends AbstractRelated
return call_user_func_array($this->reference, [&$input]);
}
public function hasReference($input)
public function hasReference($input): bool
{
return is_callable($this->reference);
}

View file

@ -31,7 +31,7 @@ class Key extends AbstractRelated
return $input[$this->reference];
}
public function hasReference($input)
public function hasReference($input): bool
{
return is_array($input) && array_key_exists($this->reference, $input);
}

View file

@ -18,7 +18,7 @@ use Respect\Validation\Exceptions\ComponentException;
class KeyNested extends AbstractRelated
{
public function hasReference($input)
public function hasReference($input): bool
{
try {
$this->getReferenceValue($input);

View file

@ -13,168 +13,61 @@ declare(strict_types=1);
namespace Respect\Validation\Rules;
use PHPUnit\Framework\TestCase;
use Respect\Validation\Test\RuleTestCase;
use Respect\Validation\Validatable;
class PrivClass
{
public const PROPERTY_VALUE = 'foo';
private $bar = self::PROPERTY_VALUE;
}
/**
* @group rule
* @covers \Respect\Validation\Rules\Attribute
* @covers \Respect\Validation\Exceptions\AttributeException
*/
class AttributeTest extends TestCase
final class AttributeTest extends RuleTestCase
{
public function testAttributeWithNoExtraValidationShouldCheckItsPresence(): void
/**
* {@inheritdoc}
*/
public function providerForValidInput(): array
{
$validator = new Attribute('bar');
$obj = new \stdClass();
$obj->bar = 'foo';
$validator->check($obj);
self::assertTrue($validator->__invoke($obj));
$validator->assert($obj);
}
/**
* @expectedException \Respect\Validation\Exceptions\AttributeException
*/
public function testAbsentAttributeShouldRaiseAttributeException(): void
{
$validator = new Attribute('bar');
$obj = new \stdClass();
$obj->baraaaaa = 'foo';
self::assertFalse($validator->__invoke($obj));
$validator->assert($obj);
}
$extraValidator = $this->createMock(Validatable::class);
$extraValidator->method('validate')
->willReturn(true);
/**
* @expectedException \Respect\Validation\Exceptions\ValidationException
*/
public function testAbsentAttributeShouldRaiseAttributeException_on_check(): void
{
$validator = new Attribute('bar');
$obj = new \stdClass();
$obj->baraaaaa = 'foo';
self::assertFalse($validator->__invoke($obj));
$validator->check($obj);
}
/**
* @dataProvider providerForInvalidAttributeNames
* @expectedException \Respect\Validation\Exceptions\ComponentException
*/
public function testInvalidConstructorArgumentsShouldThrowComponentException($attributeName): void
{
$validator = new Attribute($attributeName);
}
public function providerForInvalidAttributeNames()
{
return [
[new \stdClass()],
[123],
[''],
'Is valid when attribute is present without extra validator' => [new Attribute('bar'), $obj],
'Is valid when private attribute is present without extra validator' => [new Attribute('bar'), $this->objectWithPrivateProperty()],
'Is valid when attribute is present with extra validator' => [new Attribute('bar', $extraValidator), $obj],
'Is valid when non mandatory attribute is not present' => [new Attribute('foo', null, false), $obj],
'Is valid when non mandatory attribute is not present with extra validator' => [new Attribute('foo', $extraValidator, false), $obj],
];
}
public function testExtraValidatorRulesForAttribute(): void
{
$subValidator = new Length(1, 3);
$validator = new Attribute('bar', $subValidator);
$obj = new \stdClass();
$obj->bar = 'foo';
self::assertTrue($validator->__invoke($obj));
$validator->assert($obj);
$validator->check($obj);
}
/**
* @expectedException \Respect\Validation\Exceptions\AttributeException
* {@inheritdoc}
*/
public function testShouldNotValidateEmptyString(): void
public function providerForInvalidInput(): array
{
$subValidator = new Length(1, 3);
$validator = new Attribute('bar', $subValidator);
$obj = new \stdClass();
$obj->bar = 'foo';
self::assertFalse($validator->__invoke(''));
$validator->assert('');
$extraValidatorMock = $this->createMock(Validatable::class);
$extraValidatorMock->method('validate')->willReturn(false);
return [
'Is not valid when attribute is absent without extra validator' => [new Attribute('barr'), $obj],
'Is not valid when private attribute is not valid based on extra validator' => [new Attribute('bar', $extraValidatorMock), $this->objectWithPrivateProperty()],
'Is not valid when value provided is an empty string' => [new Attribute('barr'), ''],
'Is not valid when validator related to attribute does not validate' => [new Attribute('bar', $extraValidatorMock), $obj],
];
}
public function testExtraValidatorRulesForAttribute_should_fail_if_invalid(): void
private function objectWithPrivateProperty()
{
$subValidator = new Length(1, 3);
$validator = new Attribute('bar', $subValidator);
$obj = new \stdClass();
$obj->bar = 'foo hey this has more than 3 chars';
self::assertFalse($validator->__invoke($obj));
}
/**
* @expectedException \Respect\Validation\Exceptions\LengthException
*/
public function testExtraValidatorRulesForAttribute_should_raise_extra_validator_exception_on_check(): void
{
$subValidator = new Length(1, 3);
$validator = new Attribute('bar', $subValidator);
$obj = new \stdClass();
$obj->bar = 'foo hey this has more than 3 chars';
$validator->check($obj);
}
/**
* @expectedException \Respect\Validation\Exceptions\AttributeException
*/
public function testExtraValidatorRulesForAttribute_should_raise_AttributeException_on_assert(): void
{
$subValidator = new Length(1, 3);
$validator = new Attribute('bar', $subValidator);
$obj = new \stdClass();
$obj->bar = 'foo hey this has more than 3 chars';
$validator->assert($obj);
}
public function testNotMandatoryAttributeShouldNotFailWhenAttributeIsAbsent(): void
{
$validator = new Attribute('bar', null, false);
$obj = new \stdClass();
self::assertTrue($validator->__invoke($obj));
}
public function testNotMandatoryAttributeShouldNotFailWhenAttributeIsAbsent_with_extra_validator(): void
{
$subValidator = new Length(1, 3);
$validator = new Attribute('bar', $subValidator, false);
$obj = new \stdClass();
self::assertTrue($validator->__invoke($obj));
}
public function testPrivateAttributeShouldAlsoBeChecked(): void
{
$obj = new PrivClass();
$validatable = $this->createMock(Validatable::class);
$validatable
->expects($this->once())
->method('assert')
->with(PrivClass::PROPERTY_VALUE);
$validator = new Attribute('bar', $validatable);
$validator->assert($obj);
}
public function testPrivateAttributeShouldFailIfNotValid(): void
{
$subValidator = new Length(33333, 888888);
$validator = new Attribute('bar', $subValidator);
$obj = new PrivClass();
self::assertFalse($validator->__invoke($obj));
return new class() {
public const PROPERTY_VALUE = 'foo';
private $bar = self::PROPERTY_VALUE;
};
}
}