From 71eb972c4c6808649b6a37f2e01a9913a29832ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 9 Dec 2015 21:33:57 +0100 Subject: [PATCH 1/4] create validator and tests --- Tests/Validator/BirthdateValidatorTest.php | 112 +++++++++++++++++++ Validator/Constraints/Birthdate.php | 37 ++++++ Validator/Constraints/BirthdateValidator.php | 70 ++++++++++++ 3 files changed, 219 insertions(+) create mode 100644 Tests/Validator/BirthdateValidatorTest.php create mode 100644 Validator/Constraints/Birthdate.php create mode 100644 Validator/Constraints/BirthdateValidator.php diff --git a/Tests/Validator/BirthdateValidatorTest.php b/Tests/Validator/BirthdateValidatorTest.php new file mode 100644 index 000000000..94f1347a5 --- /dev/null +++ b/Tests/Validator/BirthdateValidatorTest.php @@ -0,0 +1,112 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\PersonBundle\Tests\Validator; + +use Chill\PersonBundle\Validator\Constraints\BirthdateValidator; +use Chill\PersonBundle\Validator\Constraints\Birthdate; +use Prophecy\Argument; +use Prophecy\Prophet; + +/** + * Test the behaviour of BirthdayValidator + * + * @author Julien Fastré + */ +class BirthdateValidatorTest extends \PHPUnit_Framework_TestCase +{ + /** + * A prophecy for \Symfony\Component\Validator\Context\ExecutionContextInterface + * + * Will reveal \Symfony\Component\Validator\Context\ExecutionContextInterface + */ + private $context; + + private $prophet; + + /** + * + * @var Birthdate + */ + private $constraint; + + public function setUp() + { + $this->prophet = new Prophet; + + $constraintViolationBuilder = $this->prophet->prophesize(); + $constraintViolationBuilder->willImplement('Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface'); + + $constraintViolationBuilder->setParameter(Argument::any(), Argument::any()) + ->willReturn($constraintViolationBuilder->reveal()); + $constraintViolationBuilder->addViolation() + ->willReturn($constraintViolationBuilder->reveal()); + + $this->context = $this->prophet->prophesize(); + $this->context->willImplement('Symfony\Component\Validator\Context\ExecutionContextInterface'); + $this->context->buildViolation(Argument::type('string')) + ->willReturn($constraintViolationBuilder->reveal()); + + $this->constraint = new Birthdate(); + } + + public function testValidBirthDate() + { + + $date = new \DateTime('2015-01-01'); + + $birthdateValidator = new BirthdateValidator(); + $birthdateValidator->initialize($this->context->reveal()); + + $birthdateValidator->validate($date, $this->constraint); + + $this->context->buildViolation(Argument::any())->shouldNotHaveBeenCalled(); + } + + public function testInvalidBirthDate() + { + $date = new \DateTime('tomorrow'); + + $birthdateValidator = new BirthdateValidator(); + $birthdateValidator->initialize($this->context->reveal()); + + $birthdateValidator->validate($date, $this->constraint); + + $this->context->buildViolation(Argument::type('string'))->shouldHaveBeenCalled(); + } + + public function testInvalidBirthDateWithParameter() + { + $date = (new \DateTime('today'))->sub(new \DateInterval('P1M')); + + $birthdateValidator = new BirthdateValidator('P1Y'); + $birthdateValidator->initialize($this->context->reveal()); + + $birthdateValidator->validate($date, $this->constraint); + + $this->context->buildViolation(Argument::type('string'))->shouldHaveBeenCalled(); + } + + public function tearDown() + { + $this->prophet->checkPredictions(); + } + + +} diff --git a/Validator/Constraints/Birthdate.php b/Validator/Constraints/Birthdate.php new file mode 100644 index 000000000..35ad118df --- /dev/null +++ b/Validator/Constraints/Birthdate.php @@ -0,0 +1,37 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\PersonBundle\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * Create a constraint on birth date: the birthdate after today are not allowed. + * + * It is possible to add a delay before today, expressed as described in + * interval_spec : http://php.net/manual/en/dateinterval.construct.php + * (this interval_spec itself is based on ISO8601 : + * https://en.wikipedia.org/wiki/ISO_8601#Durations) + * + * @author Julien Fastré + */ +class Birthdate extends Constraint +{ + public $message = "The birthdate must be before %date%"; +} diff --git a/Validator/Constraints/BirthdateValidator.php b/Validator/Constraints/BirthdateValidator.php new file mode 100644 index 000000000..5f65c3ed7 --- /dev/null +++ b/Validator/Constraints/BirthdateValidator.php @@ -0,0 +1,70 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\PersonBundle\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; + +/** + * + * + * @author Julien Fastré + */ +class BirthdateValidator extends ConstraintValidator +{ + private $interval_spec = null; + + public function __construct($interval_spec = null) + { + $this->interval_spec = $interval_spec; + } + + public function validate($value, Constraint $constraint) + { + if (!$value instanceof \DateTime) { + throw new \LogicException('The input should a be a \DateTime interface,' + . (is_object($value) ? get_class($value) : gettype($value))); + } + + $limitDate = $this->getLimitDate(); + + if ($limitDate < $value) { + $this->context->buildViolation($constraint->message) + ->setParameter('%date%', $value->format(\DateTime::ATOM)) + ->addViolation(); + } + + } + + /** + * + * @return \DateTime + */ + private function getLimitDate() + { + if ($this->interval_spec !== NULL) { + $interval = new \DateInterval($this->interval_spec); + return (new \DateTime('now'))->sub($interval); + } else { + return (new \DateTime('now')); + } + } + +} From 04c56f667aede6108b53fda60ab777cd30496893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 10 Dec 2015 14:55:12 +0100 Subject: [PATCH 2/4] register birthdateValidator in birthdate validator, improve the message --- DependencyInjection/ChillPersonExtension.php | 5 +++ DependencyInjection/Configuration.php | 35 ++++++++++++++++--- Resources/config/services.yml | 8 +++++ Resources/config/validation.yml | 2 ++ Resources/translations/validators.fr.yml | 1 + .../Controller/PersonControllerCreateTest.php | 2 +- Validator/Constraints/Birthdate.php | 5 +++ Validator/Constraints/BirthdateValidator.php | 7 +++- 8 files changed, 58 insertions(+), 7 deletions(-) diff --git a/DependencyInjection/ChillPersonExtension.php b/DependencyInjection/ChillPersonExtension.php index 548f3536f..589e2c14e 100644 --- a/DependencyInjection/ChillPersonExtension.php +++ b/DependencyInjection/ChillPersonExtension.php @@ -24,8 +24,13 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); + // set configuration for double metaphone $container->setParameter('cl_chill_person.search.use_double_metaphone', $config['search']['use_double_metaphone']); + + // set configuration for validation + $container->setParameter('chill_person.validation.birtdate_not_before', + $config['validation']['birthdate_not_after']); $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.yml'); diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index d92d0cfbe..833b5498d 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -12,6 +12,10 @@ use Symfony\Component\Config\Definition\ConfigurationInterface; */ class Configuration implements ConfigurationInterface { + + private $validationBirthdateNotAfterInfos = "The period before today during which" + . " any birthdate is not allowed. The birthdate is expressed as ISO8601 : " + . "https://en.wikipedia.org/wiki/ISO_8601#Durations"; /** * {@inheritDoc} */ @@ -29,13 +33,34 @@ class Configuration implements ConfigurationInterface ->booleanNode('use_double_metaphone') ->defaultFalse() ->end() - ->booleanNode('use_trigrams')->defaultFalse()->end(); + ->booleanNode('use_trigrams') + ->defaultFalse() + ->end() + ->end() + ->end() + ->arrayNode('validation') + ->canBeDisabled() + ->children() + ->scalarNode('birthdate_not_after') + ->info($this->validationBirthdateNotAfterInfos) + ->defaultValue('P1D') + ->validate() + ->ifTrue(function($period) { + try { + $interval = new \DateInterval($period); + } catch (\Exception $ex) { + return true; + } + return false; + }) + ->thenInvalid('Invalid period for birthdate validation : "%s" ' + . 'The parameter should match duration as defined by ISO8601 : ' + . 'https://en.wikipedia.org/wiki/ISO_8601#Durations') + ->end() + ->end() + ->end(); - // Here you should define the parameters that are allowed to - // configure your bundle. See the documentation linked above for - // more information on that topic. - return $treeBuilder; } } diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 26d220c2f..0e81ec15a 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -48,3 +48,11 @@ services: tags: - { name: security.voter } - { name: chill.role } + + chill.person.birthdate_validation: + class: Chill\PersonBundle\Validator\Constraints\BirthdateValidator + arguments: + - "%chill_person.validation.birtdate_not_before%" + tags: + - { name: validator.constraint_validator, alias: birthdate_not_before } + diff --git a/Resources/config/validation.yml b/Resources/config/validation.yml index ccda164ee..f6e683d3e 100644 --- a/Resources/config/validation.yml +++ b/Resources/config/validation.yml @@ -22,6 +22,8 @@ Chill\PersonBundle\Entity\Person: - Date: message: 'Birthdate not valid' groups: [general, creation] + - Chill\PersonBundle\Validator\Constraints\Birthdate: + groups: [general, creation] gender: - NotNull: groups: [general, creation] diff --git a/Resources/translations/validators.fr.yml b/Resources/translations/validators.fr.yml index b29c5306d..38fabbbbf 100644 --- a/Resources/translations/validators.fr.yml +++ b/Resources/translations/validators.fr.yml @@ -11,3 +11,4 @@ 'Closing date is not valid': 'La date de fermeture n''est pas valide' 'Closing date can not be null': 'La date de fermeture ne peut être nulle' The date of closing is before the date of opening: La période de fermeture est après la période d'ouverture +The birthdate must be before %date%: La date de naissance doit être avant le %date% diff --git a/Tests/Controller/PersonControllerCreateTest.php b/Tests/Controller/PersonControllerCreateTest.php index bcada006a..83acbbf7b 100644 --- a/Tests/Controller/PersonControllerCreateTest.php +++ b/Tests/Controller/PersonControllerCreateTest.php @@ -38,7 +38,7 @@ class PersonControllerCreateTest extends WebTestCase const CREATEDATE_INPUT = "chill_personbundle_person_creation[creation_date]"; const CENTER_INPUT = "chill_personbundle_person_creation[center]"; - const LONG_TEXT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosq."; + const LONG_TEXT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosq. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta.Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosq."; /** * return an authenticated client, useful for submitting form diff --git a/Validator/Constraints/Birthdate.php b/Validator/Constraints/Birthdate.php index 35ad118df..d09d549fd 100644 --- a/Validator/Constraints/Birthdate.php +++ b/Validator/Constraints/Birthdate.php @@ -34,4 +34,9 @@ use Symfony\Component\Validator\Constraint; class Birthdate extends Constraint { public $message = "The birthdate must be before %date%"; + + public function validatedBy() + { + return 'birthdate_not_before'; + } } diff --git a/Validator/Constraints/BirthdateValidator.php b/Validator/Constraints/BirthdateValidator.php index 5f65c3ed7..cba955ef6 100644 --- a/Validator/Constraints/BirthdateValidator.php +++ b/Validator/Constraints/BirthdateValidator.php @@ -38,6 +38,11 @@ class BirthdateValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { + if ($value === NULL) { + + return; + } + if (!$value instanceof \DateTime) { throw new \LogicException('The input should a be a \DateTime interface,' . (is_object($value) ? get_class($value) : gettype($value))); @@ -47,7 +52,7 @@ class BirthdateValidator extends ConstraintValidator if ($limitDate < $value) { $this->context->buildViolation($constraint->message) - ->setParameter('%date%', $value->format(\DateTime::ATOM)) + ->setParameter('%date%', $limitDate->format('d-m-Y')) ->addViolation(); } From 9fe0ae18899cf61e846476ce4838cf7fddcfbc17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 10 Dec 2015 14:55:49 +0100 Subject: [PATCH 3/4] improve person creation to fix error handling --- Controller/PersonController.php | 54 +++++++++++++++++++++------------ Form/CreationPersonType.php | 42 +++++++++++++++---------- 2 files changed, 60 insertions(+), 36 deletions(-) diff --git a/Controller/PersonController.php b/Controller/PersonController.php index 0f0a59117..b70796d24 100644 --- a/Controller/PersonController.php +++ b/Controller/PersonController.php @@ -170,12 +170,12 @@ class PersonController extends Controller ->getGroupCenters()[0] ->getCenter(); - $person = (new Person()) + $person = (new Person(new \DateTime('now'))) ->setCenter($defaultCenter); $form = $this->createForm( new CreationPersonType(CreationPersonType::FORM_NOT_REVIEWED), - array('creation_date' => new \DateTime(), 'center' => $defaultCenter), + $person, array('action' => $this->generateUrl('chill_person_review')) ); @@ -197,18 +197,22 @@ class PersonController extends Controller */ private function _bindCreationForm($form) { - $date = new \DateTime(); - $person = new Person($form['creation_date']->getData()); + /** + * @var Person + */ + $person = $form->getData(); - - $date_of_birth = new \DateTime(); - - $person->setFirstName($form['firstName']->getData()) - ->setLastName($form['lastName']->getData()) - ->setGender($form['gender']->getData()) - ->setBirthdate($form['birthdate']->getData()) - ->setCenter($form['center']->getData()) - ; + $periods = $person->getAccompanyingPeriodsOrdered(); + $period = $periods[0]; + $period->setOpeningDate($form['creation_date']->getData()); +// $person = new Person($form['creation_date']->getData()); +// +// $person->setFirstName($form['firstName']->getData()) +// ->setLastName($form['lastName']->getData()) +// ->setGender($form['gender']->getData()) +// ->setBirthdate($form['birthdate']->getData()) +// ->setCenter($form['center']->getData()) +// ; return $person; } @@ -251,15 +255,18 @@ class PersonController extends Controller $form = $this->createForm( new CreationPersonType(CreationPersonType::FORM_BEING_REVIEWED), - null, array('action' => $this->generateUrl('chill_person_create'))); + new Person(), + array('action' => $this->generateUrl('chill_person_create'))); $form->handleRequest($request); $person = $this->_bindCreationForm($form); - + var_dump($person); $errors = $this->_validatePersonAndAccompanyingPeriod($person); - - if ( count($errors) > 0) { + $this->get('logger')->info(sprintf('Person created with %d errors ', count($errors))); + + if ($errors->count() > 0) { + $this->get('logger')->info('The created person has errors'); $flashBag = $this->get('session')->getFlashBag(); $translator = $this->get('translator'); @@ -271,11 +278,14 @@ class PersonController extends Controller $form = $this->createForm( new CreationPersonType(CreationPersonType::FORM_NOT_REVIEWED), - array('action' => $this->generateUrl('chill_person_create'))); + new Person(), + array('action' => $this->generateUrl('chill_person_review'))); $form->handleRequest($request); return $this->_renderNewForm($form); + } else { + $this->get('logger')->info('Person created without errors'); } $em = $this->getDoctrine()->getManager(); @@ -331,7 +341,7 @@ class PersonController extends Controller $form = $this->createForm(new CreationPersonType()); $form->handleRequest($request); - + var_dump($form->getData()); $person = $this->_bindCreationForm($form); $errors = $this->_validatePersonAndAccompanyingPeriod($person); @@ -349,7 +359,11 @@ class PersonController extends Controller return $this->redirect($this->generateUrl('chill_person_general_edit', array('person_id' => $person->getId()))); } else { - $r = new Response('this should not happen if you reviewed your submission'); + $text = "this should not happen if you reviewed your submission\n"; + foreach ($errors as $error) { + $text .= $error->getMessage()."\n"; + } + $r = new Response($text); $r->setStatusCode(400); return $r; } diff --git a/Form/CreationPersonType.php b/Form/CreationPersonType.php index dc22aa952..6966d8346 100644 --- a/Form/CreationPersonType.php +++ b/Form/CreationPersonType.php @@ -24,9 +24,7 @@ namespace Chill\PersonBundle\Form; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; -use Chill\PersonBundle\Form\Type\CivilType; use Chill\PersonBundle\Form\Type\GenderType; -use CL\BelgianNationalNumberBundle\Form\BelgianNationalNumberType; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer; class CreationPersonType extends AbstractType @@ -53,20 +51,26 @@ class CreationPersonType extends AbstractType if ($this->form_status === self::FORM_BEING_REVIEWED) { $dateToStringTransformer = new DateTimeToStringTransformer( - null, null, 'dd-MM-yyyy', true); + null, null, 'd-m-Y', false); $builder->add('firstName', 'hidden') ->add('lastName', 'hidden') - ->add( $builder->create('birthdate', 'hidden') - ->addModelTransformer($dateToStringTransformer) - ) + ->add('birthdate', 'hidden', array( + 'property_path' => 'birthdate' + )) ->add('gender', 'hidden') - ->add( $builder->create('creation_date', 'hidden') - ->addModelTransformer($dateToStringTransformer) - ) - ->add('form_status', 'hidden') + ->add('creation_date', 'hidden', array( + 'mapped' => false + )) + ->add('form_status', 'hidden', array( + 'mapped' => false + )) ->add('center', 'center') ; + $builder->get('birthdate') + ->addModelTransformer($dateToStringTransformer); + $builder->get('creation_date', 'hidden') + ->addModelTransformer($dateToStringTransformer); } else { $builder ->add('firstName') @@ -76,10 +80,16 @@ class CreationPersonType extends AbstractType ->add('gender', new GenderType(), array( 'required' => true, 'empty_value' => null )) - ->add('creation_date', 'date', array('required' => true, - 'widget' => 'single_text', 'format' => 'dd-MM-yyyy', + ->add('creation_date', 'date', array( + 'required' => true, + 'widget' => 'single_text', + 'format' => 'dd-MM-yyyy', + 'mapped' => false, 'data' => new \DateTime())) - ->add('form_status', 'hidden', array('data' => $this->form_status)) + ->add('form_status', 'hidden', array( + 'data' => $this->form_status, + 'mapped' => false + )) ->add('center', 'center') ; } @@ -94,9 +104,9 @@ class CreationPersonType extends AbstractType */ public function setDefaultOptions(OptionsResolverInterface $resolver) { -// $resolver->setDefaults(array( -// 'data_class' => 'Chill\PersonBundle\Entity\Person' -// )); + $resolver->setDefaults(array( + 'data_class' => 'Chill\PersonBundle\Entity\Person' + )); } /** From 9f079c17697e825237cfa443d572b2f948ac4e16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 10 Dec 2015 15:17:47 +0100 Subject: [PATCH 4/4] remove debug informations --- Controller/PersonController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Controller/PersonController.php b/Controller/PersonController.php index b70796d24..b5a52bb56 100644 --- a/Controller/PersonController.php +++ b/Controller/PersonController.php @@ -261,7 +261,7 @@ class PersonController extends Controller $form->handleRequest($request); $person = $this->_bindCreationForm($form); - var_dump($person); + $errors = $this->_validatePersonAndAccompanyingPeriod($person); $this->get('logger')->info(sprintf('Person created with %d errors ', count($errors)));