diff --git a/src/Bundle/ChillPersonBundle/PersonIdentifier/Identifier/StringIdentifier.php b/src/Bundle/ChillPersonBundle/PersonIdentifier/Identifier/StringIdentifier.php index 4bc02c117..c570cd79b 100644 --- a/src/Bundle/ChillPersonBundle/PersonIdentifier/Identifier/StringIdentifier.php +++ b/src/Bundle/ChillPersonBundle/PersonIdentifier/Identifier/StringIdentifier.php @@ -16,11 +16,15 @@ use Chill\PersonBundle\Entity\Identifier\PersonIdentifierDefinition; use Chill\PersonBundle\PersonIdentifier\PersonIdentifierEngineInterface; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Validator\Context\ExecutionContextInterface; final readonly class StringIdentifier implements PersonIdentifierEngineInterface { public const NAME = 'chill-person-bundle.string-identifier'; + private const ONLY_NUMBERS = 'only_numbers'; + private const FIXED_LENGTH = 'fixed_length'; + public static function getName(): string { return self::NAME; @@ -45,4 +49,21 @@ final readonly class StringIdentifier implements PersonIdentifierEngineInterface { return '' === trim($identifier->getValue()['content'] ?? ''); } + + public function validate(ExecutionContextInterface $context, PersonIdentifier $identifier, PersonIdentifierDefinition $definition): void + { + $config = $definition->getData(); + $content = (string) ($identifier->getValue()['content'] ?? ''); + + if (($config[self::ONLY_NUMBERS] ?? false) && !preg_match('/^[0-9]+$/', $content)) { + $context->buildViolation('person_identifier.only_number') + ->addViolation(); + } + + if (null !== ($config[self::FIXED_LENGTH] ?? null) && strlen($content) !== $config[self::FIXED_LENGTH]) { + $context->buildViolation('person_identifier.fixed_length') + ->setParameter('limit', (string) $config[self::FIXED_LENGTH]) + ->addViolation(); + } + } } diff --git a/src/Bundle/ChillPersonBundle/Tests/PersonIdentifier/Identifier/StringIdentifierValidationTest.php b/src/Bundle/ChillPersonBundle/Tests/PersonIdentifier/Identifier/StringIdentifierValidationTest.php new file mode 100644 index 000000000..3b8190896 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/PersonIdentifier/Identifier/StringIdentifierValidationTest.php @@ -0,0 +1,128 @@ + 'String'], engine: StringIdentifier::getName(), data: $data); + } + + private function makeIdentifier(PersonIdentifierDefinition $definition, string $content): PersonIdentifier + { + $identifier = new PersonIdentifier($definition); + $identifier->setValue(['content' => $content]); + + return $identifier; + } + + /** + * Helper creating a context mock expecting zero violations. + */ + private function expectNoViolation(): ExecutionContextInterface + { + $context = $this->createMock(ExecutionContextInterface::class); + $context->expects(self::never())->method('buildViolation'); + + return $context; + } + + /** + * Helper creating a context mock expecting a single violation with a given message and optional parameters chain. + */ + private function expectSingleViolation(string $message, array $parameters = []): ExecutionContextInterface + { + /** @var ConstraintViolationBuilderInterface&MockObject $builder */ + $builder = $this->createMock(ConstraintViolationBuilderInterface::class); + + // If parameters are provided, ensure setParameter is called with each, in any order, and returns builder for chaining + if ($parameters) { + foreach ($parameters as $name => $value) { + $builder->expects(self::atLeastOnce()) + ->method('setParameter') + ->with($name, $value) + ->willReturn($builder); + } + } else { + $builder->expects(self::any())->method('setParameter')->willReturn($builder); + } + + $builder->expects(self::once())->method('addViolation'); + + $context = $this->createMock(ExecutionContextInterface::class); + $context->expects(self::once()) + ->method('buildViolation') + ->with($message) + ->willReturn($builder); + + return $context; + } + + public function testNoOptionsAcceptsAnyString(): void + { + $engine = new StringIdentifier(); + $definition = $this->makeDefinition([]); + $identifier = $this->makeIdentifier($definition, 'abc123'); + + $engine->validate($this->expectNoViolation(), $identifier, $definition); + } + + public function testOnlyNumbersValid(): void + { + $engine = new StringIdentifier(); + $definition = $this->makeDefinition(['only_numbers' => true]); + $identifier = $this->makeIdentifier($definition, '123456'); + + $engine->validate($this->expectNoViolation(), $identifier, $definition); + } + + public function testOnlyNumbersInvalid(): void + { + $engine = new StringIdentifier(); + $definition = $this->makeDefinition(['only_numbers' => true]); + $identifier = $this->makeIdentifier($definition, '12AB'); + + $engine->validate($this->expectSingleViolation('person_identifier.only_number'), $identifier, $definition); + } + + public function testFixedLengthValid(): void + { + $engine = new StringIdentifier(); + $definition = $this->makeDefinition(['fixed_length' => 5]); + $identifier = $this->makeIdentifier($definition, '12345'); + + $engine->validate($this->expectNoViolation(), $identifier, $definition); + } + + public function testFixedLengthInvalid(): void + { + $engine = new StringIdentifier(); + $definition = $this->makeDefinition(['fixed_length' => 5]); + $identifier = $this->makeIdentifier($definition, '1234'); + + $engine->validate($this->expectSingleViolation('person_identifier.fixed_length', ['limit' => 5]), $identifier, $definition); + } +}