diff --git a/src/Bundle/ChillPersonBundle/Entity/Identifier/PersonIdentifier.php b/src/Bundle/ChillPersonBundle/Entity/Identifier/PersonIdentifier.php index b0c79ccae..d851535b6 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Identifier/PersonIdentifier.php +++ b/src/Bundle/ChillPersonBundle/Entity/Identifier/PersonIdentifier.php @@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Entity\Identifier; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\PersonIdentifier\Validator\UniqueIdentifierConstraint; +use Chill\PersonBundle\PersonIdentifier\Validator\ValidIdentifierConstraint; use Doctrine\ORM\Mapping as ORM; #[ORM\Entity] @@ -20,6 +21,7 @@ use Doctrine\ORM\Mapping as ORM; #[ORM\UniqueConstraint(name: 'chill_person_identifier_unique', columns: ['definition_id', 'canonical'])] #[ORM\UniqueConstraint(name: 'chill_person_identifier_unique_person_definition', columns: ['definition_id', 'person_id'])] #[UniqueIdentifierConstraint] +#[ValidIdentifierConstraint] class PersonIdentifier { #[ORM\Id] diff --git a/src/Bundle/ChillPersonBundle/PersonIdentifier/PersonIdentifierEngineInterface.php b/src/Bundle/ChillPersonBundle/PersonIdentifier/PersonIdentifierEngineInterface.php index b467cf3d0..f68dd86d7 100644 --- a/src/Bundle/ChillPersonBundle/PersonIdentifier/PersonIdentifierEngineInterface.php +++ b/src/Bundle/ChillPersonBundle/PersonIdentifier/PersonIdentifierEngineInterface.php @@ -14,6 +14,7 @@ namespace Chill\PersonBundle\PersonIdentifier; use Chill\PersonBundle\Entity\Identifier\PersonIdentifier; use Chill\PersonBundle\Entity\Identifier\PersonIdentifierDefinition; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Validator\Context\ExecutionContextInterface; interface PersonIdentifierEngineInterface { @@ -32,4 +33,6 @@ interface PersonIdentifierEngineInterface * by the definition, the validation will fails. */ public function isEmpty(PersonIdentifier $identifier): bool; + + public function validate(ExecutionContextInterface $context, PersonIdentifier $identifier, PersonIdentifierDefinition $definition): void; } diff --git a/src/Bundle/ChillPersonBundle/PersonIdentifier/PersonIdentifierManagerInterface.php b/src/Bundle/ChillPersonBundle/PersonIdentifier/PersonIdentifierManagerInterface.php index 326454bb4..eddcb6095 100644 --- a/src/Bundle/ChillPersonBundle/PersonIdentifier/PersonIdentifierManagerInterface.php +++ b/src/Bundle/ChillPersonBundle/PersonIdentifier/PersonIdentifierManagerInterface.php @@ -24,6 +24,8 @@ interface PersonIdentifierManagerInterface /** * @param int|PersonIdentifierDefinition $personIdentifierDefinition an instance of PersonIdentifierDefinition, or his id + * + * @throw PersonIdentifierNotFoundException */ public function buildWorkerByPersonIdentifierDefinition(int|PersonIdentifierDefinition $personIdentifierDefinition): PersonIdentifierWorker; } diff --git a/src/Bundle/ChillPersonBundle/PersonIdentifier/PersonIdentifierWorker.php b/src/Bundle/ChillPersonBundle/PersonIdentifier/PersonIdentifierWorker.php index dc0279a21..3bc2cf1be 100644 --- a/src/Bundle/ChillPersonBundle/PersonIdentifier/PersonIdentifierWorker.php +++ b/src/Bundle/ChillPersonBundle/PersonIdentifier/PersonIdentifierWorker.php @@ -14,8 +14,9 @@ namespace Chill\PersonBundle\PersonIdentifier; use Chill\PersonBundle\Entity\Identifier\PersonIdentifier; use Chill\PersonBundle\Entity\Identifier\PersonIdentifierDefinition; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Validator\Context\ExecutionContextInterface; -final readonly class PersonIdentifierWorker +readonly class PersonIdentifierWorker { public function __construct( private PersonIdentifierEngineInterface $identifierEngine, @@ -54,4 +55,9 @@ final readonly class PersonIdentifierWorker { return $this->identifierEngine->isEmpty($identifier); } + + public function validate(ExecutionContextInterface $context, PersonIdentifier $identifier, PersonIdentifierDefinition $definition): void + { + $this->identifierEngine->validate($context, $identifier, $definition); + } } diff --git a/src/Bundle/ChillPersonBundle/PersonIdentifier/Validator/ValidIdentifierConstraint.php b/src/Bundle/ChillPersonBundle/PersonIdentifier/Validator/ValidIdentifierConstraint.php new file mode 100644 index 000000000..24b3551b1 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/PersonIdentifier/Validator/ValidIdentifierConstraint.php @@ -0,0 +1,23 @@ +personIdentifierManager->buildWorkerByPersonIdentifierDefinition($value->getDefinition()); + + $worker->validate($this->context, $value, $value->getDefinition()); + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/PersonIdentifierListApiControllerTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/PersonIdentifierListApiControllerTest.php index bdeba7104..058b56ecb 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Controller/PersonIdentifierListApiControllerTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Controller/PersonIdentifierListApiControllerTest.php @@ -14,6 +14,7 @@ namespace Chill\PersonBundle\Tests\Controller; use Chill\MainBundle\Pagination\PaginatorFactoryInterface; use Chill\MainBundle\Serializer\Normalizer\CollectionNormalizer; use Chill\PersonBundle\Controller\PersonIdentifierListApiController; +use Chill\PersonBundle\Entity\Identifier\PersonIdentifier; use Chill\PersonBundle\Entity\Identifier\PersonIdentifierDefinition; use Chill\PersonBundle\PersonIdentifier\Normalizer\PersonIdentifierWorkerNormalizer; use Chill\PersonBundle\PersonIdentifier\PersonIdentifierEngineInterface; @@ -25,6 +26,7 @@ use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\Security\Core\Security; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Serializer; +use Symfony\Component\Validator\Context\ExecutionContextInterface; /** * @internal @@ -72,10 +74,12 @@ class PersonIdentifierListApiControllerTest extends TestCase public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder, PersonIdentifierDefinition $personIdentifierDefinition): void {} - public function renderAsString(?\Chill\PersonBundle\Entity\Identifier\PersonIdentifier $identifier, PersonIdentifierDefinition $definition): string + public function renderAsString(?PersonIdentifier $identifier, PersonIdentifierDefinition $definition): string { return ''; } + + public function validate(ExecutionContextInterface $context, PersonIdentifier $identifier, PersonIdentifierDefinition $definition): void {} }; $definition1 = new PersonIdentifierDefinition(['en' => 'Label 1'], 'dummy'); diff --git a/src/Bundle/ChillPersonBundle/Tests/PersonIdentifier/Normalizer/PersonIdentifierWorkerNormalizerTest.php b/src/Bundle/ChillPersonBundle/Tests/PersonIdentifier/Normalizer/PersonIdentifierWorkerNormalizerTest.php index 8635e1615..936314ad9 100644 --- a/src/Bundle/ChillPersonBundle/Tests/PersonIdentifier/Normalizer/PersonIdentifierWorkerNormalizerTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/PersonIdentifier/Normalizer/PersonIdentifierWorkerNormalizerTest.php @@ -11,12 +11,14 @@ declare(strict_types=1); namespace Chill\PersonBundle\Tests\PersonIdentifier\Normalizer; +use Chill\PersonBundle\Entity\Identifier\PersonIdentifier; use Chill\PersonBundle\Entity\Identifier\PersonIdentifierDefinition; use Chill\PersonBundle\PersonIdentifier\Normalizer\PersonIdentifierWorkerNormalizer; use Chill\PersonBundle\PersonIdentifier\PersonIdentifierEngineInterface; use Chill\PersonBundle\PersonIdentifier\PersonIdentifierWorker; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Exception\UnexpectedValueException; +use Symfony\Component\Validator\Context\ExecutionContextInterface; /** * @internal @@ -40,7 +42,7 @@ class PersonIdentifierWorkerNormalizerTest extends TestCase public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder, PersonIdentifierDefinition $personIdentifierDefinition): void {} - public function renderAsString(?\Chill\PersonBundle\Entity\Identifier\PersonIdentifier $identifier, PersonIdentifierDefinition $definition): string + public function renderAsString(?PersonIdentifier $identifier, PersonIdentifierDefinition $definition): string { return ''; } @@ -70,10 +72,12 @@ class PersonIdentifierWorkerNormalizerTest extends TestCase public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder, PersonIdentifierDefinition $personIdentifierDefinition): void {} - public function renderAsString(?\Chill\PersonBundle\Entity\Identifier\PersonIdentifier $identifier, PersonIdentifierDefinition $definition): string + public function renderAsString(?PersonIdentifier $identifier, PersonIdentifierDefinition $definition): string { return ''; } + + public function validate(ExecutionContextInterface $context, PersonIdentifier $identifier, PersonIdentifierDefinition $definition): void {} }; $definition = new PersonIdentifierDefinition(label: ['en' => 'SSN'], engine: 'string'); diff --git a/src/Bundle/ChillPersonBundle/Tests/PersonIdentifier/Rendering/PersonIdRenderingTest.php b/src/Bundle/ChillPersonBundle/Tests/PersonIdentifier/Rendering/PersonIdRenderingTest.php index ce1df3f9d..11e6bb70b 100644 --- a/src/Bundle/ChillPersonBundle/Tests/PersonIdentifier/Rendering/PersonIdRenderingTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/PersonIdentifier/Rendering/PersonIdRenderingTest.php @@ -24,6 +24,7 @@ use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; +use Symfony\Component\Validator\Context\ExecutionContextInterface; /** * @internal @@ -71,6 +72,8 @@ class PersonIdRenderingTest extends TestCase // same behavior as StringIdentifier::renderAsString return $identifier?->getValue()['content'] ?? ''; } + + public function validate(ExecutionContextInterface $context, PersonIdentifier $identifier, PersonIdentifierDefinition $definition): void {} }; return new PersonIdentifierWorker($engine, $definition); diff --git a/src/Bundle/ChillPersonBundle/Tests/PersonIdentifier/Validator/ValidIdentifierConstraintValidatorTest.php b/src/Bundle/ChillPersonBundle/Tests/PersonIdentifier/Validator/ValidIdentifierConstraintValidatorTest.php new file mode 100644 index 000000000..8f698c497 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/PersonIdentifier/Validator/ValidIdentifierConstraintValidatorTest.php @@ -0,0 +1,79 @@ +manager is set in setUp() before parent::setUp() calls this method + return new ValidIdentifierConstraintValidator($this->manager); + } + + protected function setUp(): void + { + // Prepare manager prophecy and reveal it before parent::setUp() + $managerProphecy = $this->prophesize(PersonIdentifierManagerInterface::class); + $this->manager = $managerProphecy->reveal(); + + // Store the prophecy itself for later configuration in tests + $this->managerProphecy = $managerProphecy; // dynamic property for test methods + + parent::setUp(); + } + + public function testItCallsEngineValidateThroughWorker(): void + { + // Arrange a definition and corresponding identifier + $definition = new PersonIdentifierDefinition(label: ['en' => 'Any'], engine: 'any.engine'); + $identifier = new PersonIdentifier($definition); + + // Create an engine prophecy; we only care that validate() is called once with expected args + $engineProphecy = $this->prophesize(\Chill\PersonBundle\PersonIdentifier\PersonIdentifierEngineInterface::class); + $engineProphecy + ->validate($this->context, $identifier, $definition) + ->shouldBeCalled(); + + // Build a real worker that wraps our mocked engine and the concrete definition + $worker = new PersonIdentifierWorker($engineProphecy->reveal(), $definition); + + // Configure the manager to return our worker for this definition + $this->managerProphecy + ->buildWorkerByPersonIdentifierDefinition($definition) + ->willReturn($worker); + + // Act: run the validator + $this->validator->validate($identifier, new ValidIdentifierConstraint()); + + // Assert: no explicit assertion needed; Prophecy will fail if method wasn't called + $this->assertNoViolation(); + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/PersonJsonDenormalizerTest.php b/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/PersonJsonDenormalizerTest.php index cfa947d44..596ecf9c8 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/PersonJsonDenormalizerTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/PersonJsonDenormalizerTest.php @@ -25,6 +25,7 @@ use libphonenumber\PhoneNumber; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; +use Symfony\Component\Validator\Context\ExecutionContextInterface; /** * @internal @@ -79,6 +80,8 @@ final class PersonJsonDenormalizerTest extends TestCase return '' === $content; } + + public function validate(ExecutionContextInterface $context, PersonIdentifier $identifier, PersonIdentifierDefinition $definition): void {} }; return new PersonIdentifierWorker($engine, $definition); diff --git a/src/Bundle/ChillPersonBundle/translations/validators+intl-icu.fr.yaml b/src/Bundle/ChillPersonBundle/translations/validators+intl-icu.fr.yaml new file mode 100644 index 000000000..3b65462e1 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/translations/validators+intl-icu.fr.yaml @@ -0,0 +1,7 @@ +person_identifier: + fixed_length: >- + {limit, plural, + =1 {L'identifier doit contenir exactement 1 caractère} + other {L'identifiant doit contenir exactement # caractères} + } + only_number: "L'identifiant ne doit contenir que des chiffres"