From e9e6c05e3d3bc09863ddc398c55036faf9ca7e60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 21 Oct 2025 13:22:04 +0200 Subject: [PATCH] Refactor `PersonEdit` flow to introduce `PersonEditDTO` - Replaced `Person` entity binding with `PersonEditDTO` in `PersonType` to decouple data transfer and entity manipulation. - Added `PersonEditDTOFactory` for creating and mapping `PersonEditDTO` instances. - Simplified `PersonAltNameDataMapper` and `PersonIdentifiersDataMapper`. - Updated `PersonEditController` to use `PersonEditDTO` for better separation of concerns. - Adjusted related tests, configurations, and templates accordingly. --- .../Actions/PersonEdit/PersonEditDTO.php | 108 +++++++++++++ .../Service/PersonEditDTOFactory.php | 130 ++++++++++++++++ .../Config/ConfigPersonAltNamesHelper.php | 2 + .../Controller/PersonEditController.php | 7 +- .../DataMapper/PersonAltNameDataMapper.php | 57 +------ .../PersonIdentifiersDataMapper.php | 64 +++----- .../Form/PersonIdentifiersType.php | 8 +- .../ChillPersonBundle/Form/PersonType.php | 4 +- .../Form/Type/PersonAltNameType.php | 1 + .../PersonIdentifierEngineInterface.php | 3 + .../Resources/views/Person/edit.html.twig | 4 +- .../Service/PersonEditDTOFactoryTest.php | 146 ++++++++++++++++++ .../config/services/actions.yaml | 3 + 13 files changed, 439 insertions(+), 98 deletions(-) create mode 100644 src/Bundle/ChillPersonBundle/Actions/PersonEdit/PersonEditDTO.php create mode 100644 src/Bundle/ChillPersonBundle/Actions/PersonEdit/Service/PersonEditDTOFactory.php create mode 100644 src/Bundle/ChillPersonBundle/Tests/Action/PersonEdit/Service/PersonEditDTOFactoryTest.php diff --git a/src/Bundle/ChillPersonBundle/Actions/PersonEdit/PersonEditDTO.php b/src/Bundle/ChillPersonBundle/Actions/PersonEdit/PersonEditDTO.php new file mode 100644 index 000000000..ed19e581d --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Actions/PersonEdit/PersonEditDTO.php @@ -0,0 +1,108 @@ + where the key is the altname's key + */ + public array $altNames = []; + + public string $memo = ''; + + public ?EmploymentStatus $employmentStatus = null; + + public ?AdministrativeStatus $administrativeStatus = null; + + public string $placeOfBirth = ''; + + public ?string $contactInfo = ''; + + #[MisdPhoneNumberConstraint(type: [MisdPhoneNumberConstraint::FIXED_LINE, MisdPhoneNumberConstraint::VOIP, MisdPhoneNumberConstraint::PERSONAL_NUMBER])] + public ?PhoneNumber $phonenumber = null; + + #[MisdPhoneNumberConstraint(type: [MisdPhoneNumberConstraint::MOBILE])] + public ?PhoneNumber $mobilenumber = null; + + public ?bool $acceptSms = false; + + #[Assert\Valid(traverse: true)] + public Collection $otherPhonenumbers; // Collection + + #[Assert\Email] + public ?string $email = ''; + + public ?bool $acceptEmail = false; + + public ?Country $countryOfBirth = null; + + public ?Country $nationality = null; + + public Collection $spokenLanguages; // Collection + + public ?Civility $civility = null; + + public ?MaritalStatus $maritalStatus = null; + + public ?\DateTimeInterface $maritalStatusDate = null; + + #[Assert\Valid] + public CommentEmbeddable $maritalStatusComment; + + public ?array $cFData = null; + + /** + * @var array + */ + #[Assert\Valid(traverse: true)] + public array $identifiers = []; +} diff --git a/src/Bundle/ChillPersonBundle/Actions/PersonEdit/Service/PersonEditDTOFactory.php b/src/Bundle/ChillPersonBundle/Actions/PersonEdit/Service/PersonEditDTOFactory.php new file mode 100644 index 000000000..e25cd0ad2 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Actions/PersonEdit/Service/PersonEditDTOFactory.php @@ -0,0 +1,130 @@ +firstName = $person->getFirstName(); + $dto->lastName = $person->getLastName(); + $dto->birthdate = $person->getBirthdate(); + $dto->deathdate = (null !== $deathDate = $person->getDeathdate()) ? \DateTimeImmutable::createFromInterface($deathDate) : null; + $dto->gender = $person->getGender(); + $dto->genderComment = $person->getGenderComment(); + $dto->numberOfChildren = $person->getNumberOfChildren(); + $dto->memo = $person->getMemo() ?? ''; + $dto->employmentStatus = $person->getEmploymentStatus(); + $dto->administrativeStatus = $person->getAdministrativeStatus(); + $dto->placeOfBirth = $person->getPlaceOfBirth() ?? ''; + $dto->contactInfo = $person->getcontactInfo(); + $dto->phonenumber = $person->getPhonenumber(); + $dto->mobilenumber = $person->getMobilenumber(); + $dto->acceptSms = $person->getAcceptSMS(); + $dto->otherPhonenumbers = $person->getOtherPhoneNumbers(); + $dto->email = $person->getEmail(); + $dto->acceptEmail = $person->getAcceptEmail(); + $dto->countryOfBirth = $person->getCountryOfBirth(); + $dto->nationality = $person->getNationality(); + $dto->spokenLanguages = $person->getSpokenLanguages(); + $dto->civility = $person->getCivility(); + $dto->maritalStatus = $person->getMaritalStatus(); + $dto->maritalStatusDate = $person->getMaritalStatusDate(); + $dto->maritalStatusComment = $person->getMaritalStatusComment(); + $dto->cFData = $person->getCFData(); + + + foreach ($this->configPersonAltNamesHelper->getChoices() as $key => $labels) { + $altName = $person->getAltNames()->findFirst(fn (int $k, PersonAltName $altName) => $altName->getKey() === $key); + if (null === $altName) { + $altName = new PersonAltName(); + $altName->setKey($key); + } + $dto->altNames[$key] = $altName; + } + + foreach ($this->personIdentifierManager->getWorkers() as $worker) { + $identifier = $person + ->getIdentifiers() + ->findFirst(fn (int $key, PersonIdentifier $identifier) => $worker->getDefinition() === $identifier->getDefinition()); + if (null === $identifier) { + $identifier = new PersonIdentifier($worker->getDefinition()); + $person->addIdentifier($identifier); + } + $dto->identifiers['identifier_'.$worker->getDefinition()->getId()] = $identifier; + } + + return $dto; + } + + public function mapPersonEditDTOtoPerson(PersonEditDTO $personEditDTO, Person $person): void + { + // Copy all editable fields from the DTO back to the Person entity + $person + ->setFirstName($personEditDTO->firstName) + ->setLastName($personEditDTO->lastName) + ->setBirthdate($personEditDTO->birthdate) + ->setDeathdate($personEditDTO->deathdate) + ->setGender($personEditDTO->gender) + ->setGenderComment($personEditDTO->genderComment) + ->setNumberOfChildren($personEditDTO->numberOfChildren) + ->setMemo($personEditDTO->memo) + ->setEmploymentStatus($personEditDTO->employmentStatus) + ->setAdministrativeStatus($personEditDTO->administrativeStatus) + ->setPlaceOfBirth($personEditDTO->placeOfBirth) + ->setcontactInfo($personEditDTO->contactInfo) + ->setPhonenumber($personEditDTO->phonenumber) + ->setMobilenumber($personEditDTO->mobilenumber) + ->setAcceptSMS($personEditDTO->acceptSms ?? false) + ->setOtherPhoneNumbers($personEditDTO->otherPhonenumbers) + ->setEmail($personEditDTO->email) + ->setAcceptEmail($personEditDTO->acceptEmail ?? false) + ->setCountryOfBirth($personEditDTO->countryOfBirth) + ->setNationality($personEditDTO->nationality) + ->setSpokenLanguages($personEditDTO->spokenLanguages) + ->setCivility($personEditDTO->civility) + ->setMaritalStatus($personEditDTO->maritalStatus) + ->setMaritalStatusDate($personEditDTO->maritalStatusDate) + ->setMaritalStatusComment($personEditDTO->maritalStatusComment) + ->setCFData($personEditDTO->cFData); + + foreach ($personEditDTO->altNames as $altName) { + if ('' === $altName->getLabel()) { + $person->removeAltName($altName); + } else { + $person->addAltName($altName); + } + } + + foreach ($personEditDTO->identifiers as $identifier) { + $worker = $this->personIdentifierManager->buildWorkerByPersonIdentifierDefinition($identifier->getDefinition()); + if ($worker->isEmpty($identifier)) { + $person->removeIdentifier($identifier); + } else { + $person->addIdentifier($identifier); + } + } + } +} diff --git a/src/Bundle/ChillPersonBundle/Config/ConfigPersonAltNamesHelper.php b/src/Bundle/ChillPersonBundle/Config/ConfigPersonAltNamesHelper.php index 4be480442..bac53aa23 100644 --- a/src/Bundle/ChillPersonBundle/Config/ConfigPersonAltNamesHelper.php +++ b/src/Bundle/ChillPersonBundle/Config/ConfigPersonAltNamesHelper.php @@ -28,6 +28,8 @@ class ConfigPersonAltNamesHelper /** * get the choices as key => values. + * + * @return array> where the key is the altName's key, and the value is an array of TranslatableString */ public function getChoices(): array { diff --git a/src/Bundle/ChillPersonBundle/Controller/PersonEditController.php b/src/Bundle/ChillPersonBundle/Controller/PersonEditController.php index be5b87bc3..e09bb97b6 100644 --- a/src/Bundle/ChillPersonBundle/Controller/PersonEditController.php +++ b/src/Bundle/ChillPersonBundle/Controller/PersonEditController.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Controller; use Chill\CustomFieldsBundle\EntityRepository\CustomFieldsDefaultGroupRepository; +use Chill\PersonBundle\Actions\PersonEdit\Service\PersonEditDTOFactory; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Form\PersonType; use Chill\PersonBundle\Security\Authorization\PersonVoter; @@ -38,6 +39,7 @@ final readonly class PersonEditController private EntityManagerInterface $entityManager, private UrlGeneratorInterface $urlGenerator, private Environment $twig, + private PersonEditDTOFactory $personEditDTOFactory, ) {} /** @@ -50,9 +52,11 @@ final readonly class PersonEditController throw new AccessDeniedHttpException('You are not allowed to edit this person.'); } + $dto = $this->personEditDTOFactory->createPersonEditDTO($person); + $form = $this->formFactory->create( PersonType::class, - $person, + $dto, ['cFGroup' => $this->customFieldsDefaultGroupRepository->findOneByEntity(Person::class)?->getCustomFieldsGroup()] ); @@ -62,6 +66,7 @@ final readonly class PersonEditController $session ->getFlashBag()->add('error', new TranslatableMessage('This form contains errors')); } elseif ($form->isSubmitted() && $form->isValid()) { + $this->personEditDTOFactory->mapPersonEditDTOtoPerson($dto, $person); $this->entityManager->flush(); $session->getFlashBag()->add('success', new TranslatableMessage('The person data has been updated')); diff --git a/src/Bundle/ChillPersonBundle/Form/DataMapper/PersonAltNameDataMapper.php b/src/Bundle/ChillPersonBundle/Form/DataMapper/PersonAltNameDataMapper.php index 3a2a61cad..00986bddd 100644 --- a/src/Bundle/ChillPersonBundle/Form/DataMapper/PersonAltNameDataMapper.php +++ b/src/Bundle/ChillPersonBundle/Form/DataMapper/PersonAltNameDataMapper.php @@ -12,10 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Form\DataMapper; use Chill\PersonBundle\Entity\PersonAltName; -use Doctrine\Common\Collections\ArrayCollection; -use Doctrine\Common\Collections\Collection; use Symfony\Component\Form\DataMapperInterface; -use Symfony\Component\Form\Exception\UnexpectedTypeException; class PersonAltNameDataMapper implements DataMapperInterface { @@ -25,62 +22,24 @@ class PersonAltNameDataMapper implements DataMapperInterface return; } - if (!$viewData instanceof Collection) { - throw new UnexpectedTypeException($viewData, Collection::class); - } - - $mapIndexToKey = []; - - foreach ($viewData->getIterator() as $key => $altName) { - /* @var PersonAltName $altName */ - $mapIndexToKey[$altName->getKey()] = $key; + if (!is_array($viewData)) { + throw new \InvalidArgumentException('View data must be an array'); } foreach ($forms as $key => $form) { - if (\array_key_exists($key, $mapIndexToKey)) { - $form->setData($viewData->get($mapIndexToKey[$key])->getLabel()); + $personAltName = $viewData[$key]; + if (!$personAltName instanceof PersonAltName) { + throw new \InvalidArgumentException('PersonAltName must be an instance of PersonAltName'); } + $form->setData($personAltName->getLabel()); } } public function mapFormsToData(\Traversable $forms, &$viewData): void { - $mapIndexToKey = []; - - if (\is_array($viewData)) { - $dataIterator = $viewData; - } else { - $dataIterator = $viewData instanceof ArrayCollection ? - $viewData->toArray() : $viewData->getIterator(); - } - - foreach ($dataIterator as $key => $altName) { - /* @var PersonAltName $altName */ - $mapIndexToKey[$altName->getKey()] = $key; - } - foreach ($forms as $key => $form) { - $isEmpty = empty($form->getData()); - - if (\array_key_exists($key, $mapIndexToKey)) { - if ($isEmpty) { - $viewData->remove($mapIndexToKey[$key]); - } else { - $viewData->get($mapIndexToKey[$key])->setLabel($form->getData()); - } - } else { - if (!$isEmpty) { - $altName = (new PersonAltName()) - ->setKey($key) - ->setLabel($form->getData()); - - if (\is_array($viewData)) { - $viewData[] = $altName; - } else { - $viewData->add($altName); - } - } - } + $personAltName = array_find($viewData, fn (PersonAltName $altName) => $altName->getKey() === $key); + $personAltName->setLabel($form->getData()); } } } diff --git a/src/Bundle/ChillPersonBundle/Form/DataMapper/PersonIdentifiersDataMapper.php b/src/Bundle/ChillPersonBundle/Form/DataMapper/PersonIdentifiersDataMapper.php index c89b1485f..8516f18ab 100644 --- a/src/Bundle/ChillPersonBundle/Form/DataMapper/PersonIdentifiersDataMapper.php +++ b/src/Bundle/ChillPersonBundle/Form/DataMapper/PersonIdentifiersDataMapper.php @@ -14,65 +14,51 @@ namespace Chill\PersonBundle\Form\DataMapper; use Chill\PersonBundle\Entity\Identifier\PersonIdentifier; use Chill\PersonBundle\PersonIdentifier\Exception\UnexpectedTypeException; use Chill\PersonBundle\PersonIdentifier\PersonIdentifierManagerInterface; -use Chill\PersonBundle\Repository\Identifier\PersonIdentifierDefinitionRepository; -use Doctrine\Common\Collections\Collection; use Symfony\Component\Form\DataMapperInterface; -use Symfony\Component\Form\FormInterface; final readonly class PersonIdentifiersDataMapper implements DataMapperInterface { public function __construct( private PersonIdentifierManagerInterface $identifierManager, - private PersonIdentifierDefinitionRepository $identifierDefinitionRepository, ) {} + /** + * @pure + */ public function mapDataToForms($viewData, \Traversable $forms): void { - if (!$viewData instanceof Collection) { - throw new UnexpectedTypeException($viewData, Collection::class); + if (!$viewData instanceof PersonIdentifier) { + throw new UnexpectedTypeException($viewData, PersonIdentifier::class); } - /** @var array $formsByKey */ - $formsByKey = iterator_to_array($forms); - foreach ($this->identifierManager->getWorkers() as $worker) { - if (!$worker->getDefinition()->isEditableByUsers()) { - continue; - } - - $form = $formsByKey['identifier_'.$worker->getDefinition()->getId()] ?? null; - if (null === $form) { - continue; - } - - $identifier = $viewData->findFirst(fn (int $key, PersonIdentifier $identifier) => $worker->getDefinition() === $identifier->getDefinition()); - if (null === $identifier) { - $identifier = new PersonIdentifier($worker->getDefinition()); - } - $form->setData($identifier->getValue()); + $worker = $this->identifierManager->buildWorkerByPersonIdentifierDefinition($viewData->getDefinition()); + if (!$worker->getDefinition()->isEditableByUsers()) { + return; + } + foreach ($forms as $key => $form) { + $form->setData($viewData->getValue()[$key]); } } + /** + * @pure + */ public function mapFormsToData(\Traversable $forms, &$viewData): void { - if (!$viewData instanceof Collection) { - throw new UnexpectedTypeException($viewData, Collection::class); + if (!$viewData instanceof PersonIdentifier) { + throw new UnexpectedTypeException($viewData, PersonIdentifier::class); + } + $worker = $this->identifierManager->buildWorkerByPersonIdentifierDefinition($viewData->getDefinition()); + if (!$worker->getDefinition()->isEditableByUsers()) { + return; } + $values = []; foreach ($forms as $name => $form) { - $identifierId = (int) substr((string) $name, 11); - $identifier = $viewData->findFirst(fn (int $key, PersonIdentifier $identifier) => $identifier->getDefinition()->getId() === $identifierId); - $definition = $this->identifierDefinitionRepository->find($identifierId); - if (null === $identifier) { - $identifier = new PersonIdentifier($definition); - $viewData->add($identifier); - } - if (!$identifier->getDefinition()->isEditableByUsers()) { - continue; - } - - $worker = $this->identifierManager->buildWorkerByPersonIdentifierDefinition($definition); - $identifier->setValue($form->getData()); - $identifier->setCanonical($worker->canonicalizeValue($identifier->getValue())); + $values[$name] = $form->getData(); } + + $viewData->setValue($values); + $viewData->setCanonical($worker->canonicalizeValue($viewData->getValue())); } } diff --git a/src/Bundle/ChillPersonBundle/Form/PersonIdentifiersType.php b/src/Bundle/ChillPersonBundle/Form/PersonIdentifiersType.php index 0ab057280..199b89bc8 100644 --- a/src/Bundle/ChillPersonBundle/Form/PersonIdentifiersType.php +++ b/src/Bundle/ChillPersonBundle/Form/PersonIdentifiersType.php @@ -29,7 +29,7 @@ final class PersonIdentifiersType extends AbstractType public function buildForm(FormBuilderInterface $builder, array $options): void { - foreach ($this->identifierManager->getWorkers() as $worker) { + foreach ($this->identifierManager->getWorkers() as $k => $worker) { if (!$worker->getDefinition()->isEditableByUsers()) { continue; } @@ -45,16 +45,16 @@ final class PersonIdentifiersType extends AbstractType options: [ 'compound' => true, 'label' => $this->translatableStringHelper->localize($worker->getDefinition()->getLabel()), + 'error_bubbling' => false, ] ); + $subBuilder->setDataMapper($this->identifiersDataMapper); $worker->buildForm($subBuilder); $builder->add($subBuilder); } - - $builder->setDataMapper($this->identifiersDataMapper); } - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefault('step', 'on_edit') ->setAllowedValues('step', ['on_edit', 'on_create']); diff --git a/src/Bundle/ChillPersonBundle/Form/PersonType.php b/src/Bundle/ChillPersonBundle/Form/PersonType.php index 09ab04b01..60b75f325 100644 --- a/src/Bundle/ChillPersonBundle/Form/PersonType.php +++ b/src/Bundle/ChillPersonBundle/Form/PersonType.php @@ -21,8 +21,8 @@ use Chill\MainBundle\Form\Type\PickCivilityType; use Chill\MainBundle\Form\Type\Select2CountryType; use Chill\MainBundle\Form\Type\Select2LanguageType; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; +use Chill\PersonBundle\Actions\PersonEdit\PersonEditDTO; use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper; -use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\PersonPhone; use Chill\PersonBundle\Form\Type\PersonAltNameType; use Chill\PersonBundle\Form\Type\PersonPhoneType; @@ -242,7 +242,7 @@ class PersonType extends AbstractType public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ - 'data_class' => Person::class, + 'data_class' => PersonEditDTO::class, ]); $resolver->setRequired([ diff --git a/src/Bundle/ChillPersonBundle/Form/Type/PersonAltNameType.php b/src/Bundle/ChillPersonBundle/Form/Type/PersonAltNameType.php index 6d983be37..5ee8830e0 100644 --- a/src/Bundle/ChillPersonBundle/Form/Type/PersonAltNameType.php +++ b/src/Bundle/ChillPersonBundle/Form/Type/PersonAltNameType.php @@ -32,6 +32,7 @@ class PersonAltNameType extends AbstractType [ 'label' => $label, 'required' => false, + 'empty_data' => '', ] ); } diff --git a/src/Bundle/ChillPersonBundle/PersonIdentifier/PersonIdentifierEngineInterface.php b/src/Bundle/ChillPersonBundle/PersonIdentifier/PersonIdentifierEngineInterface.php index 90b50df3c..84de2968e 100644 --- a/src/Bundle/ChillPersonBundle/PersonIdentifier/PersonIdentifierEngineInterface.php +++ b/src/Bundle/ChillPersonBundle/PersonIdentifier/PersonIdentifierEngineInterface.php @@ -19,6 +19,9 @@ interface PersonIdentifierEngineInterface { public static function getName(): string; + /** + * @phpstan-pure + */ public function canonicalizeValue(array $value, PersonIdentifierDefinition $definition): ?string; public function buildForm(FormBuilderInterface $builder, PersonIdentifierDefinition $personIdentifierDefinition): void; diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Person/edit.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Person/edit.html.twig index fe5dde242..169479854 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Person/edit.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Person/edit.html.twig @@ -140,9 +140,7 @@

{{ 'person.Identifiers'|trans }}

- {% for f in form.identifiers %} - {{ form_row(f) }} - {% endfor %} + {{ form_widget(form.identifiers) }}
{% else %} diff --git a/src/Bundle/ChillPersonBundle/Tests/Action/PersonEdit/Service/PersonEditDTOFactoryTest.php b/src/Bundle/ChillPersonBundle/Tests/Action/PersonEdit/Service/PersonEditDTOFactoryTest.php new file mode 100644 index 000000000..28241b14a --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Action/PersonEdit/Service/PersonEditDTOFactoryTest.php @@ -0,0 +1,146 @@ +createMock(ConfigPersonAltNamesHelper::class); + $identifierManager = $this->createMock(PersonIdentifierManagerInterface::class); + $factory = new PersonEditDTOFactory($configHelper, $identifierManager); + + $dto = new PersonEditDTO(); + $dto->firstName = 'John'; + $dto->lastName = 'Doe'; + $dto->birthdate = new \DateTime('1980-05-10'); + $dto->deathdate = new \DateTimeImmutable('2050-01-01'); + $dto->gender = new Gender(); + $dto->genderComment = new CommentEmbeddable('gender comment'); + $dto->numberOfChildren = 2; + $dto->memo = 'Some memo'; + $dto->employmentStatus = new EmploymentStatus(); + $dto->administrativeStatus = new AdministrativeStatus(); + $dto->placeOfBirth = 'Cityville'; + $dto->contactInfo = 'Some contact info'; + $phone = new PhoneNumber(); + $dto->phonenumber = $phone; + $mobile = new PhoneNumber(); + $dto->mobilenumber = $mobile; + $dto->acceptSms = true; + $dto->otherPhonenumbers = new ArrayCollection(); + $dto->email = 'john.doe@example.org'; + $dto->acceptEmail = true; + $dto->countryOfBirth = new Country(); + $dto->nationality = new Country(); + $dto->spokenLanguages = new ArrayCollection([new Language()]); + $dto->civility = new Civility(); + $dto->maritalStatus = new MaritalStatus(); + $dto->maritalStatusDate = new \DateTime('2010-01-01'); + $dto->maritalStatusComment = new CommentEmbeddable('married'); + $dto->cFData = ['foo' => 'bar']; + + $person = new Person(); + + $factory->mapPersonEditDTOtoPerson($dto, $person); + + self::assertSame('John', $person->getFirstName()); + self::assertSame('Doe', $person->getLastName()); + self::assertSame($dto->birthdate, $person->getBirthdate()); + self::assertSame($dto->deathdate, $person->getDeathdate()); + self::assertSame($dto->gender, $person->getGender()); + self::assertSame($dto->genderComment, $person->getGenderComment()); + self::assertSame($dto->numberOfChildren, $person->getNumberOfChildren()); + self::assertSame('Some memo', $person->getMemo()); + self::assertSame($dto->employmentStatus, $person->getEmploymentStatus()); + self::assertSame($dto->administrativeStatus, $person->getAdministrativeStatus()); + self::assertSame('Cityville', $person->getPlaceOfBirth()); + self::assertSame('Some contact info', $person->getcontactInfo()); + self::assertSame($phone, $person->getPhonenumber()); + self::assertSame($mobile, $person->getMobilenumber()); + self::assertTrue($person->getAcceptSMS()); + self::assertSame($dto->otherPhonenumbers, $person->getOtherPhoneNumbers()); + self::assertSame('john.doe@example.org', $person->getEmail()); + self::assertTrue($person->getAcceptEmail()); + self::assertSame($dto->countryOfBirth, $person->getCountryOfBirth()); + self::assertSame($dto->nationality, $person->getNationality()); + self::assertSame($dto->spokenLanguages, $person->getSpokenLanguages()); + self::assertSame($dto->civility, $person->getCivility()); + self::assertSame($dto->maritalStatus, $person->getMaritalStatus()); + self::assertSame($dto->maritalStatusDate, $person->getMaritalStatusDate()); + self::assertSame($dto->maritalStatusComment, $person->getMaritalStatusComment()); + self::assertSame($dto->cFData, $person->getCFData()); + } + + public function testAltNamesHandlingWithConfigHelper(): void + { + $configHelper = $this->createMock(ConfigPersonAltNamesHelper::class); + $configHelper->method('getChoices')->willReturn([ + 'aka' => ['en' => 'Also Known As'], + 'nickname' => ['en' => 'Nickname'], + ]); + + $identifierManager = $this->createMock(PersonIdentifierManagerInterface::class); + $identifierManager->method('getWorkers')->willReturn([]); + + $factory = new PersonEditDTOFactory($configHelper, $identifierManager); + $person = new Person(); + + $dto = $factory->createPersonEditDTO($person); + + // Assert DTO has two altNames keys from helper + self::assertCount(2, $dto->altNames); + self::assertContainsOnlyInstancesOf(PersonAltName::class, $dto->altNames); + self::assertSame(['aka', 'nickname'], array_keys($dto->altNames)); + self::assertSame(['aka' => 'aka', 'nickname' => 'nickname'], array_map(fn (PersonAltName $altName) => $altName->getKey(), $dto->altNames)); + + // Fill only one label and leave the other empty + $dto->altNames['aka']->setLabel('The Boss'); + // 'nickname' remains empty by default + + // Map DTO back to person + $factory->mapPersonEditDTOtoPerson($dto, $person); + + // Assert only the filled alt name is persisted on the person + $altNames = $person->getAltNames(); + self::assertCount(1, $altNames); + $altNameArray = $altNames->toArray(); + self::assertSame('aka', $altNameArray[0]->getKey()); + self::assertSame('The Boss', $altNameArray[0]->getLabel()); + } +} diff --git a/src/Bundle/ChillPersonBundle/config/services/actions.yaml b/src/Bundle/ChillPersonBundle/config/services/actions.yaml index d6e2c80a5..4ed808605 100644 --- a/src/Bundle/ChillPersonBundle/config/services/actions.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/actions.yaml @@ -11,3 +11,6 @@ services: Chill\PersonBundle\Actions\Remove\Handler\: resource: '../../Actions/Remove/Handler' + + Chill\PersonBundle\Actions\PersonEdit\Service\: + resource: '../../Actions/PersonEdit/Service'