From f17ed7a4f8cdf0c5b627ccb57328adfbc85d59a0 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 1 Oct 2025 14:55:24 +0200 Subject: [PATCH] Implement global circular reference handler in line with latest symfony practices and remove now redundant code from current normalizers --- config/packages/serializer.yaml | 4 +++ config/services.yaml | 5 +++ .../Serializer/CircularReferenceHandler.php | 17 ++++++++++ ...mpanyingPeriodWorkEvaluationNormalizer.php | 34 +++++++++---------- .../AccompanyingPeriodWorkNormalizer.php | 31 ++++++++--------- .../Normalizer/SocialIssueNormalizer.php | 32 ++++++----------- 6 files changed, 68 insertions(+), 55 deletions(-) create mode 100644 config/packages/serializer.yaml create mode 100644 src/Bundle/ChillMainBundle/Serializer/CircularReferenceHandler.php diff --git a/config/packages/serializer.yaml b/config/packages/serializer.yaml new file mode 100644 index 000000000..e0968fb23 --- /dev/null +++ b/config/packages/serializer.yaml @@ -0,0 +1,4 @@ +framework: + serializer: + enabled: true + enable_attributes: true diff --git a/config/services.yaml b/config/services.yaml index 89a12b595..da12550f4 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -18,3 +18,8 @@ services: Chill\MainBundle\ArgumentResolver\EntityValueResolver: tags: - { name: controller.argument_value_resolver, priority: 50 } + + Chill\MainBundle\Serializer\CircularReferenceHandler: + public: false + tags: + - { name: 'serializer.circular_reference_handler' } diff --git a/src/Bundle/ChillMainBundle/Serializer/CircularReferenceHandler.php b/src/Bundle/ChillMainBundle/Serializer/CircularReferenceHandler.php new file mode 100644 index 000000000..a1ce8e80d --- /dev/null +++ b/src/Bundle/ChillMainBundle/Serializer/CircularReferenceHandler.php @@ -0,0 +1,17 @@ + $object->getId(), + '_class' => (new \ReflectionClass($object))->getShortName(), + ]; + } + + return spl_object_id($object); + } +} diff --git a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodWorkEvaluationNormalizer.php b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodWorkEvaluationNormalizer.php index 24b31e86e..a87a99486 100644 --- a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodWorkEvaluationNormalizer.php +++ b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodWorkEvaluationNormalizer.php @@ -14,42 +14,43 @@ namespace Chill\PersonBundle\Serializer\Normalizer; use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository; use Chill\MainBundle\Workflow\Helper\MetadataExtractor; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation; -use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; -use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Workflow\Registry; -class AccompanyingPeriodWorkEvaluationNormalizer implements \Symfony\Component\Serializer\Normalizer\NormalizerInterface, NormalizerAwareInterface +class AccompanyingPeriodWorkEvaluationNormalizer implements NormalizerInterface, NormalizerAwareInterface { use NormalizerAwareTrait; - private const IGNORE_EVALUATION = 'evaluation:ignore'; - - public function __construct(private readonly Registry $registry, private readonly EntityWorkflowRepository $entityWorkflowRepository, private readonly MetadataExtractor $metadataExtractor) {} + public function __construct( + private readonly Registry $registry, + private readonly EntityWorkflowRepository $entityWorkflowRepository, + private readonly MetadataExtractor $metadataExtractor, + ) {} /** * @param AccompanyingPeriodWorkEvaluation $object */ public function normalize($object, ?string $format = null, array $context = []): array { - $initial = $this->normalizer->normalize($object, $format, array_merge( - $context, - )); + $initial = $this->normalizer->normalize($object, $format, $context); - // due to bug: https://api-platform.com/docs/core/serialization/#collection-relation - // and also: https://github.com/symfony/symfony/issues/36965 - // we have to rewrite the documents as a collection $initial['documents'] = $this->normalizer->normalize( $object->getDocuments()->getValues(), $format, $context ); - // then, we add normalization for things which are not into the entity + $initial['accompanyingPeriodWork'] = $this->normalizer->normalize( + $object->getAccompanyingPeriodWork(), + $format, + $context + ); + // Add additional workflows $initial['workflows_availables'] = $this->metadataExtractor->availableWorkflowFor( - AccompanyingPeriodWorkEvaluation::class, + AccompanyingPeriodWorkEvaluation::class ); $workflows = $this->entityWorkflowRepository->findBy([ @@ -63,12 +64,11 @@ class AccompanyingPeriodWorkEvaluationNormalizer implements \Symfony\Component\S public function supportsNormalization($data, ?string $format = null, array $context = []): bool { return 'json' === $format - && $data instanceof AccompanyingPeriodWorkEvaluation - && !\array_key_exists(self::IGNORE_EVALUATION, $context); + && ($data instanceof AccompanyingPeriodWorkEvaluation || ($data instanceof \Doctrine\Persistence\Proxy && $data->__isInitialized())); } public function getSupportedTypes(?string $format): array { - return 'json' === $format ? [AccompanyingPeriodWorkEvaluation::class => true] : []; + return 'json' === $format ? [AccompanyingPeriodWorkEvaluation::class => null] : []; } } diff --git a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodWorkNormalizer.php b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodWorkNormalizer.php index df95b7972..5b5cccbee 100644 --- a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodWorkNormalizer.php +++ b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodWorkNormalizer.php @@ -21,7 +21,6 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluatio use Chill\PersonBundle\Entity\SocialWork\SocialAction; use Chill\ThirdPartyBundle\Entity\ThirdParty; use Symfony\Component\Serializer\Exception\UnexpectedValueException; -use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; use Symfony\Component\Workflow\Registry; @@ -32,7 +31,11 @@ class AccompanyingPeriodWorkNormalizer implements \Symfony\Component\Serializer\ private const IGNORE_WORK = 'ignore:work'; - public function __construct(private readonly Registry $registry, private readonly EntityWorkflowRepository $entityWorkflowRepository, private readonly MetadataExtractor $metadataExtractor) {} + public function __construct( + private readonly Registry $registry, + private readonly EntityWorkflowRepository $entityWorkflowRepository, + private readonly MetadataExtractor $metadataExtractor, + ) {} public function normalize($object, ?string $format = null, array $context = []): array|\ArrayObject|bool|float|int|string|null { @@ -42,7 +45,9 @@ class AccompanyingPeriodWorkNormalizer implements \Symfony\Component\Serializer\ if ('docgen' === $format && !($object instanceof AccompanyingPeriodWork || null === $object)) { throw new UnexpectedValueException(sprintf('Object must be an instanceof AccompanyingPeriodWork or null when format is docgen, %s given', get_debug_type($object))); } - $cleanContext = array_filter($context, fn (string|int $key) => !in_array($key, ['docgen:expects', self::IGNORE_WORK], true), ARRAY_FILTER_USE_KEY); + + // Only remove docgen:expects, keep IGNORE_WORK in context + $cleanContext = array_filter($context, fn (string|int $key) => 'docgen:expects' !== $key, ARRAY_FILTER_USE_KEY); if (null === $object && 'docgen' === $format) { $dateNull = $this->normalizer->normalize(null, $format, [...$context, 'docgen:expects' => \DateTimeImmutable::class]); @@ -72,10 +77,7 @@ class AccompanyingPeriodWorkNormalizer implements \Symfony\Component\Serializer\ ]; } - $initial = $this->normalizer->normalize($object, $format, array_merge( - $cleanContext, - [self::IGNORE_WORK => null === $object ? null : spl_object_hash($object)] - )); + $initial = $this->normalizer->normalize($object, $format, $cleanContext); // due to bug: https://api-platform.com/docs/core/serialization/#collection-relation // and also: https://github.com/symfony/symfony/issues/36965 @@ -83,7 +85,7 @@ class AccompanyingPeriodWorkNormalizer implements \Symfony\Component\Serializer\ $initial['accompanyingPeriodWorkEvaluations'] = $this->normalizer->normalize( $object->getAccompanyingPeriodWorkEvaluations()->getValues(), $format, - [...$cleanContext] + $cleanContext ); // add the referrers @@ -119,7 +121,7 @@ class AccompanyingPeriodWorkNormalizer implements \Symfony\Component\Serializer\ 'relatedEntityClass' => AccompanyingPeriodWork::class, ]); - $initial['workflows'] = $this->normalizer->normalize($workflows, 'json', $context); + $initial['workflows'] = $this->normalizer->normalize($workflows, 'json', $contextWithIgnore); } return $initial; @@ -128,18 +130,15 @@ class AccompanyingPeriodWorkNormalizer implements \Symfony\Component\Serializer\ public function supportsNormalization($data, ?string $format = null, array $context = []): bool { return match ($format) { - 'json' => $data instanceof AccompanyingPeriodWork && ($context[self::IGNORE_WORK] ?? null) !== spl_object_hash($data), - 'docgen' => ($data instanceof AccompanyingPeriodWork || (null === $data && ($context['docgen:expects'] ?? null) === AccompanyingPeriodWork::class)) - && !array_key_exists(self::IGNORE_WORK, $context), + 'json' => $data instanceof AccompanyingPeriodWork, + 'docgen' => ($data instanceof AccompanyingPeriodWork + || (null === $data && ($context['docgen:expects'] ?? null) === AccompanyingPeriodWork::class)), default => false, }; } public function getSupportedTypes(?string $format): array { - return match ($format) { - 'json', 'docgen' => [AccompanyingPeriodWork::class => true], - default => [], - }; + return 'json' === $format ? [AccompanyingPeriodWork::class => null] : []; } } diff --git a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialIssueNormalizer.php b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialIssueNormalizer.php index b60b94e95..d4a80667a 100644 --- a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialIssueNormalizer.php +++ b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialIssueNormalizer.php @@ -56,31 +56,19 @@ class SocialIssueNormalizer implements \Symfony\Component\Serializer\Normalizer\ public function supportsNormalization($data, $format = null, array $context = []): bool { - if ($data instanceof SocialIssue && 'json' === $format) { - return true; - } - - if ('docgen' === $format) { - if ($data instanceof SocialIssue) { - return true; - } - - if (null === $data && SocialIssue::class === ($context['docgen:expects'] ?? null)) { - return true; - } - } - - return false; + return match ($format) { + 'json' => $data instanceof SocialIssue, + 'docgen' => $data instanceof SocialIssue || + (null === $data && ($context['docgen:expects'] ?? null) === SocialIssue::class), + default => false, + }; } public function getSupportedTypes(?string $format): array { - if ('json' === $format || 'docgen' === $format) { - return [ - SocialIssue::class => true, - ]; - } - - return []; + return match ($format) { + 'json', 'docgen' => [SocialIssue::class => true], + default => [], + }; } }