DX: Refactor ListPerson to extract methods linked to label and select

This commit is contained in:
Julien Fastré 2022-10-26 13:30:16 +02:00
parent f434cc5c02
commit f4c3997e55
2 changed files with 443 additions and 368 deletions

View File

@ -20,28 +20,20 @@ use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\MainBundle\Export\Helper\ExportAddressHelper;
use Chill\MainBundle\Export\ListInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Repository\CivilityRepositoryInterface;
use Chill\MainBundle\Repository\CountryRepository;
use Chill\MainBundle\Repository\LanguageRepositoryInterface;
use Chill\MainBundle\Repository\UserRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Repository\MaritalStatusRepositoryInterface;
use Chill\PersonBundle\Export\Helper\ListPersonHelper;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use DateTime;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Exception;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use function addcslashes;
use function array_key_exists;
use function array_keys;
@ -56,96 +48,35 @@ use function uniqid;
*/
class ListPerson implements ExportElementValidatedInterface, ListInterface, GroupedExportInterface
{
public const FIELDS = [
'id',
'civility',
'firstName',
'lastName',
'birthdate',
'center',
'deathdate',
'placeOfBirth',
'gender',
'genderComment',
'maritalStatus',
'maritalStatusComment',
'maritalStatusDate',
'memo',
'email',
'phonenumber',
'mobilenumber',
'numberOfChildren',
'contactInfo',
'countryOfBirth',
'nationality',
// add full addresses
'address_fields',
// add a list of spoken languages
'spokenLanguages',
// add household id
'household_id',
// add created at, created by, updated at, and updated by
'lifecycleUpdate',
];
private const HELPER_ATTRIBUTES =
ExportAddressHelper::F_ATTRIBUTES |
ExportAddressHelper::F_BUILDING |
ExportAddressHelper::F_COUNTRY |
ExportAddressHelper::F_GEOM |
ExportAddressHelper::F_POSTAL_CODE |
ExportAddressHelper::F_STREET |
ExportAddressHelper::F_AS_STRING;
private ExportAddressHelper $addressHelper;
private CivilityRepositoryInterface $civilityRepository;
private CountryRepository $countryRepository;
private CustomFieldProvider $customFieldProvider;
private EntityManagerInterface $entityManager;
private LanguageRepositoryInterface $languageRepository;
private MaritalStatusRepositoryInterface $maritalStatusRepository;
private ListPersonHelper $listPersonHelper;
private $slugs = [];
private TranslatableStringHelper $translatableStringHelper;
private TranslatorInterface $translator;
private UserRepositoryInterface $userRepository;
public function __construct(
ExportAddressHelper $addressHelper,
CivilityRepositoryInterface $civilityRepository,
CountryRepository $countryRepository,
CustomFieldProvider $customFieldProvider,
ListPersonHelper $listPersonHelper,
EntityManagerInterface $em,
LanguageRepositoryInterface $languageRepository,
MaritalStatusRepositoryInterface $maritalStatusRepository,
TranslatableStringHelper $translatableStringHelper,
TranslatorInterface $translator,
UserRepositoryInterface $userRepository
TranslatableStringHelper $translatableStringHelper
) {
$this->addressHelper = $addressHelper;
$this->civilityRepository = $civilityRepository;
$this->countryRepository = $countryRepository;
$this->entityManager = $em;
$this->languageRepository = $languageRepository;
$this->maritalStatusRepository = $maritalStatusRepository;
$this->translator = $translator;
$this->translatableStringHelper = $translatableStringHelper;
$this->customFieldProvider = $customFieldProvider;
$this->userRepository = $userRepository;
$this->listPersonHelper = $listPersonHelper;
$this->entityManager = $em;
$this->translatableStringHelper = $translatableStringHelper;
}
public function buildForm(FormBuilderInterface $builder)
{
$choices = array_combine(self::FIELDS, self::FIELDS);
$choices = array_combine(ListPersonHelper::FIELDS, ListPersonHelper::FIELDS);
foreach ($this->getCustomFields() as $cf) {
$choices[$this->translatableStringHelper->localize($cf->getName())]
@ -205,183 +136,24 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou
public function getLabels($key, array $values, $data)
{
if (substr($key, 0, strlen('address_fields')) === 'address_fields') {
return $this->addressHelper->getLabel($key, $values, $data, 'address_fields');
if (in_array($key, $this->listPersonHelper->getAllPossibleFields(), true)) {
return $this->listPersonHelper->getLabels($key, $values, $data);
}
switch ($key) {
case 'birthdate':
case 'deathdate':
case 'maritalStatusDate':
case 'createdAt':
case 'updatedAt':
// for birthdate, we have to transform the string into a date
// to format the date correctly.
return function ($value) use ($key) {
if ('_header' === $value) {
return $this->translator->trans($key);
}
if (null === $value) {
return '';
}
// warning: won't work with DateTimeImmutable as we reset time a few lines later
$date = DateTime::createFromFormat('Y-m-d', $value);
$hasTime = false;
if (false === $date) {
$date = DateTime::createFromFormat('Y-m-d H:i:s', $value);
$hasTime = true;
}
// check that the creation could occurs.
if (false === $date) {
throw new Exception(sprintf('The value %s could '
. 'not be converted to %s', $value, DateTime::class));
}
if (!$hasTime) {
$date->setTime(0, 0, 0);
}
return $date;
};
case 'createdBy':
case 'updatedBy':
return function ($value) use ($key) {
if ('_header' === $value) {
return $this->translator->trans($key);
}
if (null === $value) {
return '';
}
return $this->userRepository->find($value)->getLabel();
};
case 'civility':
return function ($value) use ($key) {
if ('_header' === $value) {
return $this->translator->trans($key);
}
if (null === $value) {
return '';
}
$civility = $this->civilityRepository->find($value);
if (null === $civility) {
return '';
}
return $this->translatableStringHelper->localize($civility->getName());
};
case 'gender':
// for gender, we have to translate men/women statement
return function ($value) use ($key) {
if ('_header' === $value) {
return $this->translator->trans($key);
}
return $this->translator->trans($value);
};
case 'maritalStatus':
return function ($value) use ($key) {
if ('_header' === $value) {
return $this->translator->trans($key);
}
if (null === $value) {
return '';
}
$maritalStatus = $this->maritalStatusRepository->find($value);
return $this->translatableStringHelper->localize($maritalStatus->getName());
};
case 'spokenLanguages':
return function ($value) use ($key) {
if ('_header' === $value) {
return $this->translator->trans($key);
}
if (null === $value) {
return '';
}
$ids = json_decode($value);
return
implode(
'|',
array_map(function ($id) {
if (null === $id) {
return '';
}
$lang = $this->languageRepository->find($id);
if (null === $lang) {
return null;
}
return $this->translatableStringHelper->localize($lang->getName());
}, $ids)
);
};
case 'countryOfBirth':
case 'nationality':
return function ($value) use ($key) {
if ('_header' === $value) {
return $this->translator->trans($key);
}
if (null === $value) {
return '';
}
$country = $this->countryRepository->find($value);
return $this->translatableStringHelper->localize(
$country->getName()
);
};
default:
// for fields which are associated with person
if (in_array($key, self::FIELDS, true)) {
return function ($value) use ($key) {
if ('_header' === $value) {
return $this->translator->trans($key);
}
return $value;
};
}
return $this->getLabelForCustomField($key, $values, $data);
}
return $this->getLabelForCustomField($key, $values, $data);
}
public function getQueryKeys($data)
{
$fields = [];
foreach (self::FIELDS as $key) {
foreach (ListPersonHelper::FIELDS as $key) {
if (!in_array($key, $data['fields'], true)) {
continue;
}
if (substr($key, 0, strlen('address_fields')) === 'address_fields') {
$fields = array_merge($fields, $this->addressHelper->getKeys(self::HELPER_ATTRIBUTES, 'address_fields'));
$fields = array_merge($fields, $this->addressHelper->getKeys(ListPersonHelper::HELPER_ATTRIBUTES, 'address_fields'));
continue;
}
@ -442,109 +214,7 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou
$fields = $data['fields'];
foreach (self::FIELDS as $f) {
if (!in_array($f, $fields, true)) {
continue;
}
switch ($f) {
case 'countryOfBirth':
case 'nationality':
$qb->addSelect(sprintf('IDENTITY(person.%s) as %s', $f, $f));
break;
case 'address_fields':
foreach ($this->addressHelper->getKeys(self::HELPER_ATTRIBUTES, 'address_fields') as $key) {
$qb
->addSelect(sprintf('IDENTITY(personHouseholdAddress.address) AS %s', $key));
}
$this->addCurrentAddressAt($qb, $data['address_date']);
break;
case 'spokenLanguages':
$qb
->leftJoin('person.spokenLanguages', 'spokenLanguage')
->addSelect('AGGREGATE(spokenLanguage.id) AS spokenLanguages')
->addGroupBy('person');
if (in_array('center', $fields, true)) {
$qb->addGroupBy('center');
}
if (in_array('address_fields', $fields, true)) {
$qb->addGroupBy('address_fieldsid');
}
if (in_array('household_id', $fields, true)) {
$qb->addGroupBy('household_id');
}
break;
case 'household_id':
$qb
->addSelect('IDENTITY(personHouseholdAddress.household) AS household_id');
$this->addCurrentAddressAt($qb, $data['address_date']);
break;
case 'center':
$qb
->addSelect('IDENTITY(centerHistory.center) AS center')
->leftJoin('person.centerHistory', 'centerHistory')
->andWhere(
$qb->expr()->orX(
$qb->expr()->isNull('centerHistory'),
$qb->expr()->andX(
$qb->expr()->lte('centerHistory.startDate', ':address_date'),
$qb->expr()->orX(
$qb->expr()->isNull('centerHistory.endDate'),
$qb->expr()->gte('centerHistory.endDate', ':address_date')
)
)
)
)
->setParameter('address_date', $data['address_date']);
break;
case 'lifecycleUpdate':
$qb
->addSelect('person.createdAt AS createdAt')
->addSelect('IDENTITY(person.createdBy) AS createdBy')
->addSelect('person.updatedAt AS updatedAt')
->addSelect('IDENTITY(person.updatedBy) AS updatedBy');
break;
case 'genderComment':
$qb->addSelect('person.genderComment.comment AS genderComment');
break;
case 'maritalStatus':
$qb->addSelect('IDENTITY(person.maritalStatus) AS maritalStatus');
break;
case 'maritalStatusComment':
$qb->addSelect('person.maritalStatusComment.comment AS maritalStatusComment');
break;
case 'civility':
$qb->addSelect('IDENTITY(person.civility) AS civility');
break;
default:
$qb->addSelect(sprintf('person.%s as %s', $f, $f));
}
}
$this->listPersonHelper->addSelect($qb, $fields, $data['address_date']);
foreach ($this->getCustomFields() as $cf) {
if (!in_array($cf->getSlug(), $fields, true)) {
@ -595,7 +265,7 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou
{
// get the field starting with address_
$addressFields = array_filter(
self::FIELDS,
ListPersonHelper::FIELDS,
static fn (string $el): bool => substr($el, 0, 8) === 'address_'
);
@ -611,29 +281,6 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou
}
}
private function addCurrentAddressAt(QueryBuilder $qb, DateTimeImmutable $date): void
{
if (!(in_array('personHouseholdAddress', $qb->getAllAliases(), true))) {
$qb
->leftJoin('person.householdAddresses', 'personHouseholdAddress')
->andWhere(
$qb->expr()->orX(
// no address at this time
$qb->expr()->isNull('personHouseholdAddress'),
// there is one address...
$qb->expr()->andX(
$qb->expr()->lte('personHouseholdAddress.validFrom', ':address_date'),
$qb->expr()->orX(
$qb->expr()->isNull('personHouseholdAddress.validTo'),
$qb->expr()->gt('personHouseholdAddress.validTo', ':address_date')
)
)
)
)
->setParameter('address_date', $date);
}
}
private function DQLToSlug($cleanedSlug)
{
return $this->slugs[$cleanedSlug]['slug'];

View File

@ -0,0 +1,428 @@
<?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\Export\Helper;
use Chill\MainBundle\Export\Helper\ExportAddressHelper;
use Chill\MainBundle\Repository\CivilityRepositoryInterface;
use Chill\MainBundle\Repository\CountryRepository;
use Chill\MainBundle\Repository\LanguageRepositoryInterface;
use Chill\MainBundle\Repository\UserRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Repository\MaritalStatusRepositoryInterface;
use DateTime;
use DateTimeImmutable;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Exception;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use Symfony\Contracts\Translation\TranslatorInterface;
use function in_array;
use function strlen;
/**
* Helper for list person: provide:.
*
* * the labels;
* * add select statement in query
*
* for some fields
*/
class ListPersonHelper
{
public const FIELDS = [
'id',
'civility',
'firstName',
'lastName',
'birthdate',
'center',
'deathdate',
'placeOfBirth',
'gender',
'genderComment',
'maritalStatus',
'maritalStatusComment',
'maritalStatusDate',
'memo',
'email',
'phonenumber',
'mobilenumber',
'numberOfChildren',
'contactInfo',
'countryOfBirth',
'nationality',
// add full addresses
'address_fields',
// add a list of spoken languages
'spokenLanguages',
// add household id
'household_id',
// add created at, created by, updated at, and updated by
'lifecycleUpdate',
];
public const HELPER_ATTRIBUTES = ExportAddressHelper::F_ATTRIBUTES |
ExportAddressHelper::F_BUILDING |
ExportAddressHelper::F_COUNTRY |
ExportAddressHelper::F_GEOM |
ExportAddressHelper::F_POSTAL_CODE |
ExportAddressHelper::F_STREET |
ExportAddressHelper::F_AS_STRING;
private ExportAddressHelper $addressHelper;
private CivilityRepositoryInterface $civilityRepository;
private CountryRepository $countryRepository;
private LanguageRepositoryInterface $languageRepository;
private MaritalStatusRepositoryInterface $maritalStatusRepository;
private TranslatableStringHelper $translatableStringHelper;
private TranslatorInterface $translator;
private UserRepositoryInterface $userRepository;
public function __construct(
ExportAddressHelper $addressHelper,
CivilityRepositoryInterface $civilityRepository,
CountryRepository $countryRepository,
LanguageRepositoryInterface $languageRepository,
MaritalStatusRepositoryInterface $maritalStatusRepository,
TranslatableStringHelper $translatableStringHelper,
TranslatorInterface $translator,
UserRepositoryInterface $userRepository
) {
$this->addressHelper = $addressHelper;
$this->civilityRepository = $civilityRepository;
$this->countryRepository = $countryRepository;
$this->languageRepository = $languageRepository;
$this->maritalStatusRepository = $maritalStatusRepository;
$this->translatableStringHelper = $translatableStringHelper;
$this->translator = $translator;
$this->userRepository = $userRepository;
}
/**
* @param array|value-of<self::FIELDS>[] $fields
*/
public function addSelect(QueryBuilder $qb, array $fields, DateTimeImmutable $computedDate): void
{
foreach (ListPersonHelper::FIELDS as $f) {
if (!in_array($f, $fields, true)) {
continue;
}
switch ($f) {
case 'countryOfBirth':
case 'nationality':
$qb->addSelect(sprintf('IDENTITY(person.%s) as %s', $f, $f));
break;
case 'address_fields':
foreach ($this->addressHelper->getKeys(ListPersonHelper::HELPER_ATTRIBUTES, 'address_fields') as $key) {
$qb
->addSelect(sprintf('IDENTITY(personHouseholdAddress.address) AS %s', $key));
}
$this->addCurrentAddressAt($qb, $computedDate);
break;
case 'spokenLanguages':
$qb
->leftJoin('person.spokenLanguages', 'spokenLanguage')
->addSelect('AGGREGATE(spokenLanguage.id) AS spokenLanguages')
->addGroupBy('person');
if (in_array('center', $fields, true)) {
$qb->addGroupBy('center');
}
if (in_array('address_fields', $fields, true)) {
$qb->addGroupBy('address_fieldsid');
}
if (in_array('household_id', $fields, true)) {
$qb->addGroupBy('household_id');
}
break;
case 'household_id':
$qb
->addSelect('IDENTITY(personHouseholdAddress.household) AS household_id');
$this->addCurrentAddressAt($qb, $computedDate);
break;
case 'center':
$qb
->addSelect('IDENTITY(centerHistory.center) AS center')
->leftJoin('person.centerHistory', 'centerHistory')
->andWhere(
$qb->expr()->orX(
$qb->expr()->isNull('centerHistory'),
$qb->expr()->andX(
$qb->expr()->lte('centerHistory.startDate', ':address_date'),
$qb->expr()->orX(
$qb->expr()->isNull('centerHistory.endDate'),
$qb->expr()->gte('centerHistory.endDate', ':address_date')
)
)
)
)
->setParameter('address_date', $computedDate);
break;
case 'lifecycleUpdate':
$qb
->addSelect('person.createdAt AS createdAt')
->addSelect('IDENTITY(person.createdBy) AS createdBy')
->addSelect('person.updatedAt AS updatedAt')
->addSelect('IDENTITY(person.updatedBy) AS updatedBy');
break;
case 'genderComment':
$qb->addSelect('person.genderComment.comment AS genderComment');
break;
case 'maritalStatus':
$qb->addSelect('IDENTITY(person.maritalStatus) AS maritalStatus');
break;
case 'maritalStatusComment':
$qb->addSelect('person.maritalStatusComment.comment AS maritalStatusComment');
break;
case 'civility':
$qb->addSelect('IDENTITY(person.civility) AS civility');
break;
default:
$qb->addSelect(sprintf('person.%s as %s', $f, $f));
}
}
}
/**
* @return array|string[]
*/
public function getAllPossibleFields(): array
{
return array_merge(
self::FIELDS,
['createdAt', 'createdBy', 'updatedAt', 'updatedBy'],
$this->addressHelper->getKeys(self::HELPER_ATTRIBUTES, 'address_fields')
);
}
public function getLabels($key, array $values, $data): callable
{
if (substr($key, 0, strlen('address_fields')) === 'address_fields') {
return $this->addressHelper->getLabel($key, $values, $data, 'address_fields');
}
switch ($key) {
case 'birthdate':
case 'deathdate':
case 'maritalStatusDate':
case 'createdAt':
case 'updatedAt':
// for birthdate, we have to transform the string into a date
// to format the date correctly.
return function ($value) use ($key) {
if ('_header' === $value) {
return $this->translator->trans($key);
}
if (null === $value) {
return '';
}
// warning: won't work with DateTimeImmutable as we reset time a few lines later
$date = DateTime::createFromFormat('Y-m-d', $value);
$hasTime = false;
if (false === $date) {
$date = DateTime::createFromFormat('Y-m-d H:i:s', $value);
$hasTime = true;
}
// check that the creation could occurs.
if (false === $date) {
throw new Exception(sprintf('The value %s could '
. 'not be converted to %s', $value, DateTime::class));
}
if (!$hasTime) {
$date->setTime(0, 0, 0);
}
return $date;
};
case 'createdBy':
case 'updatedBy':
return function ($value) use ($key) {
if ('_header' === $value) {
return $this->translator->trans($key);
}
if (null === $value) {
return '';
}
return $this->userRepository->find($value)->getLabel();
};
case 'civility':
return function ($value) use ($key) {
if ('_header' === $value) {
return $this->translator->trans($key);
}
if (null === $value) {
return '';
}
$civility = $this->civilityRepository->find($value);
if (null === $civility) {
return '';
}
return $this->translatableStringHelper->localize($civility->getName());
};
case 'gender':
// for gender, we have to translate men/women statement
return function ($value) use ($key) {
if ('_header' === $value) {
return $this->translator->trans($key);
}
return $this->translator->trans($value);
};
case 'maritalStatus':
return function ($value) use ($key) {
if ('_header' === $value) {
return $this->translator->trans($key);
}
if (null === $value) {
return '';
}
$maritalStatus = $this->maritalStatusRepository->find($value);
return $this->translatableStringHelper->localize($maritalStatus->getName());
};
case 'spokenLanguages':
return function ($value) use ($key) {
if ('_header' === $value) {
return $this->translator->trans($key);
}
if (null === $value) {
return '';
}
$ids = json_decode($value);
return
implode(
'|',
array_map(function ($id) {
if (null === $id) {
return '';
}
$lang = $this->languageRepository->find($id);
if (null === $lang) {
return null;
}
return $this->translatableStringHelper->localize($lang->getName());
}, $ids)
);
};
case 'countryOfBirth':
case 'nationality':
return function ($value) use ($key) {
if ('_header' === $value) {
return $this->translator->trans($key);
}
if (null === $value) {
return '';
}
$country = $this->countryRepository->find($value);
return $this->translatableStringHelper->localize(
$country->getName()
);
};
default:
// for fields which are associated with person
if (in_array($key, ListPersonHelper::FIELDS, true)) {
return function ($value) use ($key) {
if ('_header' === $value) {
return $this->translator->trans($key);
}
return $value;
};
}
}
}
private function addCurrentAddressAt(QueryBuilder $qb, DateTimeImmutable $date): void
{
if (!(in_array('personHouseholdAddress', $qb->getAllAliases(), true))) {
$qb
->leftJoin('person.householdAddresses', 'personHouseholdAddress')
->andWhere(
$qb->expr()->orX(
// no address at this time
$qb->expr()->isNull('personHouseholdAddress'),
// there is one address...
$qb->expr()->andX(
$qb->expr()->lte('personHouseholdAddress.validFrom', ':address_date'),
$qb->expr()->orX(
$qb->expr()->isNull('personHouseholdAddress.validTo'),
$qb->expr()->gt('personHouseholdAddress.validTo', ':address_date')
)
)
)
)
->setParameter('address_date', $date);
}
}
}