248 lines
10 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\PersonBundle\Serializer\Normalizer;
use Chill\BudgetBundle\Service\Summary\SummaryBudgetInterface;
use Chill\DocGeneratorBundle\Serializer\Helper\NormalizeNullValueHelper;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Civility;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\PersonAltName;
use Chill\PersonBundle\Repository\Relationships\RelationshipRepository;
use Chill\PersonBundle\Templating\Entity\PersonRenderInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\DataFixtures\Exception\CircularReferenceException;
use libphonenumber\PhoneNumber;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
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\Contracts\Translation\TranslatorInterface;
class PersonDocGenNormalizer implements
ContextAwareNormalizerInterface,
NormalizerAwareInterface
{
use NormalizerAwareTrait;
private const CIRCULAR_KEY = 'person:circular';
public function __construct(private readonly PersonRenderInterface $personRender, private readonly RelationshipRepository $relationshipRepository, private readonly TranslatorInterface $translator, private readonly TranslatableStringHelper $translatableStringHelper, private readonly SummaryBudgetInterface $summaryBudget) {}
public function normalize($person, $format = null, array $context = [])
{
try {
$context = $this->addCircularToContext($person, $context);
} catch (CircularReferenceException) {
return [
'isNull' => true,
'isCircular' => true,
'text' => '',
];
}
/** @var Person $person */
$dateContext = $context;
$dateContext['docgen:expects'] = \DateTimeInterface::class;
$addressContext = array_merge($context, ['docgen:expects' => Address::class]);
$phonenumberContext = array_merge($context, ['docgen:expects' => PhoneNumber::class]);
$centerContext = array_merge($context, ['docgen:expects' => Center::class]);
$personResourceContext = array_merge($context, [
'docgen:expects' => Person\PersonResource::class,
// we simplify the list of attributes for the embedded persons
AbstractNormalizer::GROUPS => ['docgen:read'],
// when a person reference the same person... take care of circular references
AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => fn ($object, $format, $context) => $this->normalizer->normalize(null, $format, $context),
]);
if (null === $person) {
return $this->normalizeNullValue($format, $context);
}
if (!$person instanceof Person) {
throw new UnexpectedValueException();
}
$data = [
'type' => 'person',
'id' => $person->getId(),
'isNull' => false,
'civility' => $this->normalizer->normalize($person->getCivility(), $format, array_merge($context, ['docgen:expects' => Civility::class])),
'firstName' => $person->getFirstName(),
'lastName' => $person->getLastName(),
'altNames' => \implode(
', ',
\array_map(
static fn (PersonAltName $altName) => $altName->getLabel(),
$person->getAltNames()->toArray()
)
),
'text' => $this->personRender->renderString($person, []),
'age' => (int) $person->getAge(),
'birthdate' => $this->normalizer->normalize($person->getBirthdate(), $format, $dateContext),
'deathdate' => $this->normalizer->normalize($person->getDeathdate(), $format, $dateContext),
'gender' => $this->translator->trans($person->getGender()),
'maritalStatus' => null !== ($ms = $person->getMaritalStatus()) ? $this->translatableStringHelper->localize($ms->getName()) : '',
'maritalStatusDate' => $this->normalizer->normalize($person->getMaritalStatusDate(), $format, $dateContext),
'maritalStatusComment' => $this->normalizer->normalize($person->getMaritalStatusComment(), $format, $dateContext),
'email' => $person->getEmail(),
'firstPhoneNumber' => $this->normalizer->normalize($person->getPhonenumber() ?? $person->getMobilenumber(), $format, $phonenumberContext),
'fixPhoneNumber' => $this->normalizer->normalize($person->getPhonenumber(), $format, $phonenumberContext),
'mobilePhoneNumber' => $this->normalizer->normalize($person->getMobilenumber(), $format, $phonenumberContext),
'nationality' => null !== ($c = $person->getNationality()) ? $this->translatableStringHelper->localize($c->getName()) : '',
'placeOfBirth' => $person->getPlaceOfBirth(),
'memo' => $person->getMemo(),
'numberOfChildren' => (string) $person->getNumberOfChildren(),
'address' => $this->normalizer->normalize($person->getCurrentPersonAddress(), $format, $addressContext),
'resources' => $this->normalizer->normalize($person->getResources(), $format, $personResourceContext),
'center' => $this->normalizer->normalize($person->getCenter(), $format, $centerContext),
];
if ($context['docgen:person:with-household'] ?? false) {
$data['household'] = $this->normalizer->normalize(
$person->getCurrentHousehold(),
$format,
array_merge($context, [
'docgen:expects' => Household::class,
'docgen:person:with-household' => false,
'docgen:person:with-relations' => false,
'docgen:person:with-budget' => false,
])
);
}
if ($context['docgen:person:with-relations'] ?? false) {
$data['relations'] = $this->normalizer->normalize(
new ArrayCollection($this->relationshipRepository->findByPerson($person)),
$format,
array_merge($context, [
'docgen:person:with-household' => false,
'docgen:person:with-relation' => false,
'docgen:relationship:counterpart' => $person,
'docgen:person:with-budget' => false,
])
);
}
if ($context['docgen:person:with-budget'] ?? false) {
$data['budget']['person'] = $this->summaryBudget->getSummaryForPerson($person);
if ($context['docgen:person:with-household'] ?? false) {
$data['budget']['household'] = $this->summaryBudget->getSummaryForHousehold($person->getCurrentHousehold());
}
}
return $data;
}
public function supportsNormalization($data, $format = null, array $context = [])
{
if ('docgen' !== $format) {
return false;
}
return
$data instanceof Person
|| (
null === $data
&& Person::class === ($context['docgen:expects'] ?? null)
);
}
private function addCircularToContext($person, $context)
{
if (null === $person) {
$key = 'n';
} else {
$key = spl_object_hash($person);
}
if (!\array_key_exists(self::CIRCULAR_KEY, $context)) {
$context[self::CIRCULAR_KEY] = [$key];
return $context;
}
$occurences = array_reduce($context[self::CIRCULAR_KEY], static function ($carry, $item) use ($key) {
if ($key === $item) {
++$carry;
}
return $carry;
}, 0);
if (2 <= $occurences) {
throw new CircularReferenceException();
}
$context[self::CIRCULAR_KEY][] = $key;
return $context;
}
private function hasGroup($context, string $group): bool
{
$groups = $context[AbstractNormalizer::GROUPS] ?? [];
if (\is_string($groups)) {
$groups = [$groups];
}
return \in_array($group, $groups, true);
}
private function normalizeNullValue(string $format, array $context)
{
$normalizer = new NormalizeNullValueHelper($this->normalizer, 'type', 'person');
$attributes = [
'id', 'firstName', 'lastName', 'age', 'altNames', 'text',
'center' => Center::class,
'civility' => Civility::class,
'birthdate' => \DateTimeInterface::class,
'deathdate' => \DateTimeInterface::class,
'gender', 'maritalStatus',
'maritalStatusDate' => \DateTimeInterface::class,
'maritalStatusComment',
'email', 'firstPhoneNumber', 'fixPhoneNumber', 'mobilePhoneNumber', 'nationality',
'placeOfBirth', 'memo', 'numberOfChildren',
'address' => Address::class,
];
if ($context['docgen:person:with-household'] ?? false) {
$attributes['household'] = Household::class;
}
$data = $normalizer->normalize($attributes, $format, $context);
$data['resources'] = [];
if ($context['docgen:person:with-relations'] ?? false) {
$data['relations'] = [];
}
if ($context['docgen:person:with-budget'] ?? false) {
$data['budget']['person'] = $this->summaryBudget->getSummaryForPerson(null);
if ($context['docgen:person:with-household'] ?? false) {
$data['budget']['household'] = $this->summaryBudget->getSummaryForHousehold(null);
}
}
return $data;
}
}