Merge branch 'birthdate_constraint' into 'master'

Birthdate constraint

ref #4 

See merge request !3
This commit is contained in:
Julien Fastré 2015-12-11 14:55:48 +01:00
commit 0f022d4f95
11 changed files with 335 additions and 41 deletions

View File

@ -170,12 +170,12 @@ class PersonController extends Controller
->getGroupCenters()[0] ->getGroupCenters()[0]
->getCenter(); ->getCenter();
$person = (new Person()) $person = (new Person(new \DateTime('now')))
->setCenter($defaultCenter); ->setCenter($defaultCenter);
$form = $this->createForm( $form = $this->createForm(
new CreationPersonType(CreationPersonType::FORM_NOT_REVIEWED), new CreationPersonType(CreationPersonType::FORM_NOT_REVIEWED),
array('creation_date' => new \DateTime(), 'center' => $defaultCenter), $person,
array('action' => $this->generateUrl('chill_person_review')) array('action' => $this->generateUrl('chill_person_review'))
); );
@ -197,18 +197,22 @@ class PersonController extends Controller
*/ */
private function _bindCreationForm($form) private function _bindCreationForm($form)
{ {
$date = new \DateTime(); /**
$person = new Person($form['creation_date']->getData()); * @var Person
*/
$person = $form->getData();
$periods = $person->getAccompanyingPeriodsOrdered();
$date_of_birth = new \DateTime(); $period = $periods[0];
$period->setOpeningDate($form['creation_date']->getData());
$person->setFirstName($form['firstName']->getData()) // $person = new Person($form['creation_date']->getData());
->setLastName($form['lastName']->getData()) //
->setGender($form['gender']->getData()) // $person->setFirstName($form['firstName']->getData())
->setBirthdate($form['birthdate']->getData()) // ->setLastName($form['lastName']->getData())
->setCenter($form['center']->getData()) // ->setGender($form['gender']->getData())
; // ->setBirthdate($form['birthdate']->getData())
// ->setCenter($form['center']->getData())
// ;
return $person; return $person;
} }
@ -251,15 +255,18 @@ class PersonController extends Controller
$form = $this->createForm( $form = $this->createForm(
new CreationPersonType(CreationPersonType::FORM_BEING_REVIEWED), 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); $form->handleRequest($request);
$person = $this->_bindCreationForm($form); $person = $this->_bindCreationForm($form);
$errors = $this->_validatePersonAndAccompanyingPeriod($person); $errors = $this->_validatePersonAndAccompanyingPeriod($person);
$this->get('logger')->info(sprintf('Person created with %d errors ', count($errors)));
if ( count($errors) > 0) {
if ($errors->count() > 0) {
$this->get('logger')->info('The created person has errors');
$flashBag = $this->get('session')->getFlashBag(); $flashBag = $this->get('session')->getFlashBag();
$translator = $this->get('translator'); $translator = $this->get('translator');
@ -271,11 +278,14 @@ class PersonController extends Controller
$form = $this->createForm( $form = $this->createForm(
new CreationPersonType(CreationPersonType::FORM_NOT_REVIEWED), 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); $form->handleRequest($request);
return $this->_renderNewForm($form); return $this->_renderNewForm($form);
} else {
$this->get('logger')->info('Person created without errors');
} }
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
@ -331,7 +341,7 @@ class PersonController extends Controller
$form = $this->createForm(new CreationPersonType()); $form = $this->createForm(new CreationPersonType());
$form->handleRequest($request); $form->handleRequest($request);
var_dump($form->getData());
$person = $this->_bindCreationForm($form); $person = $this->_bindCreationForm($form);
$errors = $this->_validatePersonAndAccompanyingPeriod($person); $errors = $this->_validatePersonAndAccompanyingPeriod($person);
@ -349,7 +359,11 @@ class PersonController extends Controller
return $this->redirect($this->generateUrl('chill_person_general_edit', return $this->redirect($this->generateUrl('chill_person_general_edit',
array('person_id' => $person->getId()))); array('person_id' => $person->getId())));
} else { } 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); $r->setStatusCode(400);
return $r; return $r;
} }

View File

@ -24,8 +24,13 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
$configuration = new Configuration(); $configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs); $config = $this->processConfiguration($configuration, $configs);
// set configuration for double metaphone
$container->setParameter('cl_chill_person.search.use_double_metaphone', $container->setParameter('cl_chill_person.search.use_double_metaphone',
$config['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 = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml'); $loader->load('services.yml');

View File

@ -12,6 +12,10 @@ use Symfony\Component\Config\Definition\ConfigurationInterface;
*/ */
class Configuration implements 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} * {@inheritDoc}
*/ */
@ -29,13 +33,34 @@ class Configuration implements ConfigurationInterface
->booleanNode('use_double_metaphone') ->booleanNode('use_double_metaphone')
->defaultFalse() ->defaultFalse()
->end() ->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; return $treeBuilder;
} }
} }

View File

