mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
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.
310 lines
12 KiB
PHP
310 lines
12 KiB
PHP
<?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\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;
|
|
use Symfony\Component\PropertyAccess\PropertyAccessor;
|
|
use Symfony\Component\Serializer\Exception\ExceptionInterface;
|
|
use Symfony\Component\Serializer\Exception\LogicException;
|
|
use Symfony\Component\Serializer\Mapping\AttributeMetadata;
|
|
use Symfony\Component\Serializer\Mapping\ClassMetadata;
|
|
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
|
|
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
|
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
|
|
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
|
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
|
|
|
use function array_filter;
|
|
use function array_key_exists;
|
|
use function array_merge;
|
|
use function get_class;
|
|
use function implode;
|
|
use function in_array;
|
|
use function is_array;
|
|
use function is_object;
|
|
|
|
class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInterface
|
|
{
|
|
use NormalizerAwareTrait;
|
|
|
|
private ClassMetadataFactoryInterface $classMetadataFactory;
|
|
|
|
private PropertyAccessor $propertyAccess;
|
|
|
|
private TranslatableStringHelperInterface $translatableStringHelper;
|
|
|
|
public function __construct(
|
|
ClassMetadataFactoryInterface $classMetadataFactory,
|
|
TranslatableStringHelperInterface $translatableStringHelper
|
|
) {
|
|
$this->classMetadataFactory = $classMetadataFactory;
|
|
$this->propertyAccess = PropertyAccess::createPropertyAccessor();
|
|
$this->translatableStringHelper = $translatableStringHelper;
|
|
}
|
|
|
|
public function normalize($object, $format = null, array $context = [])
|
|
{
|
|
$classMetadataKey = $object ?? $context['docgen:expects'] ?? null;
|
|
|
|
if (null === $classMetadataKey) {
|
|
throw new RuntimeException('Could not determine the metadata for this object. Either provide a non-null object, or a "docgen:expects" key in the context');
|
|
}
|
|
|
|
if (!$this->classMetadataFactory->hasMetadataFor($classMetadataKey)) {
|
|
throw new LogicException(sprintf(
|
|
'This object does not have metadata: %s. Add groups on this entity to allow to serialize with the format %s and groups %s',
|
|
is_object($object) ? get_class($object) : '(todo' /*$context['docgen:expects'],*/ ,
|
|
$format,
|
|
implode(', ', ($context['groups'] ?? []))
|
|
));
|
|
}
|
|
|
|
$metadata = $this->classMetadataFactory->getMetadataFor($classMetadataKey);
|
|
$expectedGroups = array_key_exists(AbstractNormalizer::GROUPS, $context) ?
|
|
is_array($context[AbstractNormalizer::GROUPS]) ? $context[AbstractNormalizer::GROUPS] : [$context[AbstractNormalizer::GROUPS]]
|
|
: [];
|
|
$attributes = array_filter(
|
|
$metadata->getAttributesMetadata(),
|
|
static function (AttributeMetadata $a) use ($expectedGroups) {
|
|
foreach ($a->getGroups() as $g) {
|
|
if (in_array($g, $expectedGroups, true)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
);
|
|
|
|
if (null === $object) {
|
|
return $this->normalizeNullData($format, $context, $metadata, $attributes);
|
|
}
|
|
|
|
return $this->normalizeObject($object, $format, $context, $expectedGroups, $metadata, $attributes);
|
|
}
|
|
|
|
public function supportsNormalization($data, $format = null): bool
|
|
{
|
|
return 'docgen' === $format && (is_object($data) || null === $data);
|
|
}
|
|
|
|
private function getExpectedType(AttributeMetadata $attribute, ReflectionClass $reflection): string
|
|
{
|
|
$type = null;
|
|
|
|
do {
|
|
// we have to get the expected content
|
|
if ($reflection->hasProperty($attribute->getName())) {
|
|
if (!$reflection->getProperty($attribute->getName())->hasType()) {
|
|
throw new \LogicException(sprintf(
|
|
'Could not determine how the content is determined for the attribute %s on class %s. Add a type on this property',
|
|
$attribute->getName(),
|
|
$reflection->getName()
|
|
));
|
|
}
|
|
|
|
$type = $reflection->getProperty($attribute->getName())->getType();
|
|
} elseif ($reflection->hasMethod($method = 'get' . ucfirst($attribute->getName()))) {
|
|
if (!$reflection->getMethod($method)->hasReturnType()) {
|
|
throw new \LogicException(sprintf(
|
|
'Could not determine how the content is determined for the attribute %s on class %s. Add a return type on the method',
|
|
$attribute->getName(),
|
|
$reflection->getName()
|
|
));
|
|
}
|
|
|
|
$type = $reflection->getMethod($method)->getReturnType();
|
|
} elseif ($reflection->hasMethod($method = 'is' . ucfirst($attribute->getName()))) {
|
|
if (!$reflection->getMethod($method)->hasReturnType()) {
|
|
throw new \LogicException(sprintf(
|
|
'Could not determine how the content is determined for the attribute %s on class %s. Add a return type on the method',
|
|
$attribute->getName(),
|
|
$reflection->getName()
|
|
));
|
|
}
|
|
|
|
$type = $reflection->getMethod($method)->getReturnType();
|
|
} elseif ($reflection->hasMethod($attribute->getName())) {
|
|
if (!$reflection->getMethod($attribute->getName())->hasReturnType()) {
|
|
throw new \LogicException(sprintf(
|
|
'Could not determine how the content is determined for the attribute %s on class %s. Add a return type on the method',
|
|
$attribute->getName(),
|
|
$reflection->getName()
|
|
));
|
|
}
|
|
|
|
$type = $reflection->getMethod($attribute->getName())->getReturnType();
|
|
} else {
|
|
$reflection = $reflection->getParentClass();
|
|
}
|
|
} while (null === $type && $reflection instanceof ReflectionClass);
|
|
|
|
if (null === $type ?? null) {
|
|
throw new \LogicException(sprintf(
|
|
'Could not determine the type for this attribute: %s. Add a return type to the method or property declaration',
|
|
$attribute->getName()
|
|
));
|
|
}
|
|
|
|
return $type->getName();
|
|
}
|
|
|
|
/**
|
|
* @param array|AttributeMetadata[] $attributes
|
|
*/
|
|
private function normalizeNullData(string $format, array $context, ClassMetadata $metadata, array $attributes): array
|
|
{
|
|
$keys = [];
|
|
|
|
// add a discriminator
|
|
if (null !== $discriminator = $metadata->getClassDiscriminatorMapping()) {
|
|
$typeKey = $discriminator->getTypeProperty();
|
|
$typeValue = null;
|
|
|
|
foreach ($discriminator->getTypesMapping() as $type => $typeClass) {
|
|
if ($typeClass === $context['docgen:expects']) {
|
|
$typeValue = $type;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (null === $typeValue) {
|
|
$typeKey = null;
|
|
}
|
|
} else {
|
|
$typeKey = $typeValue = null;
|
|
}
|
|
|
|
foreach ($attributes as $attribute) {
|
|
$key = $attribute->getSerializedName() ?? $attribute->getName();
|
|
$keys[$key] = $this->getExpectedType($attribute, $metadata->getReflectionClass());
|
|
}
|
|
|
|
$normalizer = new NormalizeNullValueHelper($this->normalizer, $typeKey, $typeValue);
|
|
|
|
return $normalizer->normalize($keys, $format, $context, $metadata);
|
|
}
|
|
|
|
/**
|
|
* @param mixed $format
|
|
*/
|
|
private function normalizeNullOutputValue($format, array $context, AttributeMetadata $attribute, ReflectionClass $reflection)
|
|
{
|
|
$type = $this->getExpectedType($attribute, $reflection);
|
|
|
|
switch ($type) {
|
|
case 'array':
|
|
if (in_array('is-translatable', $attribute->getNormalizationContextForGroups(['docgen:read']), true)) {
|
|
return '';
|
|
}
|
|
|
|
return [];
|
|
|
|
case 'bool':
|
|
case 'double':
|
|
case 'float':
|
|
case 'int':
|
|
case 'resource':
|
|
return null;
|
|
|
|
case 'string':
|
|
return '';
|
|
|
|
default:
|
|
return $this->normalizer->normalize(
|
|
null,
|
|
$format,
|
|
array_merge(
|
|
$context,
|
|
['docgen:expects' => $type]
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param $object
|
|
* @param $format
|
|
* @param array|AttributeMetadata[] $attributes
|
|
*
|
|
* @throws ExceptionInterface
|
|
*
|
|
* @return array
|
|
*/
|
|
private function normalizeObject($object, $format, array $context, array $expectedGroups, ClassMetadata $metadata, array $attributes)
|
|
{
|
|
$data = [];
|
|
$data['isNull'] = false;
|
|
$reflection = $metadata->getReflectionClass();
|
|
|
|
// add a discriminator
|
|
if (null !== $discriminator = $metadata->getClassDiscriminatorMapping()) {
|
|
$data[$discriminator->getTypeProperty()] = $discriminator->getMappedObjectType($object);
|
|
}
|
|
|
|
foreach ($attributes as $attribute) {
|
|
/** @var AttributeMetadata $attribute */
|
|
$value = $this->propertyAccess->getValue($object, $attribute->getName());
|
|
$key = $attribute->getSerializedName() ?? $attribute->getName();
|
|
$objectContext = array_merge(
|
|
$context,
|
|
$attribute->getNormalizationContextForGroups(
|
|
is_array($context['groups']) ? $context['groups'] : [$context['groups']]
|
|
)
|
|
);
|
|
$isTranslatable = $objectContext['is-translatable'] ?? false;
|
|
|
|
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 = [];
|
|
|
|
foreach ($value as $k => $v) {
|
|
$arr[$k] =
|
|
$this->normalizer->normalize($v, $format, array_merge(
|
|
$objectContext,
|
|
$attribute->getNormalizationContextForGroups($expectedGroups)
|
|
));
|
|
}
|
|
$data[$key] = $arr;
|
|
} elseif (is_object($value)) {
|
|
$data[$key] =
|
|
$this->normalizer->normalize($value, $format, array_merge(
|
|
$objectContext,
|
|
$attribute->getNormalizationContextForGroups($expectedGroups)
|
|
));
|
|
} elseif (null === $value) {
|
|
$data[$key] = $this->normalizeNullOutputValue($format, $objectContext, $attribute, $reflection);
|
|
} else {
|
|
$data[$key] = $value;
|
|
}
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
}
|