Refactor "Zend" rule

- Only create objects that are instantiable.

- Validate if validator is a valid Zend Validator after its creation.

- Parse messages from Zend Validator on "assert()" and "check()."

- Upgrade supported version of Zend Validator: version 2.0 has some PHP
  deprecations therefore there is no used to support it.

Co-authored-by: Danilo Correa <danilosilva87@gmail.com>
Signed-off-by: Henrique Moody <henriquemoody@gmail.com>
This commit is contained in:
Henrique Moody 2019-02-06 08:41:51 -02:00
parent df5cb1e6d2
commit 166501804f
No known key found for this signature in database
GPG key ID: 221E9281655813A6
6 changed files with 211 additions and 159 deletions

View file

@ -30,7 +30,7 @@
"slevomat/coding-standard": "^5.0",
"squizlabs/php_codesniffer": "^3.4",
"symfony/validator": "^3.0||^4.0",
"zendframework/zend-validator": "^2.0"
"zendframework/zend-validator": "^2.1"
},
"suggest": {
"ext-bcmath": "Arbitrary Precision Mathematics",

View file

@ -15,19 +15,9 @@ namespace Respect\Validation\Exceptions;
/**
* @author Alexandre Gomes Gaigalas <alexandre@gaigalas.net>
* @author Danilo Correa <danilosilva87@gmail.com>
* @author Henrique Moody <henriquemoody@gmail.com>
*/
final class ZendException extends NestedValidationException
{
/**
* {@inheritdoc}
*/
public static $defaultTemplates = [
self::MODE_DEFAULT => [
self::STANDARD => '{{name}}',
],
self::MODE_NEGATIVE => [
self::STANDARD => '{{name}}',
],
];
}

View file

@ -15,61 +15,72 @@ namespace Respect\Validation\Rules;
use ReflectionClass;
use Respect\Validation\Exceptions\ComponentException;
use Respect\Validation\Exceptions\ValidationException;
use Respect\Validation\Exceptions\ZendException;
use Zend\Validator\ValidatorInterface as ZendValidator;
use function get_object_vars;
use Throwable;
use Zend\Validator\ValidatorInterface;
use function array_map;
use function current;
use function is_string;
use function mb_stripos;
use function sprintf;
use function stripos;
/**
* Use Zend validators inside Respect\Validation flow.
*
* Messages are preserved.
*
* @author Alexandre Gomes Gaigalas <alexandre@gaigalas.net>
* @author Danilo Correa <danilosilva87@gmail.com>
* @author Henrique Moody <henriquemoody@gmail.com>
* @author Hugo Hamon <hugo.hamon@sensiolabs.com>
*/
final class Zend extends AbstractRule
{
/**
* @var string[]
*/
private $messages = [];
/**
* @var ZendValidator
* @var ValidatorInterface
*/
private $zendValidator;
/**
* @param string|ZendValidator $validator
* @param string|ValidatorInterface $validator
* @param mixed[] $params
*
* @throws ComponentException
*/
public function __construct($validator, array $params = [])
{
if ($validator instanceof ZendValidator) {
$this->zendValidator = $validator;
return;
}
if (!is_string($validator)) {
throw new ComponentException('Invalid Validator Construct');
}
$this->zendValidator = $this->createZendValidator($validator, $params);
$this->zendValidator = $this->zendValidator($validator, $params);
}
/**
* @param mixed $validator
* @param mixed[] $params
*
* @throws ComponentException
*/
private function createZendValidator(string $name, array $params): ZendValidator
private function zendValidator($validator, array $params = []): ValidatorInterface
{
$name = mb_stripos($name, 'Zend') === false ? 'Zend\\Validator\\'.$name : '\\'.$name;
if ($validator instanceof ValidatorInterface) {
return $validator;
}
$reflection = new ReflectionClass($name);
if (!is_string($validator)) {
throw new ComponentException('The given argument is not a valid Zend Validator');
}
/** @var ZendValidator $validator */
$validator = $reflection->newInstanceArgs($params);
$className = stripos($validator, 'Zend') === false ? 'Zend\\Validator\\'.$validator : '\\'.$validator;
return $validator;
try {
$reflection = new ReflectionClass($className);
if (!$reflection->isInstantiable()) {
throw new ComponentException(sprintf('"%s" is not instantiable', $className));
}
return $this->zendValidator($reflection->newInstanceArgs($params));
} catch (Throwable $exception) {
throw new ComponentException(sprintf('Could not create "%s"', $validator), 0, $exception);
}
}
/**
@ -78,19 +89,40 @@ final class Zend extends AbstractRule
public function assert($input): void
{
$validator = clone $this->zendValidator;
if ($validator->isValid($input)) {
return;
}
$exceptions = [];
foreach ($validator->getMessages() as $m) {
$exceptions[] = $this->reportError($m, get_object_vars($this));
/** @var ZendException $zendException */
$zendException = $this->reportError($input);
$zendException->addChildren(
array_map(
function (string $message) use ($input): ValidationException {
$exception = $this->reportError($input);
$exception->updateTemplate($message);
return $exception;
},
$validator->getMessages()
)
);
throw $zendException;
}
/**
* {@inheritdoc}
*/
public function check($input): void
{
$validator = clone $this->zendValidator;
if ($validator->isValid($input)) {
return;
}
/** @var ZendException $zendException */
$zendException = $this->reportError($input);
$zendException->addChildren($exceptions);
$zendException->updateTemplate(current($validator->getMessages()));
throw $zendException;
}
@ -100,8 +132,6 @@ final class Zend extends AbstractRule
*/
public function validate($input): bool
{
$validator = clone $this->zendValidator;
return $validator->isValid($input);
return (clone $this->zendValidator)->isValid($input);
}
}

