Skip to content

Instantly share code, notes, and snippets.

@havvg
Last active October 24, 2021 10:33
Show Gist options
  • Save havvg/4204307 to your computer and use it in GitHub Desktop.
Save havvg/4204307 to your computer and use it in GitHub Desktop.
Symfony2: poor man's date_range form type
<?php
namespace Ormigo\Bundle\OrmigoBundle\Form\Model;
use DateTime;
class DateRange
{
/**
* @var DateTime
*/
public $start;
/**
* @var DateTime
*/
public $end;
public function __construct(DateTime $start = null, DateTime $end = null)
{
if (!$start) {
$start = new DateTime();
$start->setTime(0, 0, 0);
}
if (!$end) {
$end = new DateTime();
$end->setTime(23, 59, 59);
}
$this->start = $start;
$this->end = $end;
}
}
<?php
namespace Ormigo\Bundle\OrmigoBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Ormigo\Bundle\OrmigoBundle\Form\DataTransformer\DateRangeViewTransformer;
use Ormigo\Bundle\OrmigoBundle\Form\Validator\DateRangeValidator;
class DateRangeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('start_date', 'date', array_merge_recursive(array(
'property_path' => 'start',
'widget' => 'single_text',
'format' => 'yyyy-MM-dd',
'model_timezone' => 'UTC',
'view_timezone' => 'UTC',
'attr' => array(
'data-type' => 'start',
),
), $options['start_options']))
->add('end_date', 'date', array_merge_recursive(array(
'property_path' => 'end',
'widget' => 'single_text',
'format' => 'yyyy-MM-dd',
'model_timezone' => 'UTC',
'view_timezone' => 'UTC',
'attr' => array(
'data-type' => 'end',
),
), $options['end_options']))
;
$builder->addViewTransformer($options['transformer']);
$builder->addEventSubscriber($options['validator']);
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Ormigo\Bundle\OrmigoBundle\Form\Model\DateRange',
'end_options' => array(),
'start_options' => array(),
'transformer' => null,
'validator' => null,
));
$resolver->setAllowedTypes(array(
'transformer' => 'Symfony\Component\Form\DataTransformerInterface',
'validator' => 'Symfony\Component\EventDispatcher\EventSubscriberInterface',
));
// Those normalizers lazily create the required objects, if none given.
$resolver->setNormalizers(array(
'transformer' => function (Options $options, $value) {
if (!$value) {
$value = new DateRangeViewTransformer(new OptionsResolver());
}
return $value;
},
'validator' => function (Options $options, $value) {
if (!$value) {
$value = new DateRangeValidator(new OptionsResolver());
}
return $value;
},
));
}
public function getName()
{
return 'date_range';
}
}
<?php
namespace Ormigo\Bundle\OrmigoBundle\Form\Validator;
use DateTime;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class DateRangeValidator implements EventSubscriberInterface
{
protected $options = array();
public function __construct(OptionsResolverInterface $resolver, array $options = array())
{
$this->setDefaultOptions($resolver);
$this->options = $resolver->resolve($options);
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'allow_end_in_past' => false,
'allow_single_day' => true,
));
$resolver->setAllowedValues(array(
'allow_end_in_past' => array(true, false),
'allow_single_day' => array(true, false),
));
}
public function onPostBind(FormEvent $event)
{
$form = $event->getForm();
/* @var $dateRange \Ormigo\Bundle\OrmigoBundle\Form\Model\DateRange */
$dateRange = $form->getNormData();
if ($dateRange->start > $dateRange->end) {
$form->addError(new FormError('date_range.invalid.end_before_start'));
}
if (!$this->options['allow_single_day'] and ($dateRange->start->format('Y-m-d') === $dateRange->end->format('Y-m-d'))) {
$form->addError(new FormError('date_range.invalid.single_day'));
}
if (!$this->options['allow_end_in_past'] and ($dateRange->end < new DateTime())) {
$form->addError(new FormError('date_range.invalid.end_in_past'));
}
}
public static function getSubscribedEvents()
{
return array(
FormEvents::POST_BIND => 'onPostBind',
);
}
}
<?php
namespace Ormigo\Bundle\OrmigoBundle\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Ormigo\Bundle\OrmigoBundle\Form\Model\DateRange;
class DateRangeViewTransformer implements DataTransformerInterface
{
protected $options = array();
public function __construct(OptionsResolverInterface $resolver, array $options = array())
{
$this->setDefaultOptions($resolver);
$this->options = $resolver->resolve($options);
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'include_end' => true,
));
$resolver->setAllowedValues(array(
'include_end' => array(true, false),
));
}
public function transform($value)
{
if (!$value) {
return null;
}
if (!$value instanceof DateRange) {
throw new UnexpectedTypeException($value, 'Ormigo\Bundle\OrmigoBundle\Form\Model\DateRange');
}
return $value;
}
public function reverseTransform($value)
{
if (!$value) {
return null;
}
if (!$value instanceof DateRange) {
throw new UnexpectedTypeException($value, 'Ormigo\Bundle\OrmigoBundle\Form\Model\DateRange');
}
if ($this->options['include_end']) {
$value->end->setTime(23, 59, 59);
}
return $value;
}
}
@havvg
Copy link
Author

havvg commented Mar 13, 2014

Just because this kind of form type is not packaged with the component :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment