To create a custom form field in Symfony 5 for DateTime data type that only shows the month and year, you can create a custom form type and a custom widget to manage this. Here’s how you can achieve it:
Create the Custom Form Type
First, we will need create a custom form type that uses only the month and year fields. We will make it with configurable option with definition of which years range to show.
// src/Form/MonthYearType.php
<?php
namespace App\Form;
use App\Form\Form\DataTransformer\MonthYearToDateTransformer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\OptionsResolver\OptionsResolver;
class MonthYearType extends AbstractType
{
public function __construct(private RequestStack $requestStack)
{
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('month', ChoiceType::class, [
'choices' => $this->getMonthChoices(),
'placeholder' => 'Monat auswählen',
'label' => false,
])
->add('year', ChoiceType::class, [
'choices' => $this->getYearChoices($options['years_range']),
'placeholder' => 'Jahr auswählen',
'label' => false,
])
->addModelTransformer(new MonthYearToDateTransformer());
}
private function getMonthChoices(): array
{
$locale = $this->requestStack->getCurrentRequest()->getLocale();
$formatter = new \IntlDateFormatter(
$locale,
\IntlDateFormatter::NONE,
\IntlDateFormatter::NONE,
\date_default_timezone_get(),
\IntlDateFormatter::GREGORIAN,
'MMMM'
);
for($i = 1; $i <= 12; $i++) {
$months[$formatter->format(mktime(0, 0, 0, $i, 1))] = $i;
}
return $months;
}
private function getYearChoices(array $yearsRange): array
{
$years = [];
for ($i = $yearsRange[1]; $i >= $yearsRange[0]; $i--) {
$years[$i] = $i;
}
return $years;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'years_range' => [date('Y') - 40, date('Y')],
'data_class' => null,
]);
}
}
Customize the Form Widget
Now we will need to customize how the fields are rendered in the form.
In order to do this, first, we will create twig template:
{# templates/form/fields/month_year_widget.html.twig #}
{%- block month_year_widget -%}
<div {{ block('widget_container_attributes') -}}>
<div class="input-group">
{{ form_widget(form.month) }}
{{ form_widget(form.year) }}
</div>
</div>
{%- endblock month_year_widget -%}
And then add this template to the form themes:
# config/packages/twig.yaml
twig:
form_themes:
- 'form/fields/month_year_widget.html.twig'
Use Our Custom Mont and Year Form Type in a Form
Now that we’ve created the custom form type, we can use it in any of your forms as follows:
use App\Form\Type\MonthYearType;
...
.....
$builder->add('dateField', MonthYearType::class, [
'years_range' => [2000, 2030],
]);
Create the Data Transformer
In order to let our custom field work with \DateTimeInterface data type, we’ll have to add data transformer.
// src/Form/Form/DataTransformer/MonthYearToDateTransformer.php
<?php
namespace App\Form\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
class MonthYearToDateTransformer implements DataTransformerInterface
{
public function transform($value)
{
if ($value instanceof \DateTimeInterface) {
return [
'month' => $value->format('n'),
'year' => $value->format('Y'),
];
}
return null;
}
public function reverseTransform($value)
{
if (!is_array($value) || !array_key_exists('month', $value) || !array_key_exists('year', $value)) {
return null;
}
return new \DateTimeImmutable($value['year'] . '-' . $value['month'] . '-01');
}
}