mirror of
https://github.com/Respect/Validation.git
synced 2024-06-11 02:02:16 +02:00
Change how "Sf" rule works
Instead of creating the Symfony constraints itself "Sf" accepts an instance of "Symfony\Component\Validator\Constraint". Creating objects inside a rule, specially from an external library, makes the rule too complex and also limits the possibilities with the "Sf" rule since Symfony allows users to create complex validations (even thought their API is not as simple as ours). This commit also simplifies the way the messages are passed from Symfony to the "Sf" when only one constraint has failed; instead of passing the message of the whole constraint violation list, only the fist constraint violation message it passed. The problem that this rule will always have is that when using "Not" to invert the validation we have a way to get a proper message since Symfony Validator only return the result of constraints that failed. That's something the Respect\Validation does in a similar way and to change it a lot has to be changed. These changes were checked in "symfony/validator" 4.0 and the version was added to the "composer.json" file. Signed-off-by: Henrique Moody <henriquemoody@gmail.com>
This commit is contained in:
parent
fe7fed3461
commit
1da164a26e
|
@ -22,7 +22,7 @@
|
|||
"malukenho/docheader": "^0.1.4",
|
||||
"mikey179/vfsStream": "^1.6",
|
||||
"phpunit/phpunit": "^6.4",
|
||||
"symfony/validator": "^3.0",
|
||||
"symfony/validator": "^3.0||^4.0",
|
||||
"zendframework/zend-validator": "^2.0"
|
||||
},
|
||||
"suggest": {
|
||||
|
|
15
docs/Sf.md
15
docs/Sf.md
|
@ -1,19 +1,24 @@
|
|||
# Sf
|
||||
|
||||
- `Sf(string $validatorName)`
|
||||
- `Sf(Constraint $constraint)`
|
||||
- `Sf(Constraint $constraint, ValidatorInterface $validator)`
|
||||
|
||||
Use Symfony2 validators inside Respect\Validation flow. Messages
|
||||
are preserved.
|
||||
Validate the input with a Symfony Validator (>=4.0 or >=3.0) Constraint.
|
||||
|
||||
```php
|
||||
v::sf('Time')->validate('15:00:00');
|
||||
use Symfony\Component\Validator\Constraint\Iban;
|
||||
|
||||
v::sf(new Iban())->validate('NL39 RABO 0300 0652 64'); // true
|
||||
```
|
||||
|
||||
This rule will keep all the messages returned from Symfony.
|
||||
|
||||
## Changelog
|
||||
|
||||
Version | Description
|
||||
--------|-------------
|
||||
2.0.0 | Upgraded support to version >=3.0.0 of Symfony Validator
|
||||
2.0.0 | Do not create constraints anymore
|
||||
2.0.0 | Upgraded support to version >=4.0 or >=3.0 of Symfony Validator
|
||||
0.3.9 | Created
|
||||
|
||||
***
|
||||
|
|
|
@ -13,14 +13,21 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Validation\Exceptions;
|
||||
|
||||
class SfException extends ValidationException
|
||||
/**
|
||||
* @author Alexandre Gomes Gaigalas <alexandre@gaigalas.net>
|
||||
* @author Henrique Moody <henriquemoody@gmail.com>
|
||||
*/
|
||||
final class SfException extends ValidationException
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $defaultTemplates = [
|
||||
self::MODE_DEFAULT => [
|
||||
self::STANDARD => '{{name}}',
|
||||
self::STANDARD => '{{name}} must be valid for {{constraint}}',
|
||||
],
|
||||
self::MODE_NEGATIVE => [
|
||||
self::STANDARD => '{{name}}',
|
||||
self::STANDARD => '{{name}} must not be valid for {{constraint}}',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
|
|
@ -13,62 +13,83 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Validation\Rules;
|
||||
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
use Respect\Validation\Exceptions\ComponentException;
|
||||
use Respect\Validation\Exceptions\SfException;
|
||||
use Respect\Validation\Exceptions\ValidationException;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\Validation;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
class Sf extends AbstractRule
|
||||
/**
|
||||
* Validate the input with a Symfony Validator (>=4.0 or >=3.0) Constraint.
|
||||
*
|
||||
* @author Alexandre Gomes Gaigalas <alexandre@gaigalas.net>
|
||||
* @author Augusto Pascutti <augusto@phpsp.org.br>
|
||||
* @author Henrique Moody <henriquemoody@gmail.com>
|
||||
*/
|
||||
final class Sf extends AbstractRule
|
||||
{
|
||||
public const SYMFONY_CONSTRAINT_NAMESPACE = 'Symfony\Component\Validator\Constraints\%s';
|
||||
public $name;
|
||||
/**
|
||||
* @var Constraint
|
||||
*/
|
||||
private $constraint;
|
||||
|
||||
public function __construct($name, array $params = [])
|
||||
/**
|
||||
* @var ValidatorInterface
|
||||
*/
|
||||
private $validator;
|
||||
|
||||
/**
|
||||
* Initializes the rule with the Constraint and the Validator.
|
||||
*
|
||||
* In the the Validator is not defined, tries to create one.
|
||||
*
|
||||
* @param Constraint $constraint
|
||||
* @param ValidatorInterface|null $validator
|
||||
*/
|
||||
public function __construct(Constraint $constraint, ValidatorInterface $validator = null)
|
||||
{
|
||||
$this->name = ucfirst($name);
|
||||
$this->constraint = $this->createSymfonyConstraint($this->name, $params);
|
||||
}
|
||||
|
||||
private function createSymfonyConstraint($constraintName, array $constraintConstructorParameters = [])
|
||||
{
|
||||
$fullClassName = sprintf(self::SYMFONY_CONSTRAINT_NAMESPACE, $constraintName);
|
||||
try {
|
||||
$constraintReflection = new ReflectionClass($fullClassName);
|
||||
} catch (ReflectionException $previousException) {
|
||||
$baseExceptionMessage = 'Symfony/Validator constraint "%s" does not exist.';
|
||||
$exceptionMessage = sprintf($baseExceptionMessage, $constraintName);
|
||||
throw new ComponentException($exceptionMessage, 0, $previousException);
|
||||
}
|
||||
if ($constraintReflection->hasMethod('__construct')) {
|
||||
return $constraintReflection->newInstanceArgs($constraintConstructorParameters);
|
||||
}
|
||||
|
||||
return $constraintReflection->newInstance();
|
||||
}
|
||||
|
||||
private function returnViolationsForConstraint($valueToValidate, Constraint $symfonyConstraint)
|
||||
{
|
||||
$validator = Validation::createValidator(); // You gotta love those Symfony namings
|
||||
|
||||
return $validator->validate($valueToValidate, $symfonyConstraint);
|
||||
$this->constraint = $constraint;
|
||||
$this->validator = $validator ?: Validation::createValidator();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function assert($input): void
|
||||
{
|
||||
$violations = $this->returnViolationsForConstraint($input, $this->constraint);
|
||||
if (0 == count($violations)) {
|
||||
$violations = $this->validator->validate($input, $this->constraint);
|
||||
if (0 === $violations->count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw $this->reportError((string) $violations);
|
||||
if (1 === $violations->count()) {
|
||||
throw $this->reportError($input, ['violations' => $violations[0]->getMessage()]);
|
||||
}
|
||||
|
||||
throw $this->reportError($input, ['violations' => trim((string) $violations)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reportError($input, array $extraParams = []): ValidationException
|
||||
{
|
||||
$exception = parent::reportError($input, $extraParams);
|
||||
if (isset($extraParams['violations'])) {
|
||||
$exception->updateTemplate($extraParams['violations']);
|
||||
}
|
||||
|
||||
return $exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate($input): bool
|
||||
{
|
||||
$violations = $this->returnViolationsForConstraint($input, $this->constraint);
|
||||
if (count($violations)) {
|
||||
try {
|
||||
$this->assert($input);
|
||||
} catch (SfException $exception) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@ use Respect\Validation\Exceptions\ComponentException;
|
|||
use Respect\Validation\Exceptions\ValidationException;
|
||||
use Respect\Validation\Rules\AllOf;
|
||||
use Respect\Validation\Rules\Key;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
/**
|
||||
* @method static Validator allOf(Validatable ...$rule)
|
||||
|
@ -136,7 +138,7 @@ use Respect\Validation\Rules\Key;
|
|||
* @method static Validator resourceType()
|
||||
* @method static Validator roman()
|
||||
* @method static Validator scalarVal()
|
||||
* @method static Validator sf(string $name, array $params = null)
|
||||
* @method static Validator sf(Constraint $constraint, ValidatorInterface $validator = null)
|
||||
* @method static Validator size(string $minSize = null, string $maxSize = null)
|
||||
* @method static Validator slug()
|
||||
* @method static Validator space(string $additionalChars = null)
|
||||
|
|
55
tests/integration/rules/sf.phpt
Normal file
55
tests/integration/rules/sf.phpt
Normal file
|
@ -0,0 +1,55 @@
|
|||
--FILE--
|
||||
<?php
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
use Respect\Validation\Exceptions\NestedValidationException;
|
||||
use Respect\Validation\Exceptions\SfException;
|
||||
use Respect\Validation\Validator as v;
|
||||
use Symfony\Component\Validator\Constraints\Collection;
|
||||
use Symfony\Component\Validator\Constraints\Email;
|
||||
use Symfony\Component\Validator\Constraints\IsNull;
|
||||
|
||||
try {
|
||||
v::sf(new IsNull())->check('something');
|
||||
} catch (SfException $exception) {
|
||||
echo $exception->getMessage().PHP_EOL;
|
||||
}
|
||||
|
||||
try {
|
||||
v::not(v::sf(new IsNull()))->check(null);
|
||||
} catch (SfException $exception) {
|
||||
echo $exception->getMessage().PHP_EOL;
|
||||
}
|
||||
|
||||
try {
|
||||
v::sf(new Email())->assert('not-null');
|
||||
} catch (NestedValidationException $exception) {
|
||||
echo $exception->getFullMessage().PHP_EOL;
|
||||
}
|
||||
|
||||
try {
|
||||
v::not(v::sf(new Email()))->assert('example@example.com');
|
||||
} catch (NestedValidationException $exception) {
|
||||
echo $exception->getFullMessage().PHP_EOL;
|
||||
}
|
||||
|
||||
try {
|
||||
v::sf(
|
||||
new Collection([
|
||||
'first' => new IsNull(),
|
||||
'second' => new Email(),
|
||||
])
|
||||
)->check(['second' => 'not-email']);
|
||||
} catch (SfException $exception) {
|
||||
echo $exception->getMessage();
|
||||
}
|
||||
?>
|
||||
--EXPECTF--
|
||||
This value should be null.
|
||||
`NULL` must not be valid for `[object] (Symfony\Component\Validator\Constraints\IsNull: { %s })`
|
||||
- This value is not a valid email address.
|
||||
- "example@example.com" must not be valid for `[object] (Symfony\Component\Validator\Constraints\Email: { %s })`
|
||||
Array[first]:
|
||||
This field is missing. (code %s)
|
||||
Array[second]:
|
||||
This value is not a valid email address. (code %s)
|
|
@ -14,84 +14,70 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Rules;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Respect\Validation\Exceptions\AllOfException;
|
||||
use Respect\Validation\Validator as v;
|
||||
use stdClass;
|
||||
use Symfony\Component\Validator\Constraints\IsFalse;
|
||||
use Symfony\Component\Validator\Constraints\IsNull;
|
||||
use Symfony\Component\Validator\Validation;
|
||||
use Symfony\Component\Validator\Validator\TraceableValidator;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
/**
|
||||
* @group rule
|
||||
* @covers \Respect\Validation\Exceptions\SfException
|
||||
* @group rule
|
||||
*
|
||||
* @covers \Respect\Validation\Rules\Sf
|
||||
*
|
||||
* @author Augusto Pascutti <augusto@phpsp.org.br>
|
||||
* @author Gabriel Caruso <carusogabriel34@gmail.com>
|
||||
* @author Henrique Moody <henriquemoody@gmail.com>
|
||||
*/
|
||||
class SfTest extends TestCase
|
||||
final class SfTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function validationWithAnExistingValidationConstraint(): void
|
||||
public function itShouldValidateWithDefinedConstraintAndValidator(): void
|
||||
{
|
||||
$constraintName = 'Time';
|
||||
$validConstraintValue = '04:20:00';
|
||||
$invalidConstraintValue = 'yada';
|
||||
self::assertTrue(
|
||||
v::sf($constraintName)->validate($validConstraintValue),
|
||||
sprintf('"%s" should be valid under "%s" constraint.', $validConstraintValue, $constraintName)
|
||||
);
|
||||
self::assertFalse(
|
||||
v::sf($constraintName)->validate($invalidConstraintValue),
|
||||
sprintf('"%s" should be invalid under "%s" constraint.', $invalidConstraintValue, $constraintName)
|
||||
);
|
||||
$sut = new Sf(new IsNull());
|
||||
|
||||
self::assertTrue($sut->validate(null));
|
||||
}
|
||||
|
||||
/**
|
||||
* @doesNotPerformAssertions
|
||||
*
|
||||
* @depends validationWithAnExistingValidationConstraint
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function assertionWithAnExistingValidationConstraint(): void
|
||||
public function itShouldInvalidateWithDefinedConstraintAndValidator(): void
|
||||
{
|
||||
$constraintName = 'Time';
|
||||
$validConstraintValue = '04:20:00';
|
||||
v::sf($constraintName)->assert($validConstraintValue);
|
||||
$sut = new Sf(new IsFalse());
|
||||
|
||||
self::assertFalse($sut->validate(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends assertionWithAnExistingValidationConstraint
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function assertionMessageWithAnExistingValidationConstraint()
|
||||
public function itShouldHaveAValidatorByDefault(): void
|
||||
{
|
||||
$constraintName = 'Time';
|
||||
$invalidConstraintValue = '34:90:70';
|
||||
try {
|
||||
v::sf($constraintName)->assert($invalidConstraintValue);
|
||||
} catch (AllOfException $exception) {
|
||||
$fullValidationMessage = $exception->getFullMessage();
|
||||
$expectedValidationException = <<<'EOF'
|
||||
- Time
|
||||
EOF;
|
||||
$sut = new Sf(new IsNull());
|
||||
|
||||
return self::assertEquals(
|
||||
$expectedValidationException,
|
||||
$fullValidationMessage,
|
||||
'Exception message is different from the one expected.'
|
||||
);
|
||||
self::assertAttributeInstanceOf(ValidatorInterface::class, 'validator', $sut);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function itShouldUseTheDefinedValidatorToValidate(): void
|
||||
{
|
||||
if (!class_exists(TraceableValidator::class)) {
|
||||
self::markTestSkipped('The current version of Symfony Validator does not have '.TraceableValidator::class);
|
||||
}
|
||||
self::fail('Validation exception expected to compare message.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Respect\Validation\Exceptions\ComponentException
|
||||
* @expectedExceptionMessage Symfony/Validator constraint "FluxCapacitor" does not exist.
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function validationWithNonExistingConstraint(): void
|
||||
{
|
||||
$fantasyConstraintName = 'FluxCapacitor';
|
||||
$fantasyValue = '8GW';
|
||||
v::sf($fantasyConstraintName)->validate($fantasyValue);
|
||||
$input = new stdClass();
|
||||
|
||||
$validator = new TraceableValidator(Validation::createValidator());
|
||||
|
||||
$sut = new Sf(new IsNull(), $validator);
|
||||
$sut->validate($input);
|
||||
|
||||
self::assertSame($input, $validator->getCollectedData()[0]['context']['value']);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue