From 426458811cd7d2281b10ce0b764d8d60a101a850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 30 Jan 2020 15:51:39 +0100 Subject: [PATCH] add model + form to handle alt names --- Config/ConfigPersonAltNamesHelper.php | 70 +++++++++++ DependencyInjection/ChillPersonExtension.php | 1 + DependencyInjection/Configuration.php | 20 +++ Entity/Person.php | 39 ++++++ Entity/PersonAltName.php | 118 ++++++++++++++++++ Form/DataMapper/PersonAltNameDataMapper.php | 73 +++++++++++ Form/PersonType.php | 21 +++- Form/Type/PersonAltNameType.php | 70 +++++++++++ Resources/config/doctrine/Person.orm.yml | 5 + .../config/doctrine/PersonAltName.orm.yml | 21 ++++ Resources/config/services/alt_names.yml | 4 + Resources/config/services/form.yml | 8 ++ .../migrations/Version20200128084445.php | 31 +++++ Resources/views/Person/edit.html.twig | 3 + 14 files changed, 482 insertions(+), 2 deletions(-) create mode 100644 Config/ConfigPersonAltNamesHelper.php create mode 100644 Entity/PersonAltName.php create mode 100644 Form/DataMapper/PersonAltNameDataMapper.php create mode 100644 Form/Type/PersonAltNameType.php create mode 100644 Resources/config/doctrine/PersonAltName.orm.yml create mode 100644 Resources/config/services/alt_names.yml create mode 100644 Resources/migrations/Version20200128084445.php diff --git a/Config/ConfigPersonAltNamesHelper.php b/Config/ConfigPersonAltNamesHelper.php new file mode 100644 index 000000000..92c093385 --- /dev/null +++ b/Config/ConfigPersonAltNamesHelper.php @@ -0,0 +1,70 @@ +config = $config; + } + + /** + * Return true if at least one alt name is configured + * + * @return bool + */ + public function hasAltNames(): bool + { + return count($this->config) > 0; + } + + /** + * get the choices as key => values + * + * @return array + */ + public function getChoices(): array + { + $choices = []; + foreach ($this->config as $entry) { + + $labels = $entry['labels']; + $lang = false; + $label = false; + $cur = reset($labels); + while ($cur) { + if (key($labels) === 'lang') { + $lang = current($labels); + } + + if (key($labels) === 'label') { + $label = current($labels); + } + + if ($lang !== FALSE && $label !== FALSE) { + $choices[$entry['key']][$lang] = $label; + $lang = false; + $label = false; + } + $cur = next($labels); + } + } + + return $choices; + } + +} diff --git a/DependencyInjection/ChillPersonExtension.php b/DependencyInjection/ChillPersonExtension.php index ab87fe4ae..ace88e01d 100644 --- a/DependencyInjection/ChillPersonExtension.php +++ b/DependencyInjection/ChillPersonExtension.php @@ -65,6 +65,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac $loader->load('services/form.yml'); $loader->load('services/repository.yml'); $loader->load('services/templating.yml'); + $loader->load('services/alt_names.yml'); // load service advanced search only if configure if ($config['search']['search_by_phone'] != 'never') { diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 471e7d958..9e32389da 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -78,6 +78,26 @@ class Configuration implements ConfigurationInterface ->append($this->addFieldNode('address')) ->append($this->addFieldNode('accompanying_period')) ->append($this->addFieldNode('memo')) + ->arrayNode('alt_names') + ->defaultValue([]) + ->arrayPrototype() + ->children() + ->scalarNode('key') + ->isRequired()->cannotBeEmpty() + ->end() + ->arrayNode('labels')->isRequired()->cannotBeEmpty() + ->children() + ->scalarNode('lang')->isRequired()->cannotBeEmpty() + ->example('fr') + ->end() + ->scalarNode('label')->isRequired()->cannotBeEmpty() + ->example('Nom de jeune fille') + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() ->end() //children for 'person_fields', parent = array 'person_fields' ->end() // person_fields, parent = children of root ->arrayNode('accompanying_periods_fields') diff --git a/Entity/Person.php b/Entity/Person.php index 11bad306a..9674d8b76 100644 --- a/Entity/Person.php +++ b/Entity/Person.php @@ -42,6 +42,12 @@ class Person implements HasCenterInterface { /** @var string The person's last name */ private $lastName; + + /** + * + * @var \Doctrine\Common\Collections\Collection + */ + private $altNames; /** @var \DateTime The person's birthdate */ private $birthdate; //to change in birthdate @@ -123,6 +129,7 @@ class Person implements HasCenterInterface { $this->accompanyingPeriods = new ArrayCollection(); $this->spokenLanguages = new ArrayCollection(); $this->addresses = new ArrayCollection(); + $this->altNames = new ArrayCollection(); if ($opening === null) { $opening = new \DateTime(); @@ -324,7 +331,39 @@ class Person implements HasCenterInterface { { return $this->lastName; } + + public function getAltNames(): \Doctrine\Common\Collections\Collection + { + return $this->altNames; + } + public function setAltNames(\Doctrine\Common\Collections\Collection $altNames) + { + $this->altNames = $altNames; + + return $this; + } + + public function addAltName(PersonAltName $altName) + { + if (FALSE === $this->altNames->contains($altName)) { + $this->altNames->add($altName); + $altName->setPerson($this); + } + + return $this; + } + + public function removeAltName(PersonAltName $altName) + { + if ($this->altNames->contains($altName)) { + $altName->setPerson(null); + $this->altNames->removeElement($altName); + } + + return $this; + } + /** * Set birthdate * diff --git a/Entity/PersonAltName.php b/Entity/PersonAltName.php new file mode 100644 index 000000000..5a54d9728 --- /dev/null +++ b/Entity/PersonAltName.php @@ -0,0 +1,118 @@ +id; + } + + /** + * Set key. + * + * @param string $key + * + * @return PersonAltName + */ + public function setKey($key) + { + $this->key = $key; + + return $this; + } + + /** + * Get key. + * + * @return string + */ + public function getKey() + { + return $this->key; + } + + /** + * Set label. + * + * @param string $label + * + * @return PersonAltName + */ + public function setLabel($label) + { + $this->label = $label; + + return $this; + } + + /** + * Get label. + * + * @return string + */ + public function getLabel() + { + return $this->label; + } + + public function getPerson(): Person + { + return $this->person; + } + + public function setPerson(?Person $person = null) + { + $this->person = $person; + + return $this; + } +} diff --git a/Form/DataMapper/PersonAltNameDataMapper.php b/Form/DataMapper/PersonAltNameDataMapper.php new file mode 100644 index 000000000..3449fe814 --- /dev/null +++ b/Form/DataMapper/PersonAltNameDataMapper.php @@ -0,0 +1,73 @@ +getIterator() as $key => $altName) { + /** @var PersonAltName $altName */ + $mapIndexToKey[$altName->getKey()] = $key; + } + + foreach ($forms as $key => $form) { + if (\array_key_exists($key, $mapIndexToKey)) { + $form->setData($viewData->get($mapIndexToKey[$key])->getLabel()); + } + } + } + + /** + * + * @param FormInterface[] $forms + * @param Collection $viewData + */ + public function mapFormsToData($forms, &$viewData) + { + $mapIndexToKey = []; + foreach ($viewData->getIterator() as $key => $altName) { + /** @var PersonAltName $altName */ + $mapIndexToKey[$altName->getKey()] = $key; + } + + foreach ($forms as $key => $form) { + $isEmpty = empty($form->getData()); + + if (\array_key_exists($key, $mapIndexToKey)) { + if ($isEmpty) { + $viewData->remove($mapIndexToKey[$key]); + } else { + $viewData->get($mapIndexToKey[$key])->setLabel($form->getData()); + } + } else { + if (!$isEmpty) { + $altName = (new PersonAltName()) + ->setKey($key) + ->setLabel($form->getData()) + ; + $viewData->add($altName); + } + } + } + + } +} diff --git a/Form/PersonType.php b/Form/PersonType.php index 923b34c97..7cf24a836 100644 --- a/Form/PersonType.php +++ b/Form/PersonType.php @@ -34,6 +34,8 @@ use Chill\MainBundle\Form\Type\Select2CountryType; use Chill\MainBundle\Form\Type\Select2LanguageType; use Chill\CustomFieldsBundle\Form\Type\CustomFieldType; use Chill\PersonBundle\Form\Type\Select2MaritalStatusType; +use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper; +use Chill\PersonBundle\Form\Type\PersonAltNameType; class PersonType extends AbstractType { @@ -46,14 +48,23 @@ class PersonType extends AbstractType * @var string[] */ protected $config = array(); + + /** + * + * @var ConfigPersonAltNamesHelper + */ + protected $configAltNamesHelper; /** * * @param string[] $personFieldsConfiguration configuration of visibility of some fields */ - public function __construct(array $personFieldsConfiguration) - { + public function __construct( + array $personFieldsConfiguration, + ConfigPersonAltNamesHelper $configAltNamesHelper + ) { $this->config = $personFieldsConfiguration; + $this->configAltNamesHelper = $configAltNamesHelper; } /** @@ -70,6 +81,12 @@ class PersonType extends AbstractType 'required' => true )); + if ($this->configAltNamesHelper->hasAltNames()) { + $builder->add('altNames', PersonAltNameType::class, [ + 'by_reference' => false + ]); + } + if ($this->config['memo'] === 'visible') { $builder ->add('memo', TextareaType::class, array('required' => false)) diff --git a/Form/Type/PersonAltNameType.php b/Form/Type/PersonAltNameType.php new file mode 100644 index 000000000..fc2d752ab --- /dev/null +++ b/Form/Type/PersonAltNameType.php @@ -0,0 +1,70 @@ +configHelper = $configHelper; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + foreach ($this->getKeyChoices() as $label => $key) { + $builder->add($key, TextType::class, [ + 'label' => $label, + 'required' => false + ]); + } + + $builder->setDataMapper(new \Chill\PersonBundle\Form\DataMapper\PersonAltNameDataMapper()); + } + + protected function getKeyChoices() + { + $choices = $this->configHelper->getChoices(); + $translatedChoices = []; + + foreach ($choices as $key => $labels) { + $label = $this->translatableStringHelper->localize($labels); + $translatedChoices[$label] = $key; + } + + return $translatedChoices; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefault('class', \Chill\PersonBundle\Entity\PersonAltName::class) + ; + } + +} diff --git a/Resources/config/doctrine/Person.orm.yml b/Resources/config/doctrine/Person.orm.yml index 081a710da..29d981750 100644 --- a/Resources/config/doctrine/Person.orm.yml +++ b/Resources/config/doctrine/Person.orm.yml @@ -74,6 +74,11 @@ Chill\PersonBundle\Entity\Person: targetEntity: AccompanyingPeriod mappedBy: person cascade: [persist, remove, merge, detach] + altNames: + targetEntity: PersonAltName + mappedBy: person + cascade: [persist, remove, merge, detach] + orphanRemoval: true manyToMany: spokenLanguages: targetEntity: Chill\MainBundle\Entity\Language diff --git a/Resources/config/doctrine/PersonAltName.orm.yml b/Resources/config/doctrine/PersonAltName.orm.yml new file mode 100644 index 000000000..0b8c7b187 --- /dev/null +++ b/Resources/config/doctrine/PersonAltName.orm.yml @@ -0,0 +1,21 @@ +Chill\PersonBundle\Entity\PersonAltName: + type: entity + table: chill_person_alt_name + repositoryClass: Chill\PersonBundle\Repository\PersonAltNameRepository + id: + id: + type: integer + id: true + generator: + strategy: AUTO + fields: + key: + type: string + length: 255 + label: + type: text + + manyToOne: + person: + targetEntity: Person + inversedBy: altNames diff --git a/Resources/config/services/alt_names.yml b/Resources/config/services/alt_names.yml new file mode 100644 index 000000000..62df5b506 --- /dev/null +++ b/Resources/config/services/alt_names.yml @@ -0,0 +1,4 @@ +services: + Chill\PersonBundle\Config\ConfigPersonAltNamesHelper: + arguments: + $config: '%chill_person.person_fields.alt_names%' diff --git a/Resources/config/services/form.yml b/Resources/config/services/form.yml index c2ae1d3dc..2a1390ea3 100644 --- a/Resources/config/services/form.yml +++ b/Resources/config/services/form.yml @@ -3,6 +3,7 @@ services: class: Chill\PersonBundle\Form\PersonType arguments: - "%chill_person.person_fields%" + - '@Chill\PersonBundle\Config\ConfigPersonAltNamesHelper' tags: - { name: form.type } @@ -29,3 +30,10 @@ services: - '@Symfony\Component\Translation\TranslatorInterface' tags: - { name: form.type } + + Chill\PersonBundle\Form\Type\PersonAltNameType: + arguments: + $configHelper: '@Chill\PersonBundle\Config\ConfigPersonAltNamesHelper' + $translatableStringHelper: '@chill.main.helper.translatable_string' + tags: + - { name: form.type } diff --git a/Resources/migrations/Version20200128084445.php b/Resources/migrations/Version20200128084445.php new file mode 100644 index 000000000..6bf4a543c --- /dev/null +++ b/Resources/migrations/Version20200128084445.php @@ -0,0 +1,31 @@ +abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql("CREATE SEQUENCE chill_person_alt_name_id_seq INCREMENT BY 1 MINVALUE 1 START 1"); + $this->addSql("CREATE TABLE chill_person_alt_name (id INT NOT NULL, person_id INT DEFAULT NULL, key VARCHAR(255) NOT NULL, label TEXT NOT NULL, PRIMARY KEY(id))"); + $this->addSql("CREATE INDEX IDX_2628668E217BBB47 ON chill_person_alt_name (person_id)"); + $this->addSql("ALTER TABLE chill_person_alt_name ADD CONSTRAINT FK_2628668E217BBB47 FOREIGN KEY (person_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE"); + } + + public function down(Schema $schema) : void + { + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql("DROP INDEX IDX_2628668E217BBB47"); + $this->addSql("DROP TABLE chill_person_alt_name"); + $this->addSql("DROP SEQUENCE chill_person_alt_name_id_seq"); + } +} diff --git a/Resources/views/Person/edit.html.twig b/Resources/views/Person/edit.html.twig index 712c99803..80dff860f 100644 --- a/Resources/views/Person/edit.html.twig +++ b/Resources/views/Person/edit.html.twig @@ -39,6 +39,9 @@

{{ 'General information'|trans }}

{{ form_row(form.firstName, {'label' : 'First name'}) }} {{ form_row(form.lastName, {'label' : 'Last name'}) }} + {% if form.altNames is defined %} + {{ form_widget(form.altNames, { 'label': 'Alternative names'}) }} + {% endif %} {{ form_row(form.gender, {'label' : 'Gender'}) }}