Create custom Month/Year form filed in Symfony Forms

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');
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *

Proudly powered by WordPress | Theme: Code Blog by Crimson Themes.