Implement global circular reference handler in line with latest symfony practices and remove now redundant code from current normalizers

This commit is contained in:
2025-10-01 14:55:24 +02:00
parent d4379cef6a
commit f17ed7a4f8
6 changed files with 68 additions and 55 deletions

View File

@@ -0,0 +1,4 @@
framework:
serializer:
enabled: true
enable_attributes: true

View File

@@ -18,3 +18,8 @@ services:
Chill\MainBundle\ArgumentResolver\EntityValueResolver: Chill\MainBundle\ArgumentResolver\EntityValueResolver:
tags: tags:
- { name: controller.argument_value_resolver, priority: 50 } - { name: controller.argument_value_resolver, priority: 50 }
Chill\MainBundle\Serializer\CircularReferenceHandler:
public: false
tags:
- { name: 'serializer.circular_reference_handler' }

View File

@@ -0,0 +1,17 @@
<?php
namespace Chill\MainBundle\Serializer;
class CircularReferenceHandler
{
public function handle(object $object, ?string $format = null, array $context = []): mixed
{
if (method_exists($object, 'getId')) {
return [
'id' => $object->getId(),
'_class' => (new \ReflectionClass($object))->getShortName(),
];
}
return spl_object_id($object);
}
}

View File

@@ -14,42 +14,43 @@ namespace Chill\PersonBundle\Serializer\Normalizer;
use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository; use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository;
use Chill\MainBundle\Workflow\Helper\MetadataExtractor; use Chill\MainBundle\Workflow\Helper\MetadataExtractor;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation; 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\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Workflow\Registry; use Symfony\Component\Workflow\Registry;
class AccompanyingPeriodWorkEvaluationNormalizer implements \Symfony\Component\Serializer\Normalizer\NormalizerInterface, NormalizerAwareInterface class AccompanyingPeriodWorkEvaluationNormalizer implements NormalizerInterface, NormalizerAwareInterface
{ {
use NormalizerAwareTrait; use NormalizerAwareTrait;
private const IGNORE_EVALUATION = 'evaluation:ignore'; public function __construct(
private readonly Registry $registry,
public function __construct(private readonly Registry $registry, private readonly EntityWorkflowRepository $entityWorkflowRepository, private readonly MetadataExtractor $metadataExtractor) {} private readonly EntityWorkflowRepository $entityWorkflowRepository,
private readonly MetadataExtractor $metadataExtractor,
) {}
/** /**
* @param AccompanyingPeriodWorkEvaluation $object * @param AccompanyingPeriodWorkEvaluation $object
*/ */
public function normalize($object, ?string $format = null, array $context = []): array public function normalize($object, ?string $format = null, array $context = []): array
{ {
$initial = $this->normalizer->normalize($object, $format, array_merge( $initial = $this->normalizer->normalize($object, $format, $context);
$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( $initial['documents'] = $this->normalizer->normalize(
$object->getDocuments()->getValues(), $object->getDocuments()->getValues(),
$format, $format,
$context $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( $initial['workflows_availables'] = $this->metadataExtractor->availableWorkflowFor(
AccompanyingPeriodWorkEvaluation::class, AccompanyingPeriodWorkEvaluation::class
); );
$workflows = $this->entityWorkflowRepository->findBy([ $workflows = $this->entityWorkflowRepository->findBy([
@@ -63,12 +64,11 @@ class AccompanyingPeriodWorkEvaluationNormalizer implements \Symfony\Component\S
public function supportsNormalization($data, ?string $format = null, array $context = []): bool public function supportsNormalization($data, ?string $format = null, array $context = []): bool
{ {
return 'json' === $format return 'json' === $format
&& $data instanceof AccompanyingPeriodWorkEvaluation && ($data instanceof AccompanyingPeriodWorkEvaluation || ($data instanceof \Doctrine\Persistence\Proxy && $data->__isInitialized()));
&& !\array_key_exists(self::IGNORE_EVALUATION, $context);
} }
public function getSupportedTypes(?string $format): array public function getSupportedTypes(?string $format): array
{ {
return 'json' === $format ? [AccompanyingPeriodWorkEvaluation::class => true] : []; return 'json' === $format ? [AccompanyingPeriodWorkEvaluation::class => null] : [];
} }
} }

View File

@@ -21,7 +21,6 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluatio
use Chill\PersonBundle\Entity\SocialWork\SocialAction; use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\ThirdPartyBundle\Entity\ThirdParty; use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Symfony\Component\Serializer\Exception\UnexpectedValueException; use Symfony\Component\Serializer\Exception\UnexpectedValueException;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
use Symfony\Component\Workflow\Registry; use Symfony\Component\Workflow\Registry;
@@ -32,7 +31,11 @@ class AccompanyingPeriodWorkNormalizer implements \Symfony\Component\Serializer\
private const IGNORE_WORK = 'ignore:work'; 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 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)) { 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))); 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) { if (null === $object && 'docgen' === $format) {
$dateNull = $this->normalizer->normalize(null, $format, [...$context, 'docgen:expects' => \DateTimeImmutable::class]); $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( $initial = $this->normalizer->normalize($object, $format, $cleanContext);
$cleanContext,
[self::IGNORE_WORK => null === $object ? null : spl_object_hash($object)]
));
// due to bug: https://api-platform.com/docs/core/serialization/#collection-relation // due to bug: https://api-platform.com/docs/core/serialization/#collection-relation
// and also: https://github.com/symfony/symfony/issues/36965 // and also: https://github.com/symfony/symfony/issues/36965
@@ -83,7 +85,7 @@ class AccompanyingPeriodWorkNormalizer implements \Symfony\Component\Serializer\
$initial['accompanyingPeriodWorkEvaluations'] = $this->normalizer->normalize( $initial['accompanyingPeriodWorkEvaluations'] = $this->normalizer->normalize(
$object->getAccompanyingPeriodWorkEvaluations()->getValues(), $object->getAccompanyingPeriodWorkEvaluations()->getValues(),
$format, $format,
[...$cleanContext] $cleanContext
); );
// add the referrers // add the referrers
@@ -119,7 +121,7 @@ class AccompanyingPeriodWorkNormalizer implements \Symfony\Component\Serializer\
'relatedEntityClass' => AccompanyingPeriodWork::class, 'relatedEntityClass' => AccompanyingPeriodWork::class,
]); ]);
$initial['workflows'] = $this->normalizer->normalize($workflows, 'json', $context); $initial['workflows'] = $this->normalizer->normalize($workflows, 'json', $contextWithIgnore);
} }
return $initial; return $initial;
@@ -128,18 +130,15 @@ class AccompanyingPeriodWorkNormalizer implements \Symfony\Component\Serializer\
public function supportsNormalization($data, ?string $format = null, array $context = []): bool public function supportsNormalization($data, ?string $format = null, array $context = []): bool
{ {
return match ($format) { return match ($format) {
'json' => $data instanceof AccompanyingPeriodWork && ($context[self::IGNORE_WORK] ?? null) !== spl_object_hash($data), 'json' => $data instanceof AccompanyingPeriodWork,
'docgen' => ($data instanceof AccompanyingPeriodWork || (null === $data && ($context['docgen:expects'] ?? null) === AccompanyingPeriodWork::class)) 'docgen' => ($data instanceof AccompanyingPeriodWork
&& !array_key_exists(self::IGNORE_WORK, $context), || (null === $data && ($context['docgen:expects'] ?? null) === AccompanyingPeriodWork::class)),
default => false, default => false,
}; };
} }
public function getSupportedTypes(?string $format): array public function getSupportedTypes(?string $format): array
{ {
return match ($format) { return 'json' === $format ? [AccompanyingPeriodWork::class => null] : [];
'json', 'docgen' => [AccompanyingPeriodWork::class => true],
default => [],
};
} }
} }

View File

@@ -56,31 +56,19 @@ class SocialIssueNormalizer implements \Symfony\Component\Serializer\Normalizer\
public function supportsNormalization($data, $format = null, array $context = []): bool public function supportsNormalization($data, $format = null, array $context = []): bool
{ {
if ($data instanceof SocialIssue && 'json' === $format) { return match ($format) {
return true; 'json' => $data instanceof SocialIssue,
} 'docgen' => $data instanceof SocialIssue ||
(null === $data && ($context['docgen:expects'] ?? null) === SocialIssue::class),
if ('docgen' === $format) { default => false,
if ($data instanceof SocialIssue) { };
return true;
}
if (null === $data && SocialIssue::class === ($context['docgen:expects'] ?? null)) {
return true;
}
}
return false;
} }
public function getSupportedTypes(?string $format): array public function getSupportedTypes(?string $format): array
{ {
if ('json' === $format || 'docgen' === $format) { return match ($format) {
return [ 'json', 'docgen' => [SocialIssue::class => true],
SocialIssue::class => true, default => [],
]; };
}
return [];
} }
} }