mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
296 lines
11 KiB
PHP
296 lines
11 KiB
PHP
<?php
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Chill\DocGeneratorBundle\Serializer\Normalizer;
|
|
|
|
use Chill\DocGeneratorBundle\Serializer\Helper\NormalizeNullValueHelper;
|
|
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
|
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, ?string $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, ?string $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);
|
|
}
|
|
|
|
/**
|
|
* @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();
|
|
$isTranslatable = $attribute->getNormalizationContextForGroups(
|
|
is_array($context['groups']) ? $context['groups'] : [$context['groups']]
|
|
)['is-translatable'] ?? false;
|
|
|
|
if ($isTranslatable) {
|
|
$data[$key] = $this->translatableStringHelper
|
|
->localize($value);
|
|
} elseif (is_iterable($value)) {
|
|
$arr = [];
|
|
|
|
foreach ($value as $k => $v) {
|
|
$arr[$k] =
|
|
$this->normalizer->normalize($v, $format, array_merge(
|
|
$context,
|
|
$attribute->getNormalizationContextForGroups($expectedGroups)
|
|
));
|
|
}
|
|
$data[$key] = $arr;
|
|
} elseif (is_object($value)) {
|
|
$data[$key] =
|
|
$this->normalizer->normalize($value, $format, array_merge(
|
|
$context,
|
|
$attribute->getNormalizationContextForGroups($expectedGroups)
|
|
));
|
|
} elseif (null === $value) {
|
|
$data[$key] = $this->normalizeNullOutputValue($format, $context, $attribute, $reflection);
|
|
} else {
|
|
$data[$key] = $value;
|
|
}
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
}
|