View file

@ -0,0 +1,44 @@
--CREDITS--
Danilo Correa <danilosilva87@gmail.com>
--FILE--
<?php
declare(strict_types=1);
require 'vendor/autoload.php';
use Respect\Validation\Exceptions\NestedValidationException;
use Respect\Validation\Exceptions\ZendException;
use Respect\Validation\Validator as v;
try {
v::zend('Hostname')->check('googlecombr');
} catch (ZendException $exception) {
echo $exception->getMessage().PHP_EOL;
}
try {
v::not(v::zend('Hostname'))->check('google.com.br');
} catch (ZendException $exception) {
echo $exception->getMessage().PHP_EOL;
}
try {
v::zend('Hostname')->assert('googlecombr');
} catch (NestedValidationException $exception) {
echo $exception->getFullMessage().PHP_EOL;
}
try {
v::not(v::zend('Hostname'))->assert('google.com.br');
} catch (NestedValidationException $exception) {
echo $exception->getFullMessage().PHP_EOL;
}
?>
--EXPECT--
The input does not match the expected structure for a DNS hostname
"google.com.br" must not be valid
- "googlecombr" must be valid
- The input does not match the expected structure for a DNS hostname
- The input appears to be a local network name but local network names are not allowed
- "google.com.br" must not be valid

View file

@ -0,0 +1,35 @@
<?php
/*
* This file is part of Respect/Validation.
*
* (c) Alexandre Gomes Gaigalas <alexandre@gaigalas.net>
*
* For the full copyright and license information, please view the "LICENSE.md"
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Respect\Validation\Test\Stubs;
use Zend\Validator\ValidatorInterface;
final class ZendValidator implements ValidatorInterface
{
/**
* {@inheritdoc}
*/
public function isValid($value)
{
return true;
}
/**
* {@inheritdoc}
*/
public function getMessages()
{
return [];
}
}

View file

