vendor/symfony/symfony/src/Symfony/Component/Form/Extension/Core/Type/DateType.php line 27

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\FormInterface;
  13. use Symfony\Component\Form\FormBuilderInterface;
  14. use Symfony\Component\Form\FormView;
  15. use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer;
  16. use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer;
  17. use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer;
  18. use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer;
  19. use Symfony\Component\Form\ReversedTransformer;
  20. use Symfony\Component\OptionsResolver\Options;
  21. use Symfony\Component\OptionsResolver\OptionsResolver;
  22. use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
  23. class DateType extends AbstractType
  24. {
  25.     const DEFAULT_FORMAT = \IntlDateFormatter::MEDIUM;
  26.     const HTML5_FORMAT 'yyyy-MM-dd';
  27.     private static $acceptedFormats = array(
  28.         \IntlDateFormatter::FULL,
  29.         \IntlDateFormatter::LONG,
  30.         \IntlDateFormatter::MEDIUM,
  31.         \IntlDateFormatter::SHORT,
  32.     );
  33.     private static $widgets = array(
  34.         'text' => 'Symfony\Component\Form\Extension\Core\Type\TextType',
  35.         'choice' => 'Symfony\Component\Form\Extension\Core\Type\ChoiceType',
  36.     );
  37.     /**
  38.      * {@inheritdoc}
  39.      */
  40.     public function buildForm(FormBuilderInterface $builder, array $options)
  41.     {
  42.         $dateFormat is_int($options['format']) ? $options['format'] : self::DEFAULT_FORMAT;
  43.         $timeFormat = \IntlDateFormatter::NONE;
  44.         $calendar = \IntlDateFormatter::GREGORIAN;
  45.         $pattern is_string($options['format']) ? $options['format'] : null;
  46.         if (!in_array($dateFormatself::$acceptedFormatstrue)) {
  47.             throw new InvalidOptionsException('The "format" option must be one of the IntlDateFormatter constants (FULL, LONG, MEDIUM, SHORT) or a string representing a custom format.');
  48.         }
  49.         if ('single_text' === $options['widget']) {
  50.             if (null !== $pattern && false === strpos($pattern'y') && false === strpos($pattern'M') && false === strpos($pattern'd')) {
  51.                 throw new InvalidOptionsException(sprintf('The "format" option should contain the letters "y", "M" or "d". Its current value is "%s".'$pattern));
  52.             }
  53.             $builder->addViewTransformer(new DateTimeToLocalizedStringTransformer(
  54.                 $options['model_timezone'],
  55.                 $options['view_timezone'],
  56.                 $dateFormat,
  57.                 $timeFormat,
  58.                 $calendar,
  59.                 $pattern
  60.             ));
  61.         } else {
  62.             if (null !== $pattern && (false === strpos($pattern'y') || false === strpos($pattern'M') || false === strpos($pattern'd'))) {
  63.                 throw new InvalidOptionsException(sprintf('The "format" option should contain the letters "y", "M" and "d". Its current value is "%s".'$pattern));
  64.             }
  65.             $yearOptions $monthOptions $dayOptions = array(
  66.                 'error_bubbling' => true,
  67.             );
  68.             $formatter = new \IntlDateFormatter(
  69.                 \Locale::getDefault(),
  70.                 $dateFormat,
  71.                 $timeFormat,
  72.                 // see https://bugs.php.net/bug.php?id=66323
  73.                 class_exists('IntlTimeZone'false) ? \IntlTimeZone::createDefault() : null,
  74.                 $calendar,
  75.                 $pattern
  76.             );
  77.             // new \IntlDateFormatter may return null instead of false in case of failure, see https://bugs.php.net/bug.php?id=66323
  78.             if (!$formatter) {
  79.                 throw new InvalidOptionsException(intl_get_error_message(), intl_get_error_code());
  80.             }
  81.             $formatter->setLenient(false);
  82.             if ('choice' === $options['widget']) {
  83.                 // Only pass a subset of the options to children
  84.                 $yearOptions['choices'] = $this->formatTimestamps($formatter'/y+/'$this->listYears($options['years']));
  85.                 $yearOptions['placeholder'] = $options['placeholder']['year'];
  86.                 $yearOptions['choice_translation_domain'] = $options['choice_translation_domain']['year'];
  87.                 $monthOptions['choices'] = $this->formatTimestamps($formatter'/[M|L]+/'$this->listMonths($options['months']));
  88.                 $monthOptions['placeholder'] = $options['placeholder']['month'];
  89.                 $monthOptions['choice_translation_domain'] = $options['choice_translation_domain']['month'];
  90.                 $dayOptions['choices'] = $this->formatTimestamps($formatter'/d+/'$this->listDays($options['days']));
  91.                 $dayOptions['placeholder'] = $options['placeholder']['day'];
  92.                 $dayOptions['choice_translation_domain'] = $options['choice_translation_domain']['day'];
  93.             }
  94.             // Append generic carry-along options
  95.             foreach (array('required''translation_domain') as $passOpt) {
  96.                 $yearOptions[$passOpt] = $monthOptions[$passOpt] = $dayOptions[$passOpt] = $options[$passOpt];
  97.             }
  98.             $builder
  99.                 ->add('year'self::$widgets[$options['widget']], $yearOptions)
  100.                 ->add('month'self::$widgets[$options['widget']], $monthOptions)
  101.                 ->add('day'self::$widgets[$options['widget']], $dayOptions)
  102.                 ->addViewTransformer(new DateTimeToArrayTransformer(
  103.                     $options['model_timezone'], $options['view_timezone'], array('year''month''day')
  104.                 ))
  105.                 ->setAttribute('formatter'$formatter)
  106.             ;
  107.         }
  108.         if ('string' === $options['input']) {
  109.             $builder->addModelTransformer(new ReversedTransformer(
  110.                 new DateTimeToStringTransformer($options['model_timezone'], $options['model_timezone'], 'Y-m-d')
  111.             ));
  112.         } elseif ('timestamp' === $options['input']) {
  113.             $builder->addModelTransformer(new ReversedTransformer(
  114.                 new DateTimeToTimestampTransformer($options['model_timezone'], $options['model_timezone'])
  115.             ));
  116.         } elseif ('array' === $options['input']) {
  117.             $builder->addModelTransformer(new ReversedTransformer(
  118.                 new DateTimeToArrayTransformer($options['model_timezone'], $options['model_timezone'], array('year''month''day'))
  119.             ));
  120.         }
  121.     }
  122.     /**
  123.      * {@inheritdoc}
  124.      */
  125.     public function finishView(FormView $viewFormInterface $form, array $options)
  126.     {
  127.         $view->vars['widget'] = $options['widget'];
  128.         // Change the input to a HTML5 date input if
  129.         //  * the widget is set to "single_text"
  130.         //  * the format matches the one expected by HTML5
  131.         //  * the html5 is set to true
  132.         if ($options['html5'] && 'single_text' === $options['widget'] && self::HTML5_FORMAT === $options['format']) {
  133.             $view->vars['type'] = 'date';
  134.         }
  135.         if ($form->getConfig()->hasAttribute('formatter')) {
  136.             $pattern $form->getConfig()->getAttribute('formatter')->getPattern();
  137.             // remove special characters unless the format was explicitly specified
  138.             if (!is_string($options['format'])) {
  139.                 // remove quoted strings first
  140.                 $pattern preg_replace('/\'[^\']+\'/'''$pattern);
  141.                 // remove remaining special chars
  142.                 $pattern preg_replace('/[^yMd]+/'''$pattern);
  143.             }
  144.             // set right order with respect to locale (e.g.: de_DE=dd.MM.yy; en_US=M/d/yy)
  145.             // lookup various formats at http://userguide.icu-project.org/formatparse/datetime
  146.             if (preg_match('/^([yMd]+)[^yMd]*([yMd]+)[^yMd]*([yMd]+)$/'$pattern)) {
  147.                 $pattern preg_replace(array('/y+/''/M+/''/d+/'), array('{{ year }}''{{ month }}''{{ day }}'), $pattern);
  148.             } else {
  149.                 // default fallback
  150.                 $pattern '{{ year }}{{ month }}{{ day }}';
  151.             }
  152.             $view->vars['date_pattern'] = $pattern;
  153.         }
  154.     }
  155.     /**
  156.      * {@inheritdoc}
  157.      */
  158.     public function configureOptions(OptionsResolver $resolver)
  159.     {
  160.         $compound = function (Options $options) {
  161.             return 'single_text' !== $options['widget'];
  162.         };
  163.         $placeholderDefault = function (Options $options) {
  164.             return $options['required'] ? null '';
  165.         };
  166.         $placeholderNormalizer = function (Options $options$placeholder) use ($placeholderDefault) {
  167.             if (is_array($placeholder)) {
  168.                 $default $placeholderDefault($options);
  169.                 return array_merge(
  170.                     array('year' => $default'month' => $default'day' => $default),
  171.                     $placeholder
  172.                 );
  173.             }
  174.             return array(
  175.                 'year' => $placeholder,
  176.                 'month' => $placeholder,
  177.                 'day' => $placeholder,
  178.             );
  179.         };
  180.         $choiceTranslationDomainNormalizer = function (Options $options$choiceTranslationDomain) {
  181.             if (is_array($choiceTranslationDomain)) {
  182.                 $default false;
  183.                 return array_replace(
  184.                     array('year' => $default'month' => $default'day' => $default),
  185.                     $choiceTranslationDomain
  186.                 );
  187.             }
  188.             return array(
  189.                 'year' => $choiceTranslationDomain,
  190.                 'month' => $choiceTranslationDomain,
  191.                 'day' => $choiceTranslationDomain,
  192.             );
  193.         };
  194.         $format = function (Options $options) {
  195.             return 'single_text' === $options['widget'] ? DateType::HTML5_FORMAT DateType::DEFAULT_FORMAT;
  196.         };
  197.         $resolver->setDefaults(array(
  198.             'years' => range(date('Y') - 5date('Y') + 5),
  199.             'months' => range(112),
  200.             'days' => range(131),
  201.             'widget' => 'choice',
  202.             'input' => 'datetime',
  203.             'format' => $format,
  204.             'model_timezone' => null,
  205.             'view_timezone' => null,
  206.             'placeholder' => $placeholderDefault,
  207.             'html5' => true,
  208.             // Don't modify \DateTime classes by reference, we treat
  209.             // them like immutable value objects
  210.             'by_reference' => false,
  211.             'error_bubbling' => false,
  212.             // If initialized with a \DateTime object, FormType initializes
  213.             // this option to "\DateTime". Since the internal, normalized
  214.             // representation is not \DateTime, but an array, we need to unset
  215.             // this option.
  216.             'data_class' => null,
  217.             'compound' => $compound,
  218.             'choice_translation_domain' => false,
  219.         ));
  220.         $resolver->setNormalizer('placeholder'$placeholderNormalizer);
  221.         $resolver->setNormalizer('choice_translation_domain'$choiceTranslationDomainNormalizer);
  222.         $resolver->setAllowedValues('input', array(
  223.             'datetime',
  224.             'string',
  225.             'timestamp',
  226.             'array',
  227.         ));
  228.         $resolver->setAllowedValues('widget', array(
  229.             'single_text',
  230.             'text',
  231.             'choice',
  232.         ));
  233.         $resolver->setAllowedTypes('format', array('int''string'));
  234.         $resolver->setAllowedTypes('years''array');
  235.         $resolver->setAllowedTypes('months''array');
  236.         $resolver->setAllowedTypes('days''array');
  237.     }
  238.     /**
  239.      * {@inheritdoc}
  240.      */
  241.     public function getBlockPrefix()
  242.     {
  243.         return 'date';
  244.     }
  245.     private function formatTimestamps(\IntlDateFormatter $formatter$regex, array $timestamps)
  246.     {
  247.         $pattern $formatter->getPattern();
  248.         $timezone $formatter->getTimeZoneId();
  249.         $formattedTimestamps = array();
  250.         $formatter->setTimeZone('UTC');
  251.         if (preg_match($regex$pattern$matches)) {
  252.             $formatter->setPattern($matches[0]);
  253.             foreach ($timestamps as $timestamp => $choice) {
  254.                 $formattedTimestamps[$formatter->format($timestamp)] = $choice;
  255.             }
  256.             // I'd like to clone the formatter above, but then we get a
  257.             // segmentation fault, so let's restore the old state instead
  258.             $formatter->setPattern($pattern);
  259.         }
  260.         $formatter->setTimeZone($timezone);
  261.         return $formattedTimestamps;
  262.     }
  263.     private function listYears(array $years)
  264.     {
  265.         $result = array();
  266.         foreach ($years as $year) {
  267.             if (false !== $y gmmktime(000615$year)) {
  268.                 $result[$y] = $year;
  269.             }
  270.         }
  271.         return $result;
  272.     }
  273.     private function listMonths(array $months)
  274.     {
  275.         $result = array();
  276.         foreach ($months as $month) {
  277.             $result[gmmktime(000$month15)] = $month;
  278.         }
  279.         return $result;
  280.     }
  281.     private function listDays(array $days)
  282.     {
  283.         $result = array();
  284.         foreach ($days as $day) {
  285.             $result[gmmktime(0005$day)] = $day;
  286.         }
  287.         return $result;
  288.     }
  289. }