diff --git a/src/Symfony/Component/Validator/CHANGELOG.md b/src/Symfony/Component/Validator/CHANGELOG.md index 3863b5717a1fe..dc0fe8f744123 100644 --- a/src/Symfony/Component/Validator/CHANGELOG.md +++ b/src/Symfony/Component/Validator/CHANGELOG.md @@ -15,6 +15,7 @@ CHANGELOG * deprecated using the `Bic`, `Country`, `Currency`, `Language` and `Locale` constraints without `symfony/intl` * deprecated using the `Email` constraint without `egulias/email-validator` * deprecated using the `Expression` constraint without `symfony/expression-language` + * added options `iban` and `ibanPropertyPath` to Bic constraint 4.1.0 ----- diff --git a/src/Symfony/Component/Validator/Constraints/Bic.php b/src/Symfony/Component/Validator/Constraints/Bic.php index 9af23c8ddb89f..c72d7d677ef3e 100644 --- a/src/Symfony/Component/Validator/Constraints/Bic.php +++ b/src/Symfony/Component/Validator/Constraints/Bic.php @@ -12,7 +12,9 @@ namespace Symfony\Component\Validator\Constraints; use Symfony\Component\Intl\Intl; +use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; use Symfony\Component\Validator\Exception\LogicException; /** @@ -28,6 +30,7 @@ class Bic extends Constraint const INVALID_BANK_CODE_ERROR = '00559357-6170-4f29-aebd-d19330aa19cf'; const INVALID_COUNTRY_CODE_ERROR = '1ce76f8d-3c1f-451c-9e62-fe9c3ed486ae'; const INVALID_CASE_ERROR = '11884038-3312-4ae5-9d04-699f782130c7'; + const INVALID_IBAN_COUNTRY_CODE_ERROR = '29a2c3bb-587b-4996-b6f5-53081364cea5'; protected static $errorNames = array( self::INVALID_LENGTH_ERROR => 'INVALID_LENGTH_ERROR', @@ -38,14 +41,25 @@ class Bic extends Constraint ); public $message = 'This is not a valid Business Identifier Code (BIC).'; + public $ibanMessage = 'This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}.'; + public $iban; + public $ibanPropertyPath; - public function __construct($options = null) + public function __construct(array $options = null) { if (!class_exists(Intl::class)) { // throw new LogicException(sprintf('The "symfony/intl" component is required to use the "%s" constraint.', __CLASS__)); @trigger_error(sprintf('Using the "%s" constraint without the "symfony/intl" component installed is deprecated since Symfony 4.2.', __CLASS__), E_USER_DEPRECATED); } + if (isset($options['iban']) && isset($options['ibanPropertyPath'])) { + throw new ConstraintDefinitionException(sprintf('The "iban" and "ibanPropertyPath" options of the Iban constraint cannot be used at the same time.', \get_class($this))); + } + + if (isset($options['ibanPropertyPath']) && !class_exists(PropertyAccess::class)) { + throw new LogicException(sprintf('The "symfony/property-access" component is required to use the "%s" constraint with the "ibanPropertyPath" option.', \get_class($this))); + } + parent::__construct($options); } } diff --git a/src/Symfony/Component/Validator/Constraints/BicValidator.php b/src/Symfony/Component/Validator/Constraints/BicValidator.php index 7daf9a9c250d8..de1195202203c 100644 --- a/src/Symfony/Component/Validator/Constraints/BicValidator.php +++ b/src/Symfony/Component/Validator/Constraints/BicValidator.php @@ -12,8 +12,12 @@ namespace Symfony\Component\Validator\Constraints; use Symfony\Component\Intl\Intl; +use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; +use Symfony\Component\PropertyAccess\PropertyAccess; +use Symfony\Component\PropertyAccess\PropertyAccessor; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; use Symfony\Component\Validator\Exception\LogicException; use Symfony\Component\Validator\Exception\UnexpectedValueException; @@ -24,6 +28,13 @@ */ class BicValidator extends ConstraintValidator { + private $propertyAccessor; + + public function __construct(PropertyAccessor $propertyAccessor = null) + { + $this->propertyAccessor = $propertyAccessor; + } + /** * {@inheritdoc} */ @@ -95,5 +106,39 @@ public function validate($value, Constraint $constraint) return; } + + // check against an IBAN + $iban = $constraint->iban; + $path = $constraint->ibanPropertyPath; + if ($path && null !== $object = $this->context->getObject()) { + try { + $iban = $this->getPropertyAccessor()->getValue($object, $path); + } catch (NoSuchPropertyException $e) { + throw new ConstraintDefinitionException(sprintf('Invalid property path "%s" provided to "%s" constraint: %s', $path, \get_class($constraint), $e->getMessage()), 0, $e); + } + } + if (!$iban) { + return; + } + $ibanCountryCode = substr($iban, 0, 2); + if (ctype_alpha($ibanCountryCode) && substr($canonicalize, 4, 2) !== $ibanCountryCode) { + $this->context->buildViolation($constraint->ibanMessage) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setParameter('{{ iban }}', $iban) + ->setCode(Bic::INVALID_IBAN_COUNTRY_CODE_ERROR) + ->addViolation(); + } + } + + private function getPropertyAccessor(): PropertyAccessor + { + if (null === $this->propertyAccessor) { + if (!class_exists('Symfony\Component\PropertyAccess\PropertyAccess')) { + throw new LogicException('Unable to use property path as the Symfony PropertyAccess component is not installed.'); + } + $this->propertyAccessor = PropertyAccess::createPropertyAccessor(); + } + + return $this->propertyAccessor; } } diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf index 4bb2760b418e3..30e6804c7b83e 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf @@ -326,6 +326,10 @@ This value should be a multiple of {{ compared_value }}. This value should be a multiple of {{ compared_value }}. + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf index c7ee2795b553e..67441127cda1a 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf @@ -326,6 +326,10 @@ This value should be a multiple of {{ compared_value }}. Cette valeur doit être un multiple de {{ compared_value }}. + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + Ce BIC n'est pas associé à l'IBAN {{ iban }}. + diff --git a/src/Symfony/Component/Validator/Tests/Constraints/BicValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/BicValidatorTest.php index 231843a792d41..2d57ab0ec3c83 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/BicValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/BicValidatorTest.php @@ -13,6 +13,7 @@ use Symfony\Component\Validator\Constraints\Bic; use Symfony\Component\Validator\Constraints\BicValidator; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; class BicValidatorTest extends ConstraintValidatorTestCase @@ -36,6 +37,113 @@ public function testEmptyStringIsValid() $this->assertNoViolation(); } + public function testValidComparisonToPropertyPath() + { + $constraint = new Bic(array('ibanPropertyPath' => 'value')); + + $object = new BicComparisonTestClass('FR14 2004 1010 0505 0001 3M02 606'); + + $this->setObject($object); + + $this->validator->validate('SOGEFRPP', $constraint); + + $this->assertNoViolation(); + } + + public function testValidComparisonToPropertyPathOnArray() + { + $constraint = new Bic(array('ibanPropertyPath' => '[root][value]')); + + $this->setObject(array('root' => array('value' => 'FR14 2004 1010 0505 0001 3M02 606'))); + + $this->validator->validate('SOGEFRPP', $constraint); + + $this->assertNoViolation(); + } + + public function testInvalidComparisonToPropertyPath() + { + $constraint = new Bic(array('ibanPropertyPath' => 'value')); + $constraint->ibanMessage = 'Constraint Message'; + + $object = new BicComparisonTestClass('FR14 2004 1010 0505 0001 3M02 606'); + + $this->setObject($object); + + $this->validator->validate('UNCRIT2B912', $constraint); + + $this->buildViolation('Constraint Message') + ->setParameter('{{ value }}', '"UNCRIT2B912"') + ->setParameter('{{ iban }}', 'FR14 2004 1010 0505 0001 3M02 606') + ->setCode(Bic::INVALID_IBAN_COUNTRY_CODE_ERROR) + ->assertRaised(); + } + + public function testValidComparisonToValue() + { + $constraint = new Bic(array('iban' => 'FR14 2004 1010 0505 0001 3M02 606')); + $constraint->ibanMessage = 'Constraint Message'; + + $this->validator->validate('SOGEFRPP', $constraint); + + $this->assertNoViolation(); + } + + public function testInvalidComparisonToValue() + { + $constraint = new Bic(array('iban' => 'FR14 2004 1010 0505 0001 3M02 606')); + $constraint->ibanMessage = 'Constraint Message'; + + $this->validator->validate('UNCRIT2B912', $constraint); + + $this->buildViolation('Constraint Message') + ->setParameter('{{ value }}', '"UNCRIT2B912"') + ->setParameter('{{ iban }}', 'FR14 2004 1010 0505 0001 3M02 606') + ->setCode(Bic::INVALID_IBAN_COUNTRY_CODE_ERROR) + ->assertRaised(); + } + + public function testNoViolationOnNullObjectWithPropertyPath() + { + $constraint = new Bic(array('ibanPropertyPath' => 'propertyPath')); + + $this->setObject(null); + + $this->validator->validate('UNCRIT2B912', $constraint); + + $this->assertNoViolation(); + } + + /** + * @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException + * @expectedExceptionMessage The "iban" and "ibanPropertyPath" options of the Iban constraint cannot be used at the same time + */ + public function testThrowsConstraintExceptionIfBothValueAndPropertyPath() + { + new Bic(array( + 'iban' => 'value', + 'ibanPropertyPath' => 'propertyPath', + )); + } + + public function testInvalidValuePath() + { + $constraint = new Bic(array('ibanPropertyPath' => 'foo')); + + if (method_exists($this, 'expectException')) { + $this->expectException(ConstraintDefinitionException::class); + $this->expectExceptionMessage(sprintf('Invalid property path "foo" provided to "%s" constraint', \get_class($constraint))); + } else { + $this->setExpectedException(ConstraintDefinitionException::class, sprintf('Invalid property path "foo" provided to "%s" constraint', \get_class($constraint))); + } + + $object = new BicComparisonTestClass(5); + + $this->setObject($object); + + $this->validator->validate('UNCRIT2B912', $constraint); + } + /** * @expectedException \Symfony\Component\Validator\Exception\UnexpectedValueException */ @@ -114,3 +222,23 @@ public function getInvalidBics() ); } } + +class BicComparisonTestClass +{ + protected $value; + + public function __construct($value) + { + $this->value = $value; + } + + public function __toString() + { + return (string) $this->value; + } + + public function getValue() + { + return $this->value; + } +}