vendor/symfony/form/Extension/Core/Type/DateTimeType.php line 31

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\Form\Extension\Core\Type;
  11. use Symfony\Component\Form\AbstractType;
  12. use Symfony\Component\Form\Extension\Core\DataTransformer\ArrayToPartsTransformer;
  13. use Symfony\Component\Form\Extension\Core\DataTransformer\DataTransformerChain;
  14. use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToDateTimeTransformer;
  15. use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer;
  16. use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToHtml5LocalDateTimeTransformer;
  17. use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer;
  18. use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer;
  19. use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer;
  20. use Symfony\Component\Form\FormBuilderInterface;
  21. use Symfony\Component\Form\FormInterface;
  22. use Symfony\Component\Form\FormView;
  23. use Symfony\Component\Form\ReversedTransformer;
  24. use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
  25. use Symfony\Component\OptionsResolver\Options;
  26. use Symfony\Component\OptionsResolver\OptionsResolver;
  27. class DateTimeType extends AbstractType
  28. {
  29.     const DEFAULT_DATE_FORMAT = \IntlDateFormatter::MEDIUM;
  30.     const DEFAULT_TIME_FORMAT = \IntlDateFormatter::MEDIUM;
  31.     /**
  32.      * The HTML5 datetime-local format as defined in
  33.      * http://w3c.github.io/html-reference/datatypes.html#form.data.datetime-local.
  34.      */
  35.     const HTML5_FORMAT "yyyy-MM-dd'T'HH:mm:ss";
  36.     private static $acceptedFormats = [
  37.         \IntlDateFormatter::FULL,
  38.         \IntlDateFormatter::LONG,
  39.         \IntlDateFormatter::MEDIUM,
  40.         \IntlDateFormatter::SHORT,
  41.     ];
  42.     /**
  43.      * {@inheritdoc}
  44.      */
  45.     public function buildForm(FormBuilderInterface $builder, array $options)
  46.     {
  47.         $parts = ['year''month''day''hour'];
  48.         $dateParts = ['year''month''day'];
  49.         $timeParts = ['hour'];
  50.         if ($options['with_minutes']) {
  51.             $parts[] = 'minute';
  52.             $timeParts[] = 'minute';
  53.         }
  54.         if ($options['with_seconds']) {
  55.             $parts[] = 'second';
  56.             $timeParts[] = 'second';
  57.         }
  58.         $dateFormat = \is_int($options['date_format']) ? $options['date_format'] : self::DEFAULT_DATE_FORMAT;
  59.         $timeFormat self::DEFAULT_TIME_FORMAT;
  60.         $calendar = \IntlDateFormatter::GREGORIAN;
  61.         $pattern = \is_string($options['format']) ? $options['format'] : null;
  62.         if (!\in_array($dateFormatself::$acceptedFormatstrue)) {
  63.             throw new InvalidOptionsException('The "date_format" option must be one of the IntlDateFormatter constants (FULL, LONG, MEDIUM, SHORT) or a string representing a custom format.');
  64.         }
  65.         if ('single_text' === $options['widget']) {
  66.             if (self::HTML5_FORMAT === $pattern) {
  67.                 $builder->addViewTransformer(new DateTimeToHtml5LocalDateTimeTransformer(
  68.                     $options['model_timezone'],
  69.                     $options['view_timezone']
  70.                 ));
  71.             } else {
  72.                 $builder->addViewTransformer(new DateTimeToLocalizedStringTransformer(
  73.                     $options['model_timezone'],
  74.                     $options['view_timezone'],
  75.                     $dateFormat,
  76.                     $timeFormat,
  77.                     $calendar,
  78.                     $pattern
  79.                 ));
  80.             }
  81.         } else {
  82.             // when the form is compound the entries of the array are ignored in favor of children data
  83.             // so we need to handle the cascade setting here
  84.             $emptyData $builder->getEmptyData() ?: [];
  85.             // Only pass a subset of the options to children
  86.             $dateOptions array_intersect_key($optionsarray_flip([
  87.                 'years',
  88.                 'months',
  89.                 'days',
  90.                 'placeholder',
  91.                 'choice_translation_domain',
  92.                 'required',
  93.                 'translation_domain',
  94.                 'html5',
  95.                 'invalid_message',
  96.                 'invalid_message_parameters',
  97.             ]));
  98.             if (isset($emptyData['date'])) {
  99.                 $dateOptions['empty_data'] = $emptyData['date'];
  100.             }
  101.             $timeOptions array_intersect_key($optionsarray_flip([
  102.                 'hours',
  103.                 'minutes',
  104.                 'seconds',
  105.                 'with_minutes',
  106.                 'with_seconds',
  107.                 'placeholder',
  108.                 'choice_translation_domain',
  109.                 'required',
  110.                 'translation_domain',
  111.                 'html5',
  112.                 'invalid_message',
  113.                 'invalid_message_parameters',
  114.             ]));
  115.             if (isset($emptyData['time'])) {
  116.                 $timeOptions['empty_data'] = $emptyData['time'];
  117.             }
  118.             if (false === $options['label']) {
  119.                 $dateOptions['label'] = false;
  120.                 $timeOptions['label'] = false;
  121.             }
  122.             if (null !== $options['date_widget']) {
  123.                 $dateOptions['widget'] = $options['date_widget'];
  124.             }
  125.             if (null !== $options['date_label']) {
  126.                 $dateOptions['label'] = $options['date_label'];
  127.             }
  128.             if (null !== $options['time_widget']) {
  129.                 $timeOptions['widget'] = $options['time_widget'];
  130.             }
  131.             if (null !== $options['time_label']) {
  132.                 $timeOptions['label'] = $options['time_label'];
  133.             }
  134.             if (null !== $options['date_format']) {
  135.                 $dateOptions['format'] = $options['date_format'];
  136.             }
  137.             $dateOptions['input'] = $timeOptions['input'] = 'array';
  138.             $dateOptions['error_bubbling'] = $timeOptions['error_bubbling'] = true;
  139.             if (isset($dateOptions['format']) && DateType::HTML5_FORMAT !== $dateOptions['format']) {
  140.                 $dateOptions['html5'] = false;
  141.             }
  142.             $builder
  143.                 ->addViewTransformer(new DataTransformerChain([
  144.                     new DateTimeToArrayTransformer($options['model_timezone'], $options['view_timezone'], $parts),
  145.                     new ArrayToPartsTransformer([
  146.                         'date' => $dateParts,
  147.                         'time' => $timeParts,
  148.                     ]),
  149.                 ]))
  150.                 ->add('date'__NAMESPACE__.'\DateType'$dateOptions)
  151.                 ->add('time'__NAMESPACE__.'\TimeType'$timeOptions)
  152.             ;
  153.         }
  154.         if ('datetime_immutable' === $options['input']) {
  155.             $builder->addModelTransformer(new DateTimeImmutableToDateTimeTransformer());
  156.         } elseif ('string' === $options['input']) {
  157.             $builder->addModelTransformer(new ReversedTransformer(
  158.                 new DateTimeToStringTransformer($options['model_timezone'], $options['model_timezone'], $options['input_format'])
  159.             ));
  160.         } elseif ('timestamp' === $options['input']) {
  161.             $builder->addModelTransformer(new ReversedTransformer(
  162.                 new DateTimeToTimestampTransformer($options['model_timezone'], $options['model_timezone'])
  163.             ));
  164.         } elseif ('array' === $options['input']) {
  165.             $builder->addModelTransformer(new ReversedTransformer(
  166.                 new DateTimeToArrayTransformer($options['model_timezone'], $options['model_timezone'], $parts)
  167.             ));
  168.         }
  169.     }
  170.     /**
  171.      * {@inheritdoc}
  172.      */
  173.     public function buildView(FormView $viewFormInterface $form, array $options)
  174.     {
  175.         $view->vars['widget'] = $options['widget'];
  176.         // Change the input to a HTML5 datetime input if
  177.         //  * the widget is set to "single_text"
  178.         //  * the format matches the one expected by HTML5
  179.         //  * the html5 is set to true
  180.         if ($options['html5'] && 'single_text' === $options['widget'] && self::HTML5_FORMAT === $options['format']) {
  181.             $view->vars['type'] = 'datetime-local';
  182.         }
  183.     }
  184.     /**
  185.      * {@inheritdoc}
  186.      */
  187.     public function configureOptions(OptionsResolver $resolver)
  188.     {
  189.         $compound = function (Options $options) {
  190.             return 'single_text' !== $options['widget'];
  191.         };
  192.         // Defaults to the value of "widget"
  193.         $dateWidget = function (Options $options) {
  194.             return 'single_text' === $options['widget'] ? null $options['widget'];
  195.         };
  196.         // Defaults to the value of "widget"
  197.         $timeWidget = function (Options $options) {
  198.             return 'single_text' === $options['widget'] ? null $options['widget'];
  199.         };
  200.         $resolver->setDefaults([
  201.             'input' => 'datetime',
  202.             'model_timezone' => null,
  203.             'view_timezone' => null,
  204.             'format' => self::HTML5_FORMAT,
  205.             'date_format' => null,
  206.             'widget' => null,
  207.             'date_widget' => $dateWidget,
  208.             'time_widget' => $timeWidget,
  209.             'with_minutes' => true,
  210.             'with_seconds' => false,
  211.             'html5' => true,
  212.             // Don't modify \DateTime classes by reference, we treat
  213.             // them like immutable value objects
  214.             'by_reference' => false,
  215.             'error_bubbling' => false,
  216.             // If initialized with a \DateTime object, FormType initializes
  217.             // this option to "\DateTime". Since the internal, normalized
  218.             // representation is not \DateTime, but an array, we need to unset
  219.             // this option.
  220.             'data_class' => null,
  221.             'compound' => $compound,
  222.             'date_label' => null,
  223.             'time_label' => null,
  224.             'empty_data' => function (Options $options) {
  225.                 return $options['compound'] ? [] : '';
  226.             },
  227.             'input_format' => 'Y-m-d H:i:s',
  228.         ]);
  229.         // Don't add some defaults in order to preserve the defaults
  230.         // set in DateType and TimeType
  231.         $resolver->setDefined([
  232.             'placeholder',
  233.             'choice_translation_domain',
  234.             'years',
  235.             'months',
  236.             'days',
  237.             'hours',
  238.             'minutes',
  239.             'seconds',
  240.         ]);
  241.         $resolver->setAllowedValues('input', [
  242.             'datetime',
  243.             'datetime_immutable',
  244.             'string',
  245.             'timestamp',
  246.             'array',
  247.         ]);
  248.         $resolver->setAllowedValues('date_widget', [
  249.             null// inherit default from DateType
  250.             'single_text',
  251.             'text',
  252.             'choice',
  253.         ]);
  254.         $resolver->setAllowedValues('time_widget', [
  255.             null// inherit default from TimeType
  256.             'single_text',
  257.             'text',
  258.             'choice',
  259.         ]);
  260.         // This option will overwrite "date_widget" and "time_widget" options
  261.         $resolver->setAllowedValues('widget', [
  262.             null// default, don't overwrite options
  263.             'single_text',
  264.             'text',
  265.             'choice',
  266.         ]);
  267.         $resolver->setAllowedTypes('input_format''string');
  268.         $resolver->setDeprecated('date_format', function (Options $options$dateFormat) {
  269.             if (null !== $dateFormat && 'single_text' === $options['widget'] && self::HTML5_FORMAT === $options['format']) {
  270.                 return sprintf('Using the "date_format" option of %s with an HTML5 date widget is deprecated since Symfony 4.3 and will lead to an exception in 5.0.'self::class);
  271.                 //throw new LogicException(sprintf('Cannot use the "date_format" option of the %s with an HTML5 date.', self::class));
  272.             }
  273.             return '';
  274.         });
  275.         $resolver->setDeprecated('date_widget', function (Options $options$dateWidget) {
  276.             if (null !== $dateWidget && 'single_text' === $options['widget']) {
  277.                 return sprintf('Using the "date_widget" option of %s when the "widget" option is set to "single_text" is deprecated since Symfony 4.3 and will lead to an exception in 5.0.'self::class);
  278.                 //throw new LogicException(sprintf('Cannot use the "date_widget" option of the %s when the "widget" option is set to "single_text".', self::class));
  279.             }
  280.             return '';
  281.         });
  282.         $resolver->setDeprecated('time_widget', function (Options $options$timeWidget) {
  283.             if (null !== $timeWidget && 'single_text' === $options['widget']) {
  284.                 return sprintf('Using the "time_widget" option of %s when the "widget" option is set to "single_text" is deprecated since Symfony 4.3 and will lead to an exception in 5.0.'self::class);
  285.                 //throw new LogicException(sprintf('Cannot use the "time_widget" option of the %s when the "widget" option is set to "single_text".', self::class));
  286.             }
  287.             return '';
  288.         });
  289.         $resolver->setDeprecated('html5', function (Options $options$html5) {
  290.             if ($html5 && self::HTML5_FORMAT !== $options['format']) {
  291.                 return sprintf('Using a custom format when the "html5" option of %s is enabled is deprecated since Symfony 4.3 and will lead to an exception in 5.0.'self::class);
  292.                 //throw new LogicException(sprintf('Cannot use the "format" option of %s when the "html5" option is disabled.', self::class));
  293.             }
  294.             return '';
  295.         });
  296.     }
  297.     /**
  298.      * {@inheritdoc}
  299.      */
  300.     public function getBlockPrefix()
  301.     {
  302.         return 'datetime';
  303.     }
  304. }