Dates - Not Just A Delicious Fruit
In this video we are going to take a look at Symfony's DateType
form field type, which will act as a foundation for understanding the DateTimeType
in the next video.
Being a form field that specialises in dates, you can imagine that there are a variety of options to configure the days, months, and years.
Each of these options have usable defaults. These would be:
days
1-31months
listing all twelve months in text formatyears
shows 5 years before, and 5 years after the current year
These are customisable by passing in an array as needed, e.g.:
use Symfony\Component\Form\Extension\Core\Type\DateType;
$builder->add('yourDateField', DateType::class, [
'widget' => 'choice',
'years' => [2011,2014,2017,2023]
]);
And often you will see the use of PHP's range
function used with these options, e.g.:
use Symfony\Component\Form\Extension\Core\Type\DateType;
$builder->add('yourDateField', DateType::class, [
'widget' => 'choice',
'years' => range(2000,2030)
]);
This being short hand for creating an array containing every number between 2000
and 2030
without having to manually type them all out.
The thing is, there's a few not-so-immediately-obvious caveats to using the year
, month
, and day
options.
Firstly, they only apply if you use the widget
type of choice
. This is to say that they have no bearing on validation or stopping any other year being submitted as has been demonstrated in other videos in this series. So be careful of that.
The other problem is that if you had a situation where you only wanted to show the days after a certain day - for example, let's say we are on the 15th of the current month. We write some 'clever' bit of PHP like:
$now = (new \DateTime('now'))->format('d');
// 't' gives the last day in the current month
$endOfTheCurrentMonth = (new \DateTime('now'))->format('t');
$builder->add('yourDateField', DateType::class, [
'widget' => 'choice',
'days' => range($now, $endOfTheCurrentMonth),
]);
Then yes, we get an array of days going from 15-31 (assuming a 31 day month), but then all the months in our drop downs can only be given a date starting 15th or greater. D'oh.
So you end up doing some JavaScript / clientside tomfoolery to hack around this problem.
Validating Dates
If you want ensure only certain dates are valid - maybe any date that is greater than or equal to 'today', then you will likely want to add in some validation / assertions to your entities.
Unusually you wouldn't use a date assertion for this, but rather a 'comparison' constraint:
// src/AppBundle/Entity/Order.php
namespace AppBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Order
{
/**
* @Assert\GreaterThanOrEqual("today")
*/
protected $deliveryDate;
}
Note that this example is taken directly from the Symfony documentation.
Standard And Custom Date Formats
There are three configurable ways of accepting a date when using the DateType
, these are:
choice
text
single_text
With choice
being the default. You can change which input method you want using the widget
option.
use Symfony\Component\Form\Extension\Core\Type\DateType;
$builder->add('yourDateField', DateType::class, [
'widget' => 'choice',
]);
In my experience, the choice
and single_text
options are the most common. The immediate question I had was why is there text
and single_text
? Seems a little redundant.
Well, setting your widget
option to text
is going to render out three individual text boxes for input - one for day, one for month, and one for year.
The main reason I don't like this - aside from the default formatting - is that it's not immediately obvious to the end user how to use these fields. Do they need to enter the day name, the day number, should it have a leading zero, is it month name or number... two or four digit year? So many questions, which inevitably leads to confusion and as they say in ecommerce-land, form abandonment.
Instead, I would primarily opt for the choice
type, which thankfully is the default.
The choice
type renders out three preconfigured dropdowns with the months, days, and years preconfigured. There's still some room for error - as in selecting February 31st is entirely possible.
Thankfully, as mentioned above, there are validation rules around this, and you could add in some JavaScript to handle this client side in the first instance also. But that's all extra work on your part.
Then there is single_text
, which renders out a single text input
field which allows you to be as customisable as you like.
One cool thing about single_text
is that you will get a HTML5 date picker thrown in for free.
This may very well be 'good enough' that you do not need a third party library / plugin / extra dependency.
However, this field is also useful if you do want to use your own custom library for your date picker. To do so, you just need to turn off the HTML5 date picker by setting html5
to false
:
use Symfony\Component\Form\Extension\Core\Type\DateType;
$builder->add('yourDateField', DateType::class, [
'widget' => 'single_text',
'html5' => false,
]);
You may also need to pass in a custom format
for accepting dates. This is less likely with the DateType
than it is with the DateTimeType
that we will see in the next video, but be aware of this.
Once you pass in a custom format
option you can accept any kind of date you like. This is by far the most useful, but also the most tricky to get working.
Imagine for some reason that you needed to set up the date in the format of 'day/Month Year', which is going to confuse the heck out of Symfony unless we help it along:
use Symfony\Component\Form\Extension\Core\Type\DateType;
$builder->add('yourDateField', DateType::class, [
'widget' => 'single_text',
'html5' => false,
'format' => 'dd/MM yyyy',
]);
Honestly, I found the format
option amongst the most confusing, head-scratching part of working with the DateType
and especially the DateTimeType
. There is some method to the madness, but it's really not helped by PHP.net's really beginner-unfriendly documentation.
Some more examples to help you along:
// easiest:
'format' => 'dd/MM/yyyy HH:mm:ss'
// JavaScript ISO8601 date friendly
'format' => 'yyyy-MM-dd'T'HH:mm:ss.SSSZ',
Given these, figuring out your own should be much easier than going purely off the documentation. At least I hope it will be :)