@ -24,9 +24,7 @@ namespace Chill\PersonBundle\Form;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Chill\PersonBundle\Form\Type\CivilType;
use Chill\PersonBundle\Form\Type\GenderType; use Chill\PersonBundle\Form\Type\GenderType;
use CL\BelgianNationalNumberBundle\Form\BelgianNationalNumberType;
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer;
class CreationPersonType extends AbstractType class CreationPersonType extends AbstractType
@ -53,20 +51,26 @@ class CreationPersonType extends AbstractType
if ($this->form_status === self::FORM_BEING_REVIEWED) { if ($this->form_status === self::FORM_BEING_REVIEWED) {
$dateToStringTransformer = new DateTimeToStringTransformer( $dateToStringTransformer = new DateTimeToStringTransformer(
null, null, 'dd-MM-yyyy', true); null, null, 'd-m-Y', false);
$builder->add('firstName', 'hidden') $builder->add('firstName', 'hidden')
->add('lastName', 'hidden') ->add('lastName', 'hidden')
->add( $builder->create('birthdate', 'hidden') ->add('birthdate', 'hidden', array(
->addModelTransformer($dateToStringTransformer) 'property_path' => 'birthdate'
) ))
->add('gender', 'hidden') ->add('gender', 'hidden')
->add( $builder->create('creation_date', 'hidden') ->add('creation_date', 'hidden', array(
->addModelTransformer($dateToStringTransformer) 'mapped' => false
) ))
->add('form_status', 'hidden') ->add('form_status', 'hidden', array(
'mapped' => false
))
->add('center', 'center') ->add('center', 'center')
; ;
$builder->get('birthdate')
->addModelTransformer($dateToStringTransformer);
$builder->get('creation_date', 'hidden')
->addModelTransformer($dateToStringTransformer);
} else { } else {
$builder $builder
->add('firstName') ->add('firstName')
@ -76,10 +80,16 @@ class CreationPersonType extends AbstractType
->add('gender', new GenderType(), array( ->add('gender', new GenderType(), array(
'required' => true, 'empty_value' => null 'required' => true, 'empty_value' => null
)) ))
->add('creation_date', 'date', array('required' => true, ->add('creation_date', 'date', array(
'widget' => 'single_text', 'format' => 'dd-MM-yyyy', 'required' => true,
'widget' => 'single_text',
'format' => 'dd-MM-yyyy',
'mapped' => false,
'data' => new \DateTime())) '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') ->add('center', 'center')
; ;
} }
@ -94,9 +104,9 @@ class CreationPersonType extends AbstractType
*/ */
public function setDefaultOptions(OptionsResolverInterface $resolver) public function setDefaultOptions(OptionsResolverInterface $resolver)
{ {
// $resolver->setDefaults(array( $resolver->setDefaults(array(
// 'data_class' => 'Chill\PersonBundle\Entity\Person' 'data_class' => 'Chill\PersonBundle\Entity\Person'
// )); ));
} }
/** /**

View File

@ -48,3 +48,11 @@ services:
tags: tags:
- { name: security.voter } - { name: security.voter }
- { name: chill.role } - { 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 }

View File

@ -22,6 +22,8 @@ Chill\PersonBundle\Entity\Person:
- Date: - Date:
message: 'Birthdate not valid' message: 'Birthdate not valid'
groups: [general, creation] groups: [general, creation]
- Chill\PersonBundle\Validator\Constraints\Birthdate:
groups: [general, creation]
gender: gender:
- NotNull: - NotNull:
groups: [general, creation] groups: [general, creation]

View File

@ -11,3 +11,4 @@
'Closing date is not valid': 'La date de fermeture n''est pas valide' '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' '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 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%

View File

@ -38,7 +38,7 @@ class PersonControllerCreateTest extends WebTestCase
const CREATEDATE_INPUT = "chill_personbundle_person_creation[creation_date]"; const CREATEDATE_INPUT = "chill_personbundle_person_creation[creation_date]";
const CENTER_INPUT = "chill_personbundle_person_creation[center]"; 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 * return an authenticated client, useful for submitting form

View File

@ -0,0 +1,112 @@
<?php
/*
* Copyright (C) 2015 Julien Fastré <julien.fastre@champs-libres.coop>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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é <julien.fastre@champs-libres.coop>
*/
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();
}
}

View File

@ -0,0 +1,42 @@
<?php
/*
* Copyright (C) 2015 Julien Fastré <julien.fastre@champs-libres.coop>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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é <julien.fastre@champs-libres.coop>
*/
class Birthdate extends Constraint
{
public $message = "The birthdate must be before %date%";
public function validatedBy()
{
return 'birthdate_not_before';
}
}

View File

@ -0,0 +1,75 @@
<?php
/*
* Copyright (C) 2015 Julien Fastré <julien.fastre@champs-libres.coop>
*
* 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 <http://www.gnu.org/licenses/>.
*/
namespace Chill\PersonBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
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 === 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)));
}
$limitDate = $this->getLimitDate();
if ($limitDate < $value) {
$this->context->buildViolation($constraint->message)
->setParameter('%date%', $limitDate->format('d-m-Y'))
->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'));
}
}
}