From 8a684734e740d02975b46b98f82a285a7d6ab8df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 16 May 2023 23:24:33 +0200 Subject: [PATCH] Fixed: fix docgen normalization on household with "old" members When a household had old members, the indexes of each "current" members should be numerical and contiguous, to be transformed in a list. If this is not the case, the members are mapped to an associative array. This commit alter the generic DocGenObjectNormalizer to ensure that the ReadableCollection are normalized using the CollectionDocGenNormalizer as default, which do not preserve keys. --- .../Normalizer/CollectionDocGenNormalizer.php | 7 ++- .../Normalizer/DocGenObjectNormalizer.php | 9 ++++ .../CollectionDocGenNormalizerTest.php | 52 +++++++++++++++++++ .../Normalizer/HouseholdNormalizerTest.php | 52 ++++++++++++++++++- 4 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 src/Bundle/ChillDocGeneratorBundle/tests/Serializer/Normalizer/CollectionDocGenNormalizerTest.php diff --git a/src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/CollectionDocGenNormalizer.php b/src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/CollectionDocGenNormalizer.php index 196018a03..b4e99bb98 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/CollectionDocGenNormalizer.php +++ b/src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/CollectionDocGenNormalizer.php @@ -13,6 +13,7 @@ namespace Chill\DocGeneratorBundle\Serializer\Normalizer; use ArrayObject; use Doctrine\Common\Collections\Collection; +use Doctrine\Common\Collections\ReadableCollection; use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; @@ -51,7 +52,9 @@ class CollectionDocGenNormalizer implements ContextAwareNormalizerInterface, Nor return false; } - return $data instanceof Collection - || (null === $data && Collection::class === ($context['docgen:expects'] ?? null)); + return $data instanceof ReadableCollection + || (null === $data && Collection::class === ($context['docgen:expects'] ?? null)) + || (null === $data && ReadableCollection::class === ($context['docgen:expects'] ?? null)) + ; } } diff --git a/src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/DocGenObjectNormalizer.php b/src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/DocGenObjectNormalizer.php index 3807ee9ee..bbf12b0fd 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/DocGenObjectNormalizer.php +++ b/src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/DocGenObjectNormalizer.php @@ -13,6 +13,7 @@ namespace Chill\DocGeneratorBundle\Serializer\Normalizer; use Chill\DocGeneratorBundle\Serializer\Helper\NormalizeNullValueHelper; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; +use Doctrine\Common\Collections\ReadableCollection; use ReflectionClass; use RuntimeException; use Symfony\Component\PropertyAccess\PropertyAccess; @@ -271,6 +272,14 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte if ($isTranslatable) { $data[$key] = $this->translatableStringHelper ->localize($value); + } elseif ($value instanceof ReadableCollection) { + // when normalizing collection, we should not preserve keys (to ensure that the result is a list) + // this is why we make call to the normalizer again to use the CollectionDocGenNormalizer + $data[$key] = + $this->normalizer->normalize($value, $format, array_merge( + $objectContext, + $attribute->getNormalizationContextForGroups($expectedGroups) + )); } elseif (is_iterable($value)) { $arr = []; diff --git a/src/Bundle/ChillDocGeneratorBundle/tests/Serializer/Normalizer/CollectionDocGenNormalizerTest.php b/src/Bundle/ChillDocGeneratorBundle/tests/Serializer/Normalizer/CollectionDocGenNormalizerTest.php new file mode 100644 index 000000000..7bfddadca --- /dev/null +++ b/src/Bundle/ChillDocGeneratorBundle/tests/Serializer/Normalizer/CollectionDocGenNormalizerTest.php @@ -0,0 +1,52 @@ +normalizer = self::$container->get(NormalizerInterface::class); + } + + public function testNormalizeFilteredArray(): void + { + $coll = new ArrayCollection([ + (object) ['v' => 'foo'], + (object) ['v' => 'bar'], + (object) ['v' => 'baz'], + ]); + + //filter to get non continuous indexes + $criteria = new Criteria(); + $criteria->where(Criteria::expr()->neq('v', 'bar')); + + $filtered = $coll->matching($criteria); + $normalized = $this->normalizer->normalize($filtered, 'docgen', []); + + self::assertIsArray($normalized); + self::assertArrayHasKey(0, $normalized); + self::assertArrayHasKey(1, $normalized); + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/HouseholdNormalizerTest.php b/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/HouseholdNormalizerTest.php index acd4119a1..093b882ce 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/HouseholdNormalizerTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/HouseholdNormalizerTest.php @@ -18,6 +18,7 @@ use Chill\PersonBundle\Entity\Person; use DateTimeImmutable; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; /** @@ -39,7 +40,7 @@ final class HouseholdNormalizerTest extends KernelTestCase $this->entityManager = self::$container->get(EntityManagerInterface::class); } - public function testNormalizationRecursive() + public function testNormalizationRecursive(): void { $person = new Person(); $person->setFirstName('ok')->setLastName('ok'); @@ -67,4 +68,53 @@ final class HouseholdNormalizerTest extends KernelTestCase $this->assertArrayHasKey('type', $normalized); $this->assertEquals('household', $normalized['type']); } + + /** + * When a household have old members (members which are not "current"), + * the indexes of the household must be reset to numerical and contiguous + * indexes. This ensure that it will be mapped as a list, not as an associative + * array. + */ + public function testHouseholdDocGenNormalizationWithOldMembers(): void + { + $previousPerson = new Person(); + $previousPerson->setFirstName('ok')->setLastName('ok'); + $this->entityManager->persist($previousPerson); + $member = new HouseholdMember(); + $household = new Household(); + $position = (new Position()) + ->setShareHousehold(true) + ->setAllowHolder(true); + + $member->setPerson($previousPerson) + ->setStartDate(new DateTimeImmutable('1 year ago')) + ->setEndDate(new DateTimeImmutable('1 month ago')) + ->setPosition($position); + + $household->addMember($member); + + $currentPerson1 = new Person(); + $currentPerson1->setFirstName('p1')->setLastName('p1'); + $this->entityManager->persist($currentPerson1); + $member = new HouseholdMember(); + $member->setPerson($currentPerson1) + ->setStartDate(new DateTimeImmutable('1 year ago')) + ->setPosition($position); + $household->addMember($member); + + $normalized = $this->normalizer->normalize( + $household, + 'docgen', + [ + AbstractNormalizer::GROUPS => ['docgen:read'], + 'docgen:expects' => Household::class, + 'docgen:person:with-household' => false, + 'docgen:person:with-relations' => false, + 'docgen:person:with-budget' => false, + ] + ); + + self::assertIsArray($normalized); + self::assertArrayHasKey(0, $normalized['currentMembers']); + } }