Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Date Format & "Sanity" Validation

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

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:

... 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:

... 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:

... 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!

14
Login or Register to join the conversation
Default user avatar
Default user avatar Maksym Minenko | posted 5 years ago

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

Reply

Yo Maksym,

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

Cheers!

Reply
Default user avatar
Default user avatar Maksym Minenko | Victor | posted 5 years ago

So much better! Thank you.

Reply
Default user avatar

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?

Reply

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!

Reply
Default user avatar
Default user avatar Mike | weaverryan | posted 5 years ago | edited

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">
      <span class="input-group-addon"><i class="fa fa-calendar"></i></span>
       {{ 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

Reply
Default user avatar

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.

1 Reply

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!

2 Reply
Default user avatar
Default user avatar Vkgroup | posted 5 years ago

I have set 'widget' => 'single_text', 'format' => 'dd/MM/yyyy' and it won't work. I need to use spanish format for single_text (html5 false, jquery datapicker is configured to dateFormat: 'dd/mm/yy'). It works, if I set both config to expected Symfony format, but I need to provide users dd/MM/yyyy format. Thanks in advance.

Reply

Hey Vkgroup!

Hmm. So, you are setting Symfony's format to dd/MM/yyyy and jQuery's date picker format also to dd/MM/yyyy. I think I might know the problem. I believe Symfony's date field and jQuery's date picker use different "ways" to describe the correct format. See the red warning box at the bottom of this section: http://symfony.com/doc/current/reference/forms/types/date.html#rendering-a-single-html5-textbox (it starts with "The string used by a JavaScript date picker...").

For jQuery's date picker, you may need to use yyyy/mm/dd... or something similar.

Let me know if that helps! Cheers!

Reply
Default user avatar

Hi,

I'm following this tutorial. I added :

$('.js-datepicker').datepicker();

as was mentioned in the tutorial, entered Genus data in the form, selected date and hit save. I was expecting to see a validation error like the tutorial but I got a *bigggg* explosion:

Warning: IntlDateFormatter::parse(): Date parsing failed

500 Internal Server Error - ContextErrorException

in vendor\symfony\symfony\src\Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer.php at line 123

// to DST changes

$dateOnly = $this->isPatternDateOnly();

$timestamp = $this->getIntlDateFormatter($dateOnly)->parse($value);

if (intl_get_error_code() != 0) {

throw new TransformationFailedException(intl_get_error_message());

any idea what's going on?
thanks

Reply
Victor Avatar Victor | SFCASTS | Mehdi | posted 5 years ago | edited

Hey Mehdi,

Hm, looks like Symfony can't parse the date you passed. Try to specify the next format when initializing datepicker:


            $('.js-datepicker').datepicker({
                format: 'yyyy-mm-dd'
            });

Does it work? The problem could be in Intl version, I suppose you're on Windows, right? I also wonder what Symfony version do you use? I think as much as you can do here is to update your application to the latest Symfony version. It should probably fix the problem. But maybe not, then probably you need also to upgrade Intl on your computer, but it's difficult to do on Windows as I know. So probably, the easiest way is to pass the correct date format.

Cheers!

Reply
Default user avatar

i dont have the good DateType file my is almost empty

Reply
Victor Avatar Victor | SFCASTS | joram | posted 5 years ago | edited

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!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.1.*", // v3.1.4
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.6.4
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.3.11
        "symfony/monolog-bundle": "^2.8", // 2.11.1
        "symfony/polyfill-apcu": "^1.0", // v1.2.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.4", // 1.4.2
        "doctrine/doctrine-migrations-bundle": "^1.1" // 1.1.1
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.0.7
        "symfony/phpunit-bridge": "^3.0", // v3.1.3
        "nelmio/alice": "^2.1", // 2.1.4
        "doctrine/doctrine-fixtures-bundle": "^2.3" // 2.3.0
    }
}
userVoice