Add logger and handle fields birthdate,phoneNumber and mobileNumber

This commit is contained in:
Boris Waaub
2026-03-09 16:26:24 +01:00
parent 2b7f586284
commit 2a478d0873
3 changed files with 609 additions and 25 deletions

View File

@@ -20,18 +20,25 @@ use Chill\MainBundle\Repository\GenderRepository;
use Chill\MainBundle\Repository\PostalCodeRepositoryInterface;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Household\MembersEditorFactory;
use Chill\PersonBundle\PersonIdentifier\PersonIdentifierManagerInterface;
use Chill\PersonBundle\Repository\Identifier\PersonIdentifierDefinitionRepository;
use Chill\PersonBundle\Repository\Identifier\PersonIdentifierRepository;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\Identifier\PersonIdentifier;
use Chill\PersonBundle\Actions\Upsert\UpsertMessage;
use libphonenumber\NumberParseException;
use Psr\Log\LoggerInterface;
use Symfony\Component\Clock\ClockInterface;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;
use Symfony\Component\Validator\Validator\ValidatorInterface;
#[AsMessageHandler]
readonly class PersonUpsertHandler
{
private const LOG_PREFIX = '[PersonUpsertHandler] ';
public function __construct(
private PersonIdentifierDefinitionRepository $personIdentifierDefinitionRepository,
private PersonIdentifierRepository $personIdentifierRepository,
@@ -41,25 +48,29 @@ readonly class PersonUpsertHandler
private CenterRepositoryInterface $centerRepository,
private GenderRepository $genderRepository,
private ClockInterface $clock,
private \libphonenumber\PhoneNumberUtil $phoneNumberUtil,
private LoggerInterface $logger,
private PersonIdentifierManagerInterface $personIdentifierManager,
private ValidatorInterface $validator,
) {}
private function createAddressWithMessage(UpsertMessage $message): Address
{
$newAddress = new Address();
if (null !== $message->address) {
$newAddress->setStreet($message->address);
if (null !== $message->addressStreet) {
$newAddress->setStreet($message->addressStreet);
}
if (null !== $message->streetNumber) {
$newAddress->setStreetNumber($message->streetNumber);
if (null !== $message->addressStreetNumber) {
$newAddress->setStreetNumber($message->addressStreetNumber);
}
if (null !== $message->postcode) {
$postalCode = $this->postalCodeRepository->findOneBy(['code' => $message->postcode]);
if (null !== $message->addressPostcode) {
$postalCode = $this->postalCodeRepository->findOneBy(['code' => $message->addressPostcode]);
if (null !== $postalCode) {
$newAddress->setPostcode($postalCode);
}
}
if (null !== $message->extra) {
$newAddress->setExtra($message->extra);
if (null !== $message->addressExtra) {
$newAddress->setExtra($message->addressExtra);
}
$newAddress->setValidFrom(\DateTime::createFromImmutable($this->clock->now()));
@@ -68,10 +79,10 @@ readonly class PersonUpsertHandler
private function isMessageAddressMatch(Address $existingAddress, UpsertMessage $message): bool
{
$streetMatches = $message->address === $existingAddress->getStreet();
$streetNumberMatches = $message->streetNumber === $existingAddress->getStreetNumber();
$streetMatches = $message->addressStreet === $existingAddress->getStreet();
$streetNumberMatches = $message->addressStreetNumber === $existingAddress->getStreetNumber();
$postcodeMatches = null !== $existingAddress->getPostcode()
&& $message->postcode === $existingAddress->getPostcode()->getCode();
&& $message->addressPostcode === $existingAddress->getPostcode()->getCode();
return $streetMatches && $streetNumberMatches && $postcodeMatches;
}
@@ -113,17 +124,15 @@ readonly class PersonUpsertHandler
private function handlePersonMessage(UpsertMessage $message, Person $person): Person
{
$membersEditor = $this->membersEditorFactory->createEditor();
$currentHousehold = $person->getCurrentHousehold();
// Check if address information is provided in the message
$hasAddressInfo = $message->hasAddressInfo();
if (null !== $currentHousehold && $hasAddressInfo) {
$currentAddresses = $currentHousehold->getAddresses()->toArray();
$lastCurrentAddress = end($currentAddresses);
$lastCurrentAddress = $currentHousehold->getCurrentAddress();
if (false !== $lastCurrentAddress) {
if (null !== $lastCurrentAddress) {
$messageAddressMatch = $this->isMessageAddressMatch($lastCurrentAddress, $message);
if (!$messageAddressMatch) {
$newAddress = $this->createAddressWithMessage($message);
@@ -167,6 +176,18 @@ readonly class PersonUpsertHandler
$person->setLastName($message->lastName);
}
// Handle birthDate
if (null !== $message->birthdate) {
try {
$person->setBirthdate(new \DateTime($message->birthdate));
} catch (\Exception $e) {
$this->logger->error(self::LOG_PREFIX.'Could not parse birthdate: '.$message->birthdate, [
'exception' => $e->getTraceAsString(),
'birthdate' => $message->birthdate,
]);
}
}
// Handle gender
$gender = $this->findGenderByValue($message->gender);
if (null === $gender) {
@@ -183,6 +204,42 @@ readonly class PersonUpsertHandler
$person->setCenter($center);
}
// mobileNumber and phoneNumber
if (null !== $message->phoneNumber) {
try {
$person->setPhonenumber($this->phoneNumberUtil->parse($message->phoneNumber));
} catch (NumberParseException $e) {
$this->logger->error(self::LOG_PREFIX.'Could not parse phoneNumber', [
'exception' => $e->getTraceAsString(),
'phoneNumber' => $message->phoneNumber,
]);
throw new UnrecoverableMessageHandlingException('Could not parse phoneNumber: '.$message->phoneNumber);
}
}
if (null !== $message->mobileNumber) {
try {
$person->setMobilenumber($this->phoneNumberUtil->parse($message->mobileNumber));
} catch (NumberParseException $e) {
$this->logger->error(self::LOG_PREFIX.'Could not parse mobileNumber', [
'exception' => $e->getTraceAsString(),
'mobileNumber' => $message->mobileNumber,
]);
throw new UnrecoverableMessageHandlingException('Could not parse mobileNumber: '.$message->mobileNumber);
}
}
$errors = $this->validator->validate($person);
if ($errors->count() > 0) {
$errorMessages = [];
foreach ($errors as $error) {
$errorMessages[] = $error->getMessage();
}
$this->logger->error(self::LOG_PREFIX.'Person created / updated not valid', ['errors' => implode(', ', $errorMessages)]);
throw new UnrecoverableMessageHandlingException('Person created / updated not valid: '.implode(', ', $errorMessages));
}
return $person;
}
@@ -191,14 +248,16 @@ readonly class PersonUpsertHandler
// 1. Retrieve definition
$definition = $this->personIdentifierDefinitionRepository->find($message->personIdentifierDefinitionId);
if (null === $definition) {
throw new \RuntimeException('PersonIdentifierDefinition not found for id '.$message->personIdentifierDefinitionId);
$this->logger->error(self::LOG_PREFIX.'Person message not found: '.$message->personIdentifierDefinitionId);
throw new UnrecoverableMessageHandlingException('PersonIdentifierDefinition not found for id '.$message->personIdentifierDefinitionId);
}
// 2. Search identifiers
$identifiers = $this->personIdentifierRepository->findByDefinitionAndCanonical($definition, $message->externalId);
if (count($identifiers) > 1) {
throw new \RuntimeException('More than one identifier found for definition.');
$this->logger->error(self::LOG_PREFIX.'Person message contains more than one identifier');
throw new UnrecoverableMessageHandlingException('More than one identifier found for definition.');
}
if (0 === count($identifiers)) {
@@ -210,7 +269,12 @@ readonly class PersonUpsertHandler
// Create and bound identifier
$identifier = new PersonIdentifier($definition);
$identifier->setPerson($person);
$identifier->setValue(['content' => $message->externalId]);
$identifier->setValue($value = ['content' => $message->externalId]);
$identifier->setCanonical(
$this->personIdentifierManager
->buildWorkerByPersonIdentifierDefinition($identifier->getDefinition())
->canonicalizeValue($value)
);
$this->entityManager->persist($identifier);
} else {
// 4. Update existing person
@@ -221,5 +285,6 @@ readonly class PersonUpsertHandler
}
$this->entityManager->flush();
$this->entityManager->clear();
}
}

View File

@@ -21,15 +21,20 @@ class UpsertMessage
public ?string $birthdate = null;
public ?string $mobileNumber = null;
public ?string $phoneNumber = null;
public ?string $address = null;
public ?string $extra = null;
public ?string $streetNumber = null;
public ?string $postcode = null;
public ?string $city = null;
public ?string $addressStreet = null;
/**
* The extra field of the address.
*/
public ?string $addressExtra = null;
public ?string $addressStreetNumber = null;
public ?string $addressPostcode = null;
public ?string $addressCity = null;
public ?string $center = null;
public function hasAddressInfo(): bool
{
return null !== $this->address || null !== $this->postcode || null !== $this->streetNumber;
return null !== $this->addressStreet || null !== $this->addressPostcode || null !== $this->addressStreetNumber;
}
}

View File

@@ -27,6 +27,10 @@ use Doctrine\ORM\EntityManagerInterface;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\Clock\ClockInterface;
use Psr\Log\LoggerInterface;
use Chill\PersonBundle\PersonIdentifier\PersonIdentifierManagerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Validator\ConstraintViolationListInterface;
/**
* @internal
@@ -40,11 +44,16 @@ class PersonUpsertHandlerTest extends TestCase
private function createMembersEditorFactoryMock(): MembersEditorFactory
{
$membersEditor = $this->prophesize(MembersEditor::class);
$membersEditor->validate()->willReturn($this->prophesize(\Doctrine\Common\Collections\Collection::class)->reveal());
$violations = $this->prophesize(ConstraintViolationListInterface::class);
$violations->count()->willReturn(0);
$membersEditor->validate()->willReturn($violations->reveal());
$membersEditor->addMovement(Argument::type(\DateTimeImmutable::class), Argument::type(Person::class), null, true)->willReturn($membersEditor->reveal());
$membersEditor->getPersistable()->willReturn([]);
$membersEditor->postMove();
$factory = $this->prophesize(MembersEditorFactory::class);
$factory->createEditor()->willReturn($membersEditor->reveal());
$factory->createEditor(Argument::any())->willReturn($membersEditor->reveal());
return $factory->reveal();
}
@@ -59,6 +68,10 @@ class PersonUpsertHandlerTest extends TestCase
$centerRepository = $this->prophesize(CenterRepositoryInterface::class);
$genderRepository = $this->prophesize(GenderRepository::class);
$clock = $this->prophesize(ClockInterface::class);
$phoneNumberUtil = $this->prophesize(\libphonenumber\PhoneNumberUtil::class);
$logger = $this->prophesize(LoggerInterface::class);
$personIdentifierManager = $this->prophesize(PersonIdentifierManagerInterface::class);
$validator = $this->prophesize(ValidatorInterface::class);
$definition = $this->prophesize(\Chill\PersonBundle\Entity\Identifier\PersonIdentifierDefinition::class);
$personIdentifierDefinitionRepository->find(1)->willReturn($definition->reveal());
@@ -72,9 +85,24 @@ class PersonUpsertHandlerTest extends TestCase
// Mock center repository - no center found
$centerRepository->findBy(['name' => null])->willReturn([]);
// Mock validator
$violations = $this->prophesize(ConstraintViolationListInterface::class);
$violations->count()->willReturn(0);
$validator->validate(Argument::any())->willReturn($violations->reveal());
// Mock person identifier manager - create real worker with mocked engine
$identifierEngine = $this->prophesize(\Chill\PersonBundle\PersonIdentifier\PersonIdentifierEngineInterface::class);
$identifierEngine->canonicalizeValue(Argument::any(), Argument::any())->willReturn('123');
$worker = new \Chill\PersonBundle\PersonIdentifier\PersonIdentifierWorker(
$identifierEngine->reveal(),
$definition->reveal()
);
$personIdentifierManager->buildWorkerByPersonIdentifierDefinition(Argument::any())->willReturn($worker);
$entityManager->persist(Argument::any())->shouldBeCalled();
$entityManager->persist(Argument::any())->shouldBeCalled();
$entityManager->flush()->shouldBeCalled();
$entityManager->clear()->shouldBeCalled();
$handler = new PersonUpsertHandler(
$personIdentifierDefinitionRepository->reveal(),
@@ -85,6 +113,10 @@ class PersonUpsertHandlerTest extends TestCase
$centerRepository->reveal(),
$genderRepository->reveal(),
$clock->reveal(),
$phoneNumberUtil->reveal(),
$logger->reveal(),
$personIdentifierManager->reveal(),
$validator->reveal(),
);
$message = new UpsertMessage();
@@ -106,6 +138,10 @@ class PersonUpsertHandlerTest extends TestCase
$centerRepository = $this->prophesize(CenterRepositoryInterface::class);
$genderRepository = $this->prophesize(GenderRepository::class);
$clock = $this->prophesize(ClockInterface::class);
$phoneNumberUtil = $this->prophesize(\libphonenumber\PhoneNumberUtil::class);
$logger = $this->prophesize(LoggerInterface::class);
$personIdentifierManager = $this->prophesize(PersonIdentifierManagerInterface::class);
$validator = $this->prophesize(ValidatorInterface::class);
$definition = $this->prophesize(\Chill\PersonBundle\Entity\Identifier\PersonIdentifierDefinition::class);
$personIdentifierDefinitionRepository->find(2)->willReturn($definition->reveal());
@@ -131,7 +167,13 @@ class PersonUpsertHandlerTest extends TestCase
// Mock center repository - no center found
$centerRepository->findBy(['name' => null])->willReturn([]);
// Mock validator
$violations = $this->prophesize(ConstraintViolationListInterface::class);
$violations->count()->willReturn(0);
$validator->validate(Argument::any())->willReturn($violations->reveal());
$entityManager->flush()->shouldBeCalled();
$entityManager->clear()->shouldBeCalled();
$handler = new PersonUpsertHandler(
$personIdentifierDefinitionRepository->reveal(),
@@ -142,6 +184,10 @@ class PersonUpsertHandlerTest extends TestCase
$centerRepository->reveal(),
$genderRepository->reveal(),
$clock->reveal(),
$phoneNumberUtil->reveal(),
$logger->reveal(),
$personIdentifierManager->reveal(),
$validator->reveal(),
);
$message = new UpsertMessage();
@@ -163,6 +209,10 @@ class PersonUpsertHandlerTest extends TestCase
$centerRepository = $this->prophesize(CenterRepositoryInterface::class);
$genderRepository = $this->prophesize(GenderRepository::class);
$clock = $this->prophesize(ClockInterface::class);
$phoneNumberUtil = $this->prophesize(\libphonenumber\PhoneNumberUtil::class);
$logger = $this->prophesize(LoggerInterface::class);
$personIdentifierManager = $this->prophesize(PersonIdentifierManagerInterface::class);
$validator = $this->prophesize(ValidatorInterface::class);
$definition = $this->prophesize(\Chill\PersonBundle\Entity\Identifier\PersonIdentifierDefinition::class);
$personIdentifierDefinitionRepository->find(1)->willReturn($definition->reveal());
@@ -176,9 +226,24 @@ class PersonUpsertHandlerTest extends TestCase
// Mock center repository
$centerRepository->findBy(['name' => null])->willReturn([]);
// Mock validator
$violations = $this->prophesize(ConstraintViolationListInterface::class);
$violations->count()->willReturn(0);
$validator->validate(Argument::any())->willReturn($violations->reveal());
// Mock person identifier manager - create real worker with mocked engine
$identifierEngine = $this->prophesize(\Chill\PersonBundle\PersonIdentifier\PersonIdentifierEngineInterface::class);
$identifierEngine->canonicalizeValue(Argument::any(), Argument::any())->willReturn('123');
$worker = new \Chill\PersonBundle\PersonIdentifier\PersonIdentifierWorker(
$identifierEngine->reveal(),
$definition->reveal()
);
$personIdentifierManager->buildWorkerByPersonIdentifierDefinition(Argument::any())->willReturn($worker);
$entityManager->persist(Argument::any())->shouldBeCalled();
$entityManager->persist(Argument::any())->shouldBeCalled();
$entityManager->flush()->shouldBeCalled();
$entityManager->clear()->shouldBeCalled();
$handler = new PersonUpsertHandler(
$personIdentifierDefinitionRepository->reveal(),
@@ -189,6 +254,10 @@ class PersonUpsertHandlerTest extends TestCase
$centerRepository->reveal(),
$genderRepository->reveal(),
$clock->reveal(),
$phoneNumberUtil->reveal(),
$logger->reveal(),
$personIdentifierManager->reveal(),
$validator->reveal(),
);
$message = new UpsertMessage();
@@ -211,6 +280,10 @@ class PersonUpsertHandlerTest extends TestCase
$centerRepository = $this->prophesize(CenterRepositoryInterface::class);
$genderRepository = $this->prophesize(GenderRepository::class);
$clock = $this->prophesize(ClockInterface::class);
$phoneNumberUtil = $this->prophesize(\libphonenumber\PhoneNumberUtil::class);
$logger = $this->prophesize(LoggerInterface::class);
$personIdentifierManager = $this->prophesize(PersonIdentifierManagerInterface::class);
$validator = $this->prophesize(ValidatorInterface::class);
$definition = $this->prophesize(\Chill\PersonBundle\Entity\Identifier\PersonIdentifierDefinition::class);
$personIdentifierDefinitionRepository->find(1)->willReturn($definition->reveal());
@@ -225,9 +298,24 @@ class PersonUpsertHandlerTest extends TestCase
$center = $this->prophesize(\Chill\MainBundle\Entity\Center::class);
$centerRepository->findBy(['name' => 'Main Center'])->willReturn([$center->reveal()]);
// Mock validator
$violations = $this->prophesize(ConstraintViolationListInterface::class);
$violations->count()->willReturn(0);
$validator->validate(Argument::any())->willReturn($violations->reveal());
// Mock person identifier manager - create real worker with mocked engine
$identifierEngine = $this->prophesize(\Chill\PersonBundle\PersonIdentifier\PersonIdentifierEngineInterface::class);
$identifierEngine->canonicalizeValue(Argument::any(), Argument::any())->willReturn('123');
$worker = new \Chill\PersonBundle\PersonIdentifier\PersonIdentifierWorker(
$identifierEngine->reveal(),
$definition->reveal()
);
$personIdentifierManager->buildWorkerByPersonIdentifierDefinition(Argument::any())->willReturn($worker);
$entityManager->persist(Argument::any())->shouldBeCalled();
$entityManager->persist(Argument::any())->shouldBeCalled();
$entityManager->flush()->shouldBeCalled();
$entityManager->clear()->shouldBeCalled();
$handler = new PersonUpsertHandler(
$personIdentifierDefinitionRepository->reveal(),
@@ -238,6 +326,10 @@ class PersonUpsertHandlerTest extends TestCase
$centerRepository->reveal(),
$genderRepository->reveal(),
$clock->reveal(),
$phoneNumberUtil->reveal(),
$logger->reveal(),
$personIdentifierManager->reveal(),
$validator->reveal(),
);
$message = new UpsertMessage();
@@ -260,6 +352,10 @@ class PersonUpsertHandlerTest extends TestCase
$centerRepository = $this->prophesize(CenterRepositoryInterface::class);
$genderRepository = $this->prophesize(GenderRepository::class);
$clock = $this->prophesize(ClockInterface::class);
$phoneNumberUtil = $this->prophesize(\libphonenumber\PhoneNumberUtil::class);
$logger = $this->prophesize(LoggerInterface::class);
$personIdentifierManager = $this->prophesize(PersonIdentifierManagerInterface::class);
$validator = $this->prophesize(ValidatorInterface::class);
$definition = $this->prophesize(\Chill\PersonBundle\Entity\Identifier\PersonIdentifierDefinition::class);
$personIdentifierDefinitionRepository->find(1)->willReturn($definition->reveal());
@@ -274,9 +370,24 @@ class PersonUpsertHandlerTest extends TestCase
$center = $this->prophesize(\Chill\MainBundle\Entity\Center::class);
$centerRepository->findBy(['name' => 'Secondary Center'])->willReturn([$center->reveal()]);
// Mock validator
$violations = $this->prophesize(ConstraintViolationListInterface::class);
$violations->count()->willReturn(0);
$validator->validate(Argument::any())->willReturn($violations->reveal());
// Mock person identifier manager - create real worker with mocked engine
$identifierEngine = $this->prophesize(\Chill\PersonBundle\PersonIdentifier\PersonIdentifierEngineInterface::class);
$identifierEngine->canonicalizeValue(Argument::any(), Argument::any())->willReturn('123');
$worker = new \Chill\PersonBundle\PersonIdentifier\PersonIdentifierWorker(
$identifierEngine->reveal(),
$definition->reveal()
);
$personIdentifierManager->buildWorkerByPersonIdentifierDefinition(Argument::any())->willReturn($worker);
$entityManager->persist(Argument::any())->shouldBeCalled();
$entityManager->persist(Argument::any())->shouldBeCalled();
$entityManager->flush()->shouldBeCalled();
$entityManager->clear()->shouldBeCalled();
$handler = new PersonUpsertHandler(
$personIdentifierDefinitionRepository->reveal(),
@@ -287,6 +398,10 @@ class PersonUpsertHandlerTest extends TestCase
$centerRepository->reveal(),
$genderRepository->reveal(),
$clock->reveal(),
$phoneNumberUtil->reveal(),
$logger->reveal(),
$personIdentifierManager->reveal(),
$validator->reveal(),
);
$message = new UpsertMessage();
@@ -299,4 +414,403 @@ class PersonUpsertHandlerTest extends TestCase
$handler->__invoke($message);
}
public function testInvokeWithBirthdate(): void
{
$personIdentifierDefinitionRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierDefinitionRepository::class);
$identifierRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierRepository::class);
$entityManager = $this->prophesize(EntityManagerInterface::class);
$membersEditorFactory = $this->createMembersEditorFactoryMock();
$postalCodeRepository = $this->prophesize(PostalCodeRepositoryInterface::class);
$centerRepository = $this->prophesize(CenterRepositoryInterface::class);
$genderRepository = $this->prophesize(GenderRepository::class);
$clock = $this->prophesize(ClockInterface::class);
$phoneNumberUtil = $this->prophesize(\libphonenumber\PhoneNumberUtil::class);
$logger = $this->prophesize(LoggerInterface::class);
$personIdentifierManager = $this->prophesize(PersonIdentifierManagerInterface::class);
$validator = $this->prophesize(ValidatorInterface::class);
$definition = $this->prophesize(\Chill\PersonBundle\Entity\Identifier\PersonIdentifierDefinition::class);
$personIdentifierDefinitionRepository->find(1)->willReturn($definition->reveal());
$identifierRepository->findByDefinitionAndCanonical($definition->reveal(), '123')->willReturn([]);
// Mock gender repository
$gender = $this->prophesize(Gender::class);
$genderRepository->findByGenderTranslation(GenderEnum::NEUTRAL)->willReturn([$gender->reveal()]);
$genderRepository->findByGenderTranslation(Argument::any())->willReturn([$gender->reveal()]);
// Mock center repository
$centerRepository->findBy(['name' => null])->willReturn([]);
// Mock validator
$violations = $this->prophesize(ConstraintViolationListInterface::class);
$violations->count()->willReturn(0);
$validator->validate(Argument::any())->willReturn($violations->reveal());
// Mock person identifier manager
$identifierEngine = $this->prophesize(\Chill\PersonBundle\PersonIdentifier\PersonIdentifierEngineInterface::class);
$identifierEngine->canonicalizeValue(Argument::any(), Argument::any())->willReturn('123');
$worker = new \Chill\PersonBundle\PersonIdentifier\PersonIdentifierWorker(
$identifierEngine->reveal(),
$definition->reveal()
);
$personIdentifierManager->buildWorkerByPersonIdentifierDefinition(Argument::any())->willReturn($worker);
$entityManager->persist(Argument::any())->shouldBeCalled();
$entityManager->persist(Argument::any())->shouldBeCalled();
$entityManager->flush()->shouldBeCalled();
$entityManager->clear()->shouldBeCalled();
$handler = new PersonUpsertHandler(
$personIdentifierDefinitionRepository->reveal(),
$identifierRepository->reveal(),
$entityManager->reveal(),
$membersEditorFactory,
$postalCodeRepository->reveal(),
$centerRepository->reveal(),
$genderRepository->reveal(),
$clock->reveal(),
$phoneNumberUtil->reveal(),
$logger->reveal(),
$personIdentifierManager->reveal(),
$validator->reveal(),
);
$message = new UpsertMessage();
$message->externalId = '123';
$message->personIdentifierDefinitionId = 1;
$message->firstName = 'John';
$message->lastName = 'Doe';
$message->birthdate = '1990-01-15';
$handler->__invoke($message);
}
public function testInvokeWithPhoneNumber(): void
{
$personIdentifierDefinitionRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierDefinitionRepository::class);
$identifierRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierRepository::class);
$entityManager = $this->prophesize(EntityManagerInterface::class);
$membersEditorFactory = $this->createMembersEditorFactoryMock();
$postalCodeRepository = $this->prophesize(PostalCodeRepositoryInterface::class);
$centerRepository = $this->prophesize(CenterRepositoryInterface::class);
$genderRepository = $this->prophesize(GenderRepository::class);
$clock = $this->prophesize(ClockInterface::class);
$phoneNumberUtil = $this->prophesize(\libphonenumber\PhoneNumberUtil::class);
$logger = $this->prophesize(LoggerInterface::class);
$personIdentifierManager = $this->prophesize(PersonIdentifierManagerInterface::class);
$validator = $this->prophesize(ValidatorInterface::class);
$definition = $this->prophesize(\Chill\PersonBundle\Entity\Identifier\PersonIdentifierDefinition::class);
$personIdentifierDefinitionRepository->find(1)->willReturn($definition->reveal());
$identifierRepository->findByDefinitionAndCanonical($definition->reveal(), '123')->willReturn([]);
// Mock gender repository
$gender = $this->prophesize(Gender::class);
$genderRepository->findByGenderTranslation(GenderEnum::NEUTRAL)->willReturn([$gender->reveal()]);
$genderRepository->findByGenderTranslation(Argument::any())->willReturn([$gender->reveal()]);
// Mock center repository
$centerRepository->findBy(['name' => null])->willReturn([]);
// Mock phone number parsing
$phoneNumber = $this->prophesize(\libphonenumber\PhoneNumber::class);
$phoneNumberUtil->parse('+32123456789')->willReturn($phoneNumber->reveal());
// Mock validator
$violations = $this->prophesize(ConstraintViolationListInterface::class);
$violations->count()->willReturn(0);
$validator->validate(Argument::any())->willReturn($violations->reveal());
// Mock person identifier manager
$identifierEngine = $this->prophesize(\Chill\PersonBundle\PersonIdentifier\PersonIdentifierEngineInterface::class);
$identifierEngine->canonicalizeValue(Argument::any(), Argument::any())->willReturn('123');
$worker = new \Chill\PersonBundle\PersonIdentifier\PersonIdentifierWorker(
$identifierEngine->reveal(),
$definition->reveal()
);
$personIdentifierManager->buildWorkerByPersonIdentifierDefinition(Argument::any())->willReturn($worker);
$entityManager->persist(Argument::any())->shouldBeCalled();
$entityManager->persist(Argument::any())->shouldBeCalled();
$entityManager->flush()->shouldBeCalled();
$entityManager->clear()->shouldBeCalled();
$handler = new PersonUpsertHandler(
$personIdentifierDefinitionRepository->reveal(),
$identifierRepository->reveal(),
$entityManager->reveal(),
$membersEditorFactory,
$postalCodeRepository->reveal(),
$centerRepository->reveal(),
$genderRepository->reveal(),
$clock->reveal(),
$phoneNumberUtil->reveal(),
$logger->reveal(),
$personIdentifierManager->reveal(),
$validator->reveal(),
);
$message = new UpsertMessage();
$message->externalId = '123';
$message->personIdentifierDefinitionId = 1;
$message->firstName = 'John';
$message->lastName = 'Doe';
$message->phoneNumber = '+32123456789';
$handler->__invoke($message);
}
public function testInvokeWithMobileNumber(): void
{
$personIdentifierDefinitionRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierDefinitionRepository::class);
$identifierRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierRepository::class);
$entityManager = $this->prophesize(EntityManagerInterface::class);
$membersEditorFactory = $this->createMembersEditorFactoryMock();
$postalCodeRepository = $this->prophesize(PostalCodeRepositoryInterface::class);
$centerRepository = $this->prophesize(CenterRepositoryInterface::class);
$genderRepository = $this->prophesize(GenderRepository::class);
$clock = $this->prophesize(ClockInterface::class);
$phoneNumberUtil = $this->prophesize(\libphonenumber\PhoneNumberUtil::class);
$logger = $this->prophesize(LoggerInterface::class);
$personIdentifierManager = $this->prophesize(PersonIdentifierManagerInterface::class);
$validator = $this->prophesize(ValidatorInterface::class);
$definition = $this->prophesize(\Chill\PersonBundle\Entity\Identifier\PersonIdentifierDefinition::class);
$personIdentifierDefinitionRepository->find(1)->willReturn($definition->reveal());
$identifierRepository->findByDefinitionAndCanonical($definition->reveal(), '123')->willReturn([]);
// Mock gender repository
$gender = $this->prophesize(Gender::class);
$genderRepository->findByGenderTranslation(GenderEnum::NEUTRAL)->willReturn([$gender->reveal()]);
$genderRepository->findByGenderTranslation(Argument::any())->willReturn([$gender->reveal()]);
// Mock center repository
$centerRepository->findBy(['name' => null])->willReturn([]);
// Mock mobile number parsing
$mobileNumber = $this->prophesize(\libphonenumber\PhoneNumber::class);
$phoneNumberUtil->parse('+32987654321')->willReturn($mobileNumber->reveal());
// Mock validator
$violations = $this->prophesize(ConstraintViolationListInterface::class);
$violations->count()->willReturn(0);
$validator->validate(Argument::any())->willReturn($violations->reveal());
// Mock person identifier manager
$identifierEngine = $this->prophesize(\Chill\PersonBundle\PersonIdentifier\PersonIdentifierEngineInterface::class);
$identifierEngine->canonicalizeValue(Argument::any(), Argument::any())->willReturn('123');
$worker = new \Chill\PersonBundle\PersonIdentifier\PersonIdentifierWorker(
$identifierEngine->reveal(),
$definition->reveal()
);
$personIdentifierManager->buildWorkerByPersonIdentifierDefinition(Argument::any())->willReturn($worker);
$entityManager->persist(Argument::any())->shouldBeCalled();
$entityManager->persist(Argument::any())->shouldBeCalled();
$entityManager->flush()->shouldBeCalled();
$entityManager->clear()->shouldBeCalled();
$handler = new PersonUpsertHandler(
$personIdentifierDefinitionRepository->reveal(),
$identifierRepository->reveal(),
$entityManager->reveal(),
$membersEditorFactory,
$postalCodeRepository->reveal(),
$centerRepository->reveal(),
$genderRepository->reveal(),
$clock->reveal(),
$phoneNumberUtil->reveal(),
$logger->reveal(),
$personIdentifierManager->reveal(),
$validator->reveal(),
);
$message = new UpsertMessage();
$message->externalId = '123';
$message->personIdentifierDefinitionId = 1;
$message->firstName = 'John';
$message->lastName = 'Doe';
$message->mobileNumber = '+32987654321';
$handler->__invoke($message);
}
public function testInvokeWithAddressFields(): void
{
$personIdentifierDefinitionRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierDefinitionRepository::class);
$identifierRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierRepository::class);
$entityManager = $this->prophesize(EntityManagerInterface::class);
$membersEditorFactory = $this->createMembersEditorFactoryMock();
$postalCodeRepository = $this->prophesize(PostalCodeRepositoryInterface::class);
$centerRepository = $this->prophesize(CenterRepositoryInterface::class);
$genderRepository = $this->prophesize(GenderRepository::class);
$clock = $this->prophesize(ClockInterface::class);
$phoneNumberUtil = $this->prophesize(\libphonenumber\PhoneNumberUtil::class);
$logger = $this->prophesize(LoggerInterface::class);
$personIdentifierManager = $this->prophesize(PersonIdentifierManagerInterface::class);
$validator = $this->prophesize(ValidatorInterface::class);
$definition = $this->prophesize(\Chill\PersonBundle\Entity\Identifier\PersonIdentifierDefinition::class);
$personIdentifierDefinitionRepository->find(1)->willReturn($definition->reveal());
$identifierRepository->findByDefinitionAndCanonical($definition->reveal(), '123')->willReturn([]);
// Mock gender repository
$gender = $this->prophesize(Gender::class);
$genderRepository->findByGenderTranslation(GenderEnum::NEUTRAL)->willReturn([$gender->reveal()]);
$genderRepository->findByGenderTranslation(Argument::any())->willReturn([$gender->reveal()]);
// Mock center repository
$centerRepository->findBy(['name' => null])->willReturn([]);
// Mock postal code repository
$postalCode = $this->prophesize(\Chill\MainBundle\Entity\PostalCode::class);
$postalCodeRepository->findOneBy(['code' => '1000'])->willReturn($postalCode->reveal());
// Mock clock for address valid from
$now = new \DateTimeImmutable('2026-03-09');
$clock->now()->willReturn($now);
// Mock validator
$violations = $this->prophesize(ConstraintViolationListInterface::class);
$violations->count()->willReturn(0);
$validator->validate(Argument::any())->willReturn($violations->reveal());
// Mock person identifier manager
$identifierEngine = $this->prophesize(\Chill\PersonBundle\PersonIdentifier\PersonIdentifierEngineInterface::class);
$identifierEngine->canonicalizeValue(Argument::any(), Argument::any())->willReturn('123');
$worker = new \Chill\PersonBundle\PersonIdentifier\PersonIdentifierWorker(
$identifierEngine->reveal(),
$definition->reveal()
);
$personIdentifierManager->buildWorkerByPersonIdentifierDefinition(Argument::any())->willReturn($worker);
$entityManager->persist(Argument::any())->shouldBeCalled();
$entityManager->persist(Argument::any())->shouldBeCalled();
$entityManager->flush()->shouldBeCalled();
$entityManager->clear()->shouldBeCalled();
$handler = new PersonUpsertHandler(
$personIdentifierDefinitionRepository->reveal(),
$identifierRepository->reveal(),
$entityManager->reveal(),
$membersEditorFactory,
$postalCodeRepository->reveal(),
$centerRepository->reveal(),
$genderRepository->reveal(),
$clock->reveal(),
$phoneNumberUtil->reveal(),
$logger->reveal(),
$personIdentifierManager->reveal(),
$validator->reveal(),
);
$message = new UpsertMessage();
$message->externalId = '123';
$message->personIdentifierDefinitionId = 1;
$message->firstName = 'John';
$message->lastName = 'Doe';
$message->addressStreet = 'Main Street';
$message->addressStreetNumber = '42';
$message->addressPostcode = '1000';
$message->addressExtra = 'Apartment 3B';
$message->addressCity = 'Brussels';
$handler->__invoke($message);
}
public function testInvokeWithAllFields(): void
{
$personIdentifierDefinitionRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierDefinitionRepository::class);
$identifierRepository = $this->prophesize(\Chill\PersonBundle\Repository\Identifier\PersonIdentifierRepository::class);
$entityManager = $this->prophesize(EntityManagerInterface::class);
$membersEditorFactory = $this->createMembersEditorFactoryMock();
$postalCodeRepository = $this->prophesize(PostalCodeRepositoryInterface::class);
$centerRepository = $this->prophesize(CenterRepositoryInterface::class);
$genderRepository = $this->prophesize(GenderRepository::class);
$clock = $this->prophesize(ClockInterface::class);
$phoneNumberUtil = $this->prophesize(\libphonenumber\PhoneNumberUtil::class);
$logger = $this->prophesize(LoggerInterface::class);
$personIdentifierManager = $this->prophesize(PersonIdentifierManagerInterface::class);
$validator = $this->prophesize(ValidatorInterface::class);
$definition = $this->prophesize(\Chill\PersonBundle\Entity\Identifier\PersonIdentifierDefinition::class);
$personIdentifierDefinitionRepository->find(1)->willReturn($definition->reveal());
$identifierRepository->findByDefinitionAndCanonical($definition->reveal(), '123')->willReturn([]);
// Mock gender repository
$femaleGender = $this->prophesize(Gender::class);
$genderRepository->findByGenderTranslation(GenderEnum::FEMALE)->willReturn([$femaleGender->reveal()]);
$genderRepository->findByGenderTranslation(Argument::any())->willReturn([$femaleGender->reveal()]);
// Mock center repository
$center = $this->prophesize(\Chill\MainBundle\Entity\Center::class);
$centerRepository->findBy(['name' => 'Main Center'])->willReturn([$center->reveal()]);
// Mock postal code repository
$postalCode = $this->prophesize(\Chill\MainBundle\Entity\PostalCode::class);
$postalCodeRepository->findOneBy(['code' => '1000'])->willReturn($postalCode->reveal());
// Mock clock for address valid from
$now = new \DateTimeImmutable('2026-03-09');
$clock->now()->willReturn($now);
// Mock phone number parsing
$phoneNumber = $this->prophesize(\libphonenumber\PhoneNumber::class);
$mobileNumber = $this->prophesize(\libphonenumber\PhoneNumber::class);
$phoneNumberUtil->parse('+32123456789')->willReturn($phoneNumber->reveal());
$phoneNumberUtil->parse('+32987654321')->willReturn($mobileNumber->reveal());
// Mock validator
$violations = $this->prophesize(ConstraintViolationListInterface::class);
$violations->count()->willReturn(0);
$validator->validate(Argument::any())->willReturn($violations->reveal());
// Mock person identifier manager
$identifierEngine = $this->prophesize(\Chill\PersonBundle\PersonIdentifier\PersonIdentifierEngineInterface::class);
$identifierEngine->canonicalizeValue(Argument::any(), Argument::any())->willReturn('123');
$worker = new \Chill\PersonBundle\PersonIdentifier\PersonIdentifierWorker(
$identifierEngine->reveal(),
$definition->reveal()
);
$personIdentifierManager->buildWorkerByPersonIdentifierDefinition(Argument::any())->willReturn($worker);
$entityManager->persist(Argument::any())->shouldBeCalled();
$entityManager->persist(Argument::any())->shouldBeCalled();
$entityManager->flush()->shouldBeCalled();
$entityManager->clear()->shouldBeCalled();
$handler = new PersonUpsertHandler(
$personIdentifierDefinitionRepository->reveal(),
$identifierRepository->reveal(),
$entityManager->reveal(),
$membersEditorFactory,
$postalCodeRepository->reveal(),
$centerRepository->reveal(),
$genderRepository->reveal(),
$clock->reveal(),
$phoneNumberUtil->reveal(),
$logger->reveal(),
$personIdentifierManager->reveal(),
$validator->reveal(),
);
$message = new UpsertMessage();
$message->externalId = '123';
$message->personIdentifierDefinitionId = 1;
$message->firstName = 'Jane';
$message->lastName = 'Smith';
$message->gender = 'female';
$message->birthdate = '1985-05-20';
$message->phoneNumber = '+32123456789';
$message->mobileNumber = '+32987654321';
$message->addressStreet = 'Main Street';
$message->addressStreetNumber = '42';
$message->addressPostcode = '1000';
$message->addressExtra = 'Apartment 3B';
$message->addressCity = 'Brussels';
$message->center = 'Main Center';
$handler->__invoke($message);
}
}