mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-25 16:14:59 +00:00
Introduce PersonJsonReadDenormalizer
and PersonJsonDenormalizer
to separate responsibilities for handling person denormalization. Add corresponding test classes for improved coverage. Refactor PersonJsonNormalizer
to remove denormalization logic.
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\PersonBundle\Serializer\Normalizer;
|
||||
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Entity\Civility;
|
||||
use Chill\MainBundle\Entity\Gender;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Entity\PersonAltName;
|
||||
use libphonenumber\PhoneNumber;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\ObjectToPopulateTrait;
|
||||
|
||||
/**
|
||||
* Denormalize a Person entity from a JSON-like array structure, creating or updating an existing instance.
|
||||
*
|
||||
* To find an existing instance by his id, see the @see{PersonJsonReadDenormalizer}.
|
||||
*/
|
||||
class PersonJsonDenormalizer implements DenormalizerInterface, DenormalizerAwareInterface
|
||||
{
|
||||
use DenormalizerAwareTrait;
|
||||
use ObjectToPopulateTrait;
|
||||
|
||||
public function denormalize($data, string $type, ?string $format = null, array $context = []): Person
|
||||
{
|
||||
$person = $this->extractObjectToPopulate($type, $context);
|
||||
|
||||
if (null === $person) {
|
||||
$person = new Person();
|
||||
}
|
||||
|
||||
// Setters applied directly per known field for readability
|
||||
if (\array_key_exists('firstName', $data)) {
|
||||
$person->setFirstName($data['firstName']);
|
||||
}
|
||||
|
||||
if (\array_key_exists('lastName', $data)) {
|
||||
$person->setLastName($data['lastName']);
|
||||
}
|
||||
|
||||
if (\array_key_exists('phonenumber', $data)) {
|
||||
$person->setPhonenumber($this->denormalizer->denormalize($data['phonenumber'], PhoneNumber::class, $format, $context));
|
||||
}
|
||||
|
||||
if (\array_key_exists('mobilenumber', $data)) {
|
||||
$person->setMobilenumber($this->denormalizer->denormalize($data['mobilenumber'], PhoneNumber::class, $format, $context));
|
||||
}
|
||||
|
||||
if (\array_key_exists('gender', $data)) {
|
||||
$gender = $this->denormalizer->denormalize($data['gender'], Gender::class, $format, []);
|
||||
$person->setGender($gender);
|
||||
}
|
||||
|
||||
if (\array_key_exists('birthdate', $data)) {
|
||||
$object = $this->denormalizer->denormalize($data['birthdate'], \DateTime::class, $format, $context);
|
||||
$person->setBirthdate($object);
|
||||
}
|
||||
|
||||
if (\array_key_exists('deathdate', $data)) {
|
||||
$object = $this->denormalizer->denormalize($data['deathdate'], \DateTimeImmutable::class, $format, $context);
|
||||
$person->setDeathdate($object);
|
||||
}
|
||||
|
||||
if (\array_key_exists('center', $data)) {
|
||||
$object = $this->denormalizer->denormalize($data['center'], Center::class, $format, $context);
|
||||
$person->setCenter($object);
|
||||
}
|
||||
|
||||
if (\array_key_exists('altNames', $data)) {
|
||||
foreach ($data['altNames'] as $altName) {
|
||||
$oldAltName = $person
|
||||
->getAltNames()
|
||||
->filter(static fn (PersonAltName $n): bool => $n->getKey() === $altName['key'])->first();
|
||||
|
||||
if (false === $oldAltName) {
|
||||
$newAltName = new PersonAltName();
|
||||
$newAltName->setKey($altName['key']);
|
||||
$newAltName->setLabel($altName['label']);
|
||||
$person->addAltName($newAltName);
|
||||
} else {
|
||||
$oldAltName->setLabel($altName['label']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (\array_key_exists('email', $data)) {
|
||||
$person->setEmail($data['email']);
|
||||
}
|
||||
|
||||
if (\array_key_exists('civility', $data)) {
|
||||
$civility = $this->denormalizer->denormalize($data['civility'], Civility::class, $format, []);
|
||||
$person->setCivility($civility);
|
||||
}
|
||||
|
||||
return $person;
|
||||
}
|
||||
|
||||
public function supportsDenormalization($data, $type, $format = null): bool
|
||||
{
|
||||
return Person::class === $type && 'person' === ($data['type'] ?? null) && !isset($data['id']);
|
||||
}
|
||||
}
|
@@ -11,169 +11,31 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\PersonBundle\Serializer\Normalizer;
|
||||
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Entity\Civility;
|
||||
use Chill\MainBundle\Entity\Gender;
|
||||
use Chill\MainBundle\Phonenumber\PhoneNumberHelperInterface;
|
||||
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
|
||||
use Chill\MainBundle\Templating\Entity\ChillEntityRenderExtension;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Entity\PersonAltName;
|
||||
use Chill\PersonBundle\Repository\PersonRepository;
|
||||
use Chill\PersonBundle\Repository\ResidentialAddressRepository;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use libphonenumber\PhoneNumber;
|
||||
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
|
||||
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
|
||||
use Symfony\Component\Serializer\Normalizer\ObjectToPopulateTrait;
|
||||
|
||||
/**
|
||||
* Serialize a Person entity.
|
||||
*/
|
||||
class PersonJsonNormalizer implements DenormalizerAwareInterface, NormalizerAwareInterface, PersonJsonNormalizerInterface
|
||||
class PersonJsonNormalizer implements NormalizerAwareInterface
|
||||
{
|
||||
use DenormalizerAwareTrait;
|
||||
|
||||
use NormalizerAwareTrait;
|
||||
|
||||
use ObjectToPopulateTrait;
|
||||
|
||||
public function __construct(
|
||||
private readonly ChillEntityRenderExtension $render,
|
||||
/* TODO: replace by PersonRenderInterface, as sthis is the only one required */
|
||||
private readonly PersonRepository $repository,
|
||||
private readonly CenterResolverManagerInterface $centerResolverManager,
|
||||
private readonly ResidentialAddressRepository $residentialAddressRepository,
|
||||
private readonly PhoneNumberHelperInterface $phoneNumberHelper,
|
||||
) {}
|
||||
|
||||
public function denormalize($data, $type, $format = null, array $context = [])
|
||||
{
|
||||
$person = $this->extractObjectToPopulate($type, $context);
|
||||
|
||||
if (\array_key_exists('id', $data) && null === $person) {
|
||||
$person = $this->repository->find($data['id']);
|
||||
|
||||
if (null === $person) {
|
||||
throw new UnexpectedValueException("The person with id \"{$data['id']}\" does ".'not exists');
|
||||
}
|
||||
|
||||
// currently, not allowed to update a person through api
|
||||
// if instantiated with id
|
||||
return $person;
|
||||
}
|
||||
|
||||
if (null === $person) {
|
||||
$person = new Person();
|
||||
}
|
||||
|
||||
$fields = [
|
||||
'firstName',
|
||||
'lastName',
|
||||
'phonenumber',
|
||||
'mobilenumber',
|
||||
'gender',
|
||||
'birthdate',
|
||||
'deathdate',
|
||||
'center',
|
||||
'altNames',
|
||||
'email',
|
||||
'civility',
|
||||
];
|
||||
|
||||
$fields = array_filter(
|
||||
$fields,
|
||||
static fn (string $field): bool => \array_key_exists($field, $data)
|
||||
);
|
||||
|
||||
foreach ($fields as $item) {
|
||||
switch ($item) {
|
||||
case 'firstName':
|
||||
$person->setFirstName($data[$item]);
|
||||
|
||||
break;
|
||||
|
||||
case 'lastName':
|
||||
$person->setLastName($data[$item]);
|
||||
|
||||
break;
|
||||
|
||||
case 'phonenumber':
|
||||
$person->setPhonenumber($this->denormalizer->denormalize($data[$item], PhoneNumber::class, $format, $context));
|
||||
|
||||
break;
|
||||
|
||||
case 'mobilenumber':
|
||||
$person->setMobilenumber($this->denormalizer->denormalize($data[$item], PhoneNumber::class, $format, $context));
|
||||
|
||||
break;
|
||||
|
||||
case 'gender':
|
||||
$gender = $this->denormalizer->denormalize($data[$item], Gender::class, $format, []);
|
||||
|
||||
$person->setGender($gender);
|
||||
|
||||
break;
|
||||
|
||||
case 'birthdate':
|
||||
$object = $this->denormalizer->denormalize($data[$item], \DateTime::class, $format, $context);
|
||||
|
||||
$person->setBirthdate($object);
|
||||
|
||||
break;
|
||||
|
||||
case 'deathdate':
|
||||
$object = $this->denormalizer->denormalize($data[$item], \DateTimeImmutable::class, $format, $context);
|
||||
|
||||
$person->setDeathdate($object);
|
||||
|
||||
break;
|
||||
|
||||
case 'center':
|
||||
$object = $this->denormalizer->denormalize($data[$item], Center::class, $format, $context);
|
||||
$person->setCenter($object);
|
||||
|
||||
break;
|
||||
|
||||
case 'altNames':
|
||||
foreach ($data[$item] as $altName) {
|
||||
$oldAltName = $person
|
||||
->getAltNames()
|
||||
->filter(static fn (PersonAltName $n): bool => $n->getKey() === $altName['key'])->first();
|
||||
|
||||
if (false === $oldAltName) {
|
||||
$newAltName = new PersonAltName();
|
||||
$newAltName->setKey($altName['key']);
|
||||
$newAltName->setLabel($altName['label']);
|
||||
$person->addAltName($newAltName);
|
||||
} else {
|
||||
$oldAltName->setLabel($altName['label']);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'email':
|
||||
$person->setEmail($data[$item]);
|
||||
|
||||
break;
|
||||
|
||||
case 'civility':
|
||||
$civility = $this->denormalizer->denormalize($data[$item], Civility::class, $format, []);
|
||||
|
||||
$person->setCivility($civility);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $person;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Person $person
|
||||
* @param string|null $format
|
||||
|
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\PersonBundle\Serializer\Normalizer;
|
||||
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Repository\PersonRepository;
|
||||
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Serializer\Exception\LogicException;
|
||||
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
||||
|
||||
/**
|
||||
* Find a Person entity by his id during the denormalization process.
|
||||
*/
|
||||
readonly class PersonJsonReadDenormalizer implements DenormalizerInterface
|
||||
{
|
||||
public function __construct(private PersonRepository $repository) {}
|
||||
|
||||
public function denormalize($data, string $type, ?string $format = null, array $context = []): Person
|
||||
{
|
||||
if (!is_array($data)) {
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
|
||||
if (\array_key_exists('id', $data)) {
|
||||
$person = $this->repository->find($data['id']);
|
||||
|
||||
if (null === $person) {
|
||||
throw new UnexpectedValueException("The person with id \"{$data['id']}\" does ".'not exists');
|
||||
}
|
||||
|
||||
return $person;
|
||||
}
|
||||
|
||||
throw new LogicException();
|
||||
}
|
||||
|
||||
public function supportsDenormalization($data, string $type, ?string $format = null)
|
||||
{
|
||||
return is_array($data) && Person::class === $type && 'person' === ($data['type'] ?? null) && isset($data['id']);
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\PersonBundle\Tests\Serializer\Normalizer;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class PersonJsonDenormalizerTest extends TestCase {}
|
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\PersonBundle\Tests\Serializer\Normalizer;
|
||||
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Repository\PersonRepository;
|
||||
use Chill\PersonBundle\Serializer\Normalizer\PersonJsonReadDenormalizer;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @covers \Chill\PersonBundle\Serializer\Normalizer\PersonJsonReadDenormalizer
|
||||
*/
|
||||
final class PersonJsonReadDenormalizerTest extends TestCase
|
||||
{
|
||||
public function testSupportsDenormalizationReturnsTrueForValidData(): void
|
||||
{
|
||||
$repository = $this->getMockBuilder(PersonRepository::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$denormalizer = new PersonJsonReadDenormalizer($repository);
|
||||
|
||||
$data = [
|
||||
'type' => 'person',
|
||||
'id' => 123,
|
||||
];
|
||||
|
||||
self::assertTrue($denormalizer->supportsDenormalization($data, Person::class));
|
||||
}
|
||||
|
||||
public function testSupportsDenormalizationReturnsFalseForInvalidData(): void
|
||||
{
|
||||
$repository = $this->getMockBuilder(PersonRepository::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$denormalizer = new PersonJsonReadDenormalizer($repository);
|
||||
|
||||
// not an array
|
||||
self::assertFalse($denormalizer->supportsDenormalization('not-an-array', Person::class));
|
||||
|
||||
// missing type
|
||||
self::assertFalse($denormalizer->supportsDenormalization(['id' => 1], Person::class));
|
||||
|
||||
// wrong type value
|
||||
self::assertFalse($denormalizer->supportsDenormalization(['type' => 'not-person', 'id' => 1], Person::class));
|
||||
|
||||
// missing id
|
||||
self::assertFalse($denormalizer->supportsDenormalization(['type' => 'person'], Person::class));
|
||||
|
||||
// wrong target class
|
||||
self::assertFalse($denormalizer->supportsDenormalization(['type' => 'person', 'id' => 1], \stdClass::class));
|
||||
}
|
||||
|
||||
public function testDenormalizeReturnsPersonFromRepository(): void
|
||||
{
|
||||
$person = new Person();
|
||||
|
||||
$repository = $this->getMockBuilder(PersonRepository::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['find'])
|
||||
->getMock();
|
||||
|
||||
$repository->expects(self::once())
|
||||
->method('find')
|
||||
->with(123)
|
||||
->willReturn($person);
|
||||
|
||||
$denormalizer = new PersonJsonReadDenormalizer($repository);
|
||||
|
||||
$result = $denormalizer->denormalize(['id' => 123], Person::class);
|
||||
|
||||
self::assertSame($person, $result);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user