@ -13,153 +13,106 @@ declare(strict_types=1);
namespace Respect\Validation\Rules;
use DateTime;
use Respect\Validation\Test\TestCase;
use Respect\Validation\Exceptions\ComponentException;
use Respect\Validation\Test\Rules\Stub;
use Respect\Validation\Test\RuleTestCase;
use Respect\Validation\Test\Stubs\ZendValidator;
use Zend\Validator\ConfigProvider;
use Zend\Validator\Date as ZendDate;
use Zend\Validator\ValidatorInterface;
use function sprintf;
/**
* @group rule
* @covers \Respect\Validation\Exceptions\ZendException
* @group rule
*
* @covers \Respect\Validation\Rules\Zend
*
* @author Alexandre Gomes Gaigalas <alexandre@gaigalas.net>
* @author Augusto Pascutti <augusto@phpsp.org.br>
* @author Danilo Correa <danilosilva87@gmail.com>
* @author Gabriel Caruso <carusogabriel34@gmail.com>
* @author Henrique Moody <henriquemoody@gmail.com>
* @author Nick Lombard <github@jigsoft.co.za>
*/
final class ZendTest extends TestCase
final class ZendTest extends RuleTestCase
{
/**
* @test
* {@inheritdoc}
*/
public function constructorWithValidatorName(): void
public function providerForValidInput(): array
{
$v = new Zend('Date');
self::assertAttributeInstanceOf(
$instanceOf = ValidatorInterface::class,
$attribute = 'zendValidator',
$instance = $v
);
return [
'Constructor with name' => [new Zend('Date'), '2019-02-05'],
'Constructor with class name' => [new Zend(ZendDate::class), '2015-02-05'],
'Constructor with custom class name' => [new Zend(ZendValidator::class), '2015-02-05'],
'Constructor with instance' => [new Zend(new ZendDate()), '2018-02-05'],
'Constructor with custom instance' => [new Zend(new ZendValidator()), 'whatever'],
];
}
/**
* @depends constructorWithValidatorName
* {@inheritdoc}
*/
public function providerForInvalidInput(): array
{
return [
'Zend Date' => [new Zend('Date'), 'abc'],
'Constructor with class name' => [new Zend(ZendDate::class), '05/02/19'],
];
}
/**
* @test
*
* @test
*/
public function constructorWithValidatorClassName(): void
{
$v = new Zend(ZendDate::class);
self::assertAttributeInstanceOf(
$instanceOf = ValidatorInterface::class,
$attribute = 'zendValidator',
$instance = $v
);
}
/**
* @test
*/
public function constructorWithZendValidatorInstance(): void
{
$zendInstance = new ZendDate();
$v = new Zend($zendInstance);
self::assertAttributeSame(
$expected = $zendInstance,
$attribute = 'zendValidator',
$instance = $v
);
}
/**
* @depends constructorWithZendValidatorInstance
* @dataProvider providerForInvalidValidator
*
* @test
* @param mixed $validator
*/
public function userlandValidatorExtendingZendInterface(): void
public function itShouldThrowAnExceptionWhenValidatorIsNotValid($validator): void
{
$v = new Zend(new ZendDate());
self::assertAttributeInstanceOf(
$instanceOf = ValidatorInterface::class,
$attribute = 'zendValidator',
$instance = $v
);
$this->expectExceptionObject(new ComponentException('The given argument is not a valid Zend Validator'));
new Zend($validator);
}
/**
* @return mixed[][]
*/
public function providerForInvalidValidator(): array
{
return [
[null],
[[]],
[new Stub()],
];
}
/**
* @test
*/
public function constructorWithZendValidatorPartialNamespace(): void
{
$v = new Zend('Sitemap\Lastmod');
self::assertAttributeInstanceOf(
$instanceOf = ValidatorInterface::class,
$attribute = 'zendValidator',
$instance = $v
);
}
/**
* @depends constructorWithValidatorName
* @depends constructorWithZendValidatorPartialNamespace
*
* @test
* @dataProvider providerForUnbuildableValidator
*
* @param mixed $validator
*/
public function constructorWithValidatorNameAndParams(): void
public function itShouldThrowAnExceptionWhenValidatorCannotBeCreated(string $validator): void
{
$zendValidatorName = 'StringLength';
$zendValidatorParams = ['min' => 10, 'max' => 25];
$v = new Zend($zendValidatorName, $zendValidatorParams);
self::assertTrue(
$v->validate('12345678901'),
'The value should be valid for Zend\'s validator'
$this->expectExceptionObject(
new ComponentException(sprintf('Could not create "%s"', $validator))
);
new Zend($validator);
}
/**
* @depends constructorWithValidatorName
*
* @test
* @return string[][]
*/
public function zendDateValidatorWithRespectMethods(): void
public function providerForUnbuildableValidator(): array
{
$v = new Zend('Date');
$date = new DateTime();
self::assertTrue($v->validate($date));
$v->assert($date);
}
/**
* @depends constructorWithValidatorName
* @depends zendDateValidatorWithRespectMethods
* @expectedException \Respect\Validation\Exceptions\ZendException
*
* @test
*/
public function respectExceptionForFailedValidation(): void
{
$v = new Zend('Date');
$notValid = 'a';
self::assertFalse(
$v->validate($notValid),
'The validator returned true for an invalid value, this won\'t cause an exception later on.'
);
$v->assert($notValid);
}
/**
* @depends constructorWithValidatorName
* @depends constructorWithValidatorNameAndParams
* @depends zendDateValidatorWithRespectMethods
* @expectedException \Respect\Validation\Exceptions\ZendException
*
* @test
*/
public function paramsNot(): void
{
$v = new Zend('StringLength', ['min' => 10, 'max' => 25]);
$v->assert('aw');
return [
['ConfigProvider'],
[ConfigProvider::class],
['Zend\\Nonexistent\\Class'],
[ValidatorInterface::class],
];
}
}