Symfony Forms and Bootstrap Datetimepicker

In this example I’m going to use the spanish locale and the “Europe/Madrid” timezone.

First we need an entity that will hold the DateTime, for example a Booking entity:

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="bookings")
 */
class Booking
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @ORM\Column(type="datetime")
     */
    protected $date;

    /**
     * Constructor
     */
    public function __construct()
    {
        $this->date = new \DateTime();
    }

    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set date
     *
     * @param \Datetime $date
     * @return Booking
     */
    public function setDate($date)
    {
        $this->date = $date;

        return $this;
    }

    /**
     * Get date
     *
     * @return \Datetime 
     */
    public function getDate()
    {
        return $this->date;
    }
}

Our entity uses a DateTime object but the field in the form is going to be an input that can only have text. That’s why we need a DataTransformer. It will transform a DateTime object to string and vice versa when needed:

<?php

namespace AppBundle\Form\DataTransformer;

use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;

class DateTimeTransformer implements DataTransformerInterface
{
    /**
     * Transforms an object (DateTime) to a string.
     *
     * @param  DateTime|null $datetime
     * @return string
     */
    public function transform($datetime)
    {
        if (null === $datetime) {
            return '';
        }

        return $datetime->format('d/m/Y H:i');
    }

    /**
     * Transforms a string to an object (DateTime).
     *
     * @param  string $datetime
     * @return DateTime|null
     */
    public function reverseTransform($datetime)
    {
        // datetime optional
        if (!$datetime) {
            return;
        }

        return date_create_from_format('d/m/Y H:i', $datetime, new \DateTimeZone('Europe/Madrid'));
    }
}

Now the form class. You can see how addModelTransformer is used to link the form date field with the DataTransformer. When defining the date field it’s important to render it as a text field and use the attr option to add a data-provide and a data-format that will be added in the HTML input field as attributes. Also some CSS classes are needed (form-control input-inline datetimepicker), you can add them with attr here or later in the Twig template. I’ll show both but only one of them is needed.

<?php

namespace AppBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use AppBundle\Form\DataTransformer\DateTimeTransformer;

class BookingFormType extends AbstractType
{
    private $class;

    /**
     * @param string $class The Booking class name
     */
    public function __construct($class)
    {
        $this->class = $class;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('date', 'text', array(
                'required' => true,
                'label' => 'form.label.datetime',
                'translation_domain' => 'AppBundle',
                'attr' => array(
                    'class' => 'form-control input-inline datetimepicker',
                    'data-provide' => 'datepicker',
                    'data-format' => 'dd-mm-yyyy HH:ii',
                ),
            ))
            ->add('submit', 'submit')
        ;

        $builder->get('date')
            ->addModelTransformer(new DateTimeTransformer());
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(
            array(
                'data_class' => $this->class,
                'intention'  => 'edit',
            )
        );
    }

    public function getName()
    {
        return 'booking_edit';
    }
}

Let’s register the form as a service:

services:
    app.form.type.booking_edit:
        class: AppBundle\Form\Type\BookingFormType
        arguments: [AppBundle\Entity\Booking]
        tags:
            - { name: form.type, alias: booking_edit }

In the template we’ll include the form and the needed JavaScript and CSS for jQuery, MomentJS, Bootstrap and Bootstrap Datetimepicker. Remember that MomentJS must be loaded BEFORE Bootstrap Datetimepicker. Also just before the body closing tag we have to call the datetimepicker method on the input field. It’s very important to add “useCurrent: false” if you want to load a datetime as we do in this example.

<html>
    <head>
        <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
        <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css" rel="stylesheet">
        <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.37/css/bootstrap-datetimepicker-standalone.min.css" rel="stylesheet">
        <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.37/css/bootstrap-datetimepicker.min.css" rel="stylesheet">
        <title>Symfony and Bootstrap Datetimepicker</title>
    </head>
    <body>
        <div class="container">
            <div class="starter-template">
                <h1>Symfony and Bootstrap Datetimepicker</h1>
                <div class="row">
                    {{ form_start(form) }}
                        {{ form_errors(form) }}
                        <div class="form-group">
                            {{ form_label(form.date, null, { 'label_attr': {'class': 'col-sm-2 control-label'} }) }}{{ form_errors(form.date) }}
                            <div class='input-group date' id='datetimepicker'>
                                {{ form_widget(form.date, {'attr': {'class': 'form-control input-inline datetimepicker'}}) }}
                                <span class="input-group-addon">
                                    <span class="glyphicon glyphicon-calendar"></span>
                                </span>
                            </div>
                        </div>
                        <div class="form-group">
                            {{ form_row(form.submit, { 'label': 'OK' }) }}
                        </div>
                        {{ form_end(form) }}
                    </div>
                </div>
            </div>
        </div>

        <script src="https://code.jquery.com/jquery-1.11.3.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment-with-locales.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.37/js/bootstrap-datetimepicker.min.js"></script>
        <script type="text/javascript">
            $('#datetimepicker').datetimepicker({
                locale: 'es',
                useCurrent: false,
                sideBySide: true
            });
        </script>
    </body>
</html>

The controller is very simple (the handling of the form is not shown):

<?php

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use AppBundle\Entity\Booking;
use AppBundle\Form\Type\BookingFormType;

class MainController extends Controller
{
    /**
     * @Route("/booking/edit/{id}", name="booking_edit")
     * @ParamConverter("id", class="AppBundle:Booking")
     * @param Request $request The Request object
     * @param Booking $booking The Booking object
     * @return Response A Response instance
     */
    public function formAction(Request $request, Booking $booking)
    {
        $form = $this->createForm(new BookingFormType('\AppBundle\Entity\Booking'), $booking);

        $form->handleRequest($request);

        if ($form->isValid()) {
            ...
        }

        return $this->render('form.html.twig', array(
            'form' => $form->createView(),
        ));
    }
}

symfony-bootstrap-datetimepicker

Ref: https://eonasdan.github.io/bootstrap-datetimepicker/
http://ajaxray.com/blog/symfony2-forms-bootstrap-3-datepicker-for-date-field
http://symfony.com/doc/current/reference/forms/types/datetime.html
http://symfony.com/doc/current/cookbook/form/data_transformers.html

You might also like

Translate Symfony TimezoneType Field Type
The TimezoneType Field Type Symfony has an specific Field Type to create a Timezone select widget in...

Disable automatic locale variables setting in terminal emulator for Mac OS X
Terminal emulators usually send your current locale when conecting to an SSH server. I use Mac OS X in...

Configure the locale and keyboard layout in Raspbian
I have a keyboard with the spanish layout (with the "ñ" key, etc.) but Raspbian comes by default only...

Fix locale problems in Raspbian
If you get errors like this while running commands on your Raspbian:perl: warning: Setting locale...

  1. Hasse Ramlev Hansen

    Why dont you in AppBundle\Form\DataTransformer\DateTimeTransformer.php use the DateTime::format()?

    change
    return date(‘d/m/Y H:i’, $datetime->getTimestamp());

    to
    return $datetime->format(‘d/m/Y H:i’);

  2. I’ve seen several solutions for similar problem, but your’s is probably the cleanest and easiest to adapt to personal needs.

    Thanks!

  3. Great tuto. It’s helpful. But I notice that you have done a mistake here: public function reverseTransform($mydatetime) . You should write $datetime instead of $mydatetime.

    Best regards.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.