Buy

Date Format & "Sanity" Validation

Let's use the fancy new date picker. Fill in the other fields and hit submit.

Whoa! Validation Error!

First, the good news: the error looks nice! And we didn't do any work for that.

Now, the bad news: do you remember adding any validation? Because I don't! So, where is that coming from?

The Format of the Date Field

What you're seeing is a really amazing automatic thing that Symfony does. And I invented a term for it: "sanity validation".

Remember, even though this has a fancy date picker widget, this is just a text field, and it needs to be filled in with a very specific format. In this case, the widget fills in month/day/year.

But what if Symfony expects a different format - like year/month/day?

Well, that's exactly what's happening: Symfony expects the date in one format, but the widget is generating something else. When Symfony fails to parse the date, it adds the validation error.

Sanity Validation

So, it turns out that many fields have sanity validation. It basically makes sure that a sane value is submitted by the user. If an insane value is sent, it blocks the way and shows a message.

For example, the speciesCount is using Symfony's NumberType, which renders as an HTML5 field with up and down arrows on some browsers:

139 lines src/AppBundle/Entity/Genus.php
... lines 1 - 11
class Genus
{
... lines 14 - 31
/**
* @ORM\Column(type="integer")
*/
private $speciesCount;
... lines 36 - 137
}

If we tried to type a word here and submit, the NumberType would throw a validation error thanks to sanity validation.

Our drop-down fields also have sanity validation. If a jerk user tries to hack the system by adding an option that we did not originally include, the field will fail validation. 99% of the time, you don't know or care that this is happening. Just know, Symfony has your back.

Making the Date Formats Match

But how do we fix the date field? We just need to make Symfony's expected format match the format used by the datepicker. In fact, the format itself is an option on the DateType. I'll hold command and click into DateType. When you use the single_text widget, it expects this HTML5_FORMAT: so year-month-day.

Let's update the JavaScript widget to match this.

How? On its docs, you'll see that it also has a format option. Cool!

Now, unfortunately, the format string used by the DateType and the format string used by the datepicker widget are not exactly the same format - each has its own system, unfortunately. So, you may need to do some digging or trial and error. It turns out, the correct format is yyyy-mm-dd:

37 lines app/Resources/views/admin/genus/new.html.twig
... lines 1 - 8
{% block javascripts %}
... lines 10 - 13
<script>
jQuery(document).ready(function() {
$('.js-datepicker').datepicker({
format: 'yyyy-mm-dd'
});
});
</script>
{% endblock %}
... lines 22 - 37

OK, go back and refresh that page. Fill out the top fields... and then select a date. Moment of truth. Got it!

Data Transformers

So I keep telling you that the purpose of the field "types" is to control how a field is rendered. But that's only half of it. Behind the scenes, many fields have a "data transformer".

Basically, the job of a data transformer is to transform the data that's inside of your PHP code to a format that's visible to your user. For example, the firstDiscoveredAt value on Genus is actually a DateTime object:

139 lines src/AppBundle/Entity/Genus.php
... lines 1 - 11
class Genus
{
... lines 14 - 46
/**
* @ORM\Column(type="date")
*/
private $firstDiscoveredAt;
... lines 51 - 137
}

The data transformer internally changes that into the string that's printed in the box.

Then, when a date string is submitted, that same data transformer does its work in reverse: changing the string back into a DateTime object.

The data transformer is also kicking butt on the subFamily field. The id of the selected SubFamily is submitted. Then, the data transformer uses that to query for a SubFamily object and set that on Genus.

You don't need to know more about data transformers right now, I just want you to realize that this awesomeness is happening.

Leave a comment!

  • 2016-12-02 weaverryan

    Good detective work Mike! The disabled thing is by default (but I could see how that could be a gotcha) - so that if you have a disabled field that already has a value, a "clever" user can't just modify it in the HTML and submit. I believe this is based on some official spec somewhere (that disabled input should be ignored, but readonly shouldn't), but I can't remember for sure :).

    Glad you sorted it out!

  • 2016-12-01 Mike

    It turns out that if you set your input field to disabled it will return a NULL value. If you use readonly instead it will work just fine. I did not include the disabled bit in my code above, but that is how I had my form originally. I was also using a datepicker that did not include time so I needed to use the bootstrap datetimepicker to resolve that. In the end I decided to use readonly and add today's date by default.

  • 2016-12-01 Mike

    Thanks for getting back to me.

    My @ORM\Colum is type="datetime". Take a look below at my setup:
    Entity:


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

    Controller:


    $form = $this->createForm(JobTitleFormType::class);
    $form->handleRequest($request);
    if ($form->isSubmitted() && $form->isValid()) {
    $jobTitle = $form->getData();

    $em = $this->getDoctrine()->getManager();
    $em->persist($jobTitle);
    $em->flush();

    $this->addFlash('success', 'Job Title "' . $form->get("jobTitle")->getData(). '" has been added.');

    //return new Response($email);
    return $this->redirectToRoute('setup_jobtitles');
    }

    FormType:


    ->add('createdOn', DateTimeType::class, [
    'widget' => 'single_text',
    'html5' => false,
    ]);

    Twig:


    <div class="form-group" id="jsDatepicker">
    {{ form_label(jobTitleForm.createdOn) }}
    <div class="input-group date">

    {{ form_widget(jobTitleForm.createdOn, { 'attr': {'class': 'form-control' } } ) }}
    </div>
    </div>

    When I submit the form it will add it to the DB like this:
    2016-11-30 00:00:00

  • 2016-11-30 weaverryan

    Yo Mike!

    Hmm, is your @ORM\Column a type="date" or type="datetime"? If it's "date", change it to "datetime": our form is correctly submitting a DateTime with time, but then Doctrine is saving only the date part.

    I'm not sure why the DateTimeType on the form would be returning null, however. If the above doesn't give you a hint, feel free to post some code (the important part if your entity, form, controller) and we'll see if we can spot the issue :).

    Cheers!

  • 2016-11-30 Mike

    Hi,

    I have a similar setup where I have a value that is a DateTime object (your screen shot shows @ORM\Column(type="date")). I got everything to work, but when I submit the form the date is being added without the time. I have tried using the DateTime form type, but it returns NULL when I submit the form. Any ideas?

  • 2016-10-03 Maksym Minenko

    So much better! Thank you.

  • 2016-10-03 Victor Bocharsky

    Yo Maksym,

    Yes, you can customize it! Check out the "format" option of Date form type in docs.

    Cheers!

  • 2016-10-03 Maksym Minenko

    Is there a way to change the Symfony's date format, without showing the ugly "2016-10-10" to the user?

  • 2016-09-27 Victor Bocharsky

    Hey Joram,

    Most probably you opened a wrong file. Please, double check the path - it should be: ./vendor/symfony/symfony/src/Symfony/Component/Form/Extension/Core/Type/DateType.php . Also, the namespace of this file should be Symfony\Component\Form\Extension\Core\Type .

    Cheers!

  • 2016-09-27 joram

    i dont have the good DateType file my is almost empty