2012-07-01 07:52:20 +00:00
< ? php
/*
* This file is part of the Symfony package .
*
* ( c ) Fabien Potencier < fabien @ symfony . com >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
namespace Symfony\Component\Form\Extension\Core\Type ;
use Symfony\Component\Form\AbstractType ;
use Symfony\Component\Form\FormInterface ;
use Symfony\Component\Form\FormBuilderInterface ;
use Symfony\Component\Form\Exception\CreationException ;
use Symfony\Component\Form\FormViewInterface ;
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer ;
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer ;
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer ;
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer ;
use Symfony\Component\Form\ReversedTransformer ;
use Symfony\Component\OptionsResolver\Options ;
use Symfony\Component\OptionsResolver\OptionsResolverInterface ;
2012-07-16 19:40:19 +00:00
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException ;
2012-07-01 07:52:20 +00:00
class DateType extends AbstractType
{
const DEFAULT_FORMAT = \IntlDateFormatter :: MEDIUM ;
2012-07-16 19:40:19 +00:00
const HTML5_FORMAT = 'yyyy-MM-dd' ;
private static $acceptedFormats = array (
\IntlDateFormatter :: FULL ,
\IntlDateFormatter :: LONG ,
\IntlDateFormatter :: MEDIUM ,
\IntlDateFormatter :: SHORT ,
);
2012-07-01 07:52:20 +00:00
/**
* { @ inheritdoc }
*/
public function buildForm ( FormBuilderInterface $builder , array $options )
{
2012-07-16 19:40:19 +00:00
$dateFormat = is_int ( $options [ 'format' ]) ? $options [ 'format' ] : self :: DEFAULT_FORMAT ;
$timeFormat = \IntlDateFormatter :: NONE ;
$calendar = \IntlDateFormatter :: GREGORIAN ;
$pattern = is_string ( $options [ 'format' ]) ? $options [ 'format' ] : null ;
if ( ! in_array ( $dateFormat , self :: $acceptedFormats , true )) {
throw new InvalidOptionsException ( 'The "format" option must be one of the IntlDateFormatter constants (FULL, LONG, MEDIUM, SHORT) or a string representing a custom format.' );
2012-07-01 07:52:20 +00:00
}
2012-07-16 19:40:19 +00:00
if ( null !== $pattern && ( false === strpos ( $pattern , 'y' ) || false === strpos ( $pattern , 'M' ) || false === strpos ( $pattern , 'd' ))) {
throw new InvalidOptionsException ( sprintf ( 'The "format" option should contain the letters "y", "M" and "d". Its current value is "%s".' , $pattern ));
}
2012-07-01 07:52:20 +00:00
if ( 'single_text' === $options [ 'widget' ]) {
2012-07-16 19:40:19 +00:00
$builder -> addViewTransformer ( new DateTimeToLocalizedStringTransformer (
$options [ 'model_timezone' ],
$options [ 'view_timezone' ],
$dateFormat ,
$timeFormat ,
$calendar ,
$pattern
));
2012-07-01 07:52:20 +00:00
} else {
2012-07-16 19:40:19 +00:00
$yearOptions = $monthOptions = $dayOptions = array (
'error_bubbling' => true ,
);
$formatter = new \IntlDateFormatter (
\Locale :: getDefault (),
$dateFormat ,
$timeFormat ,
'UTC' ,
$calendar ,
$pattern
);
$formatter -> setLenient ( false );
2012-07-01 07:52:20 +00:00
if ( 'choice' === $options [ 'widget' ]) {
// Only pass a subset of the options to children
2012-07-16 19:40:19 +00:00
$yearOptions [ 'choices' ] = $this -> formatTimestamps ( $formatter , '/y+/' , $this -> listYears ( $options [ 'years' ]));
$yearOptions [ 'empty_value' ] = $options [ 'empty_value' ][ 'year' ];
$monthOptions [ 'choices' ] = $this -> formatTimestamps ( $formatter , '/M+/' , $this -> listMonths ( $options [ 'months' ]));
$monthOptions [ 'empty_value' ] = $options [ 'empty_value' ][ 'month' ];
$dayOptions [ 'choices' ] = $this -> formatTimestamps ( $formatter , '/d+/' , $this -> listDays ( $options [ 'days' ]));
$dayOptions [ 'empty_value' ] = $options [ 'empty_value' ][ 'day' ];
}
2012-07-01 07:52:20 +00:00
2012-07-16 19:40:19 +00:00
// Append generic carry-along options
foreach ( array ( 'required' , 'translation_domain' ) as $passOpt ) {
$yearOptions [ $passOpt ] = $monthOptions [ $passOpt ] = $dayOptions [ $passOpt ] = $options [ $passOpt ];
2012-07-01 07:52:20 +00:00
}
$builder
-> add ( 'year' , $options [ 'widget' ], $yearOptions )
-> add ( 'month' , $options [ 'widget' ], $monthOptions )
-> add ( 'day' , $options [ 'widget' ], $dayOptions )
-> addViewTransformer ( new DateTimeToArrayTransformer (
2012-07-16 19:40:19 +00:00
$options [ 'model_timezone' ], $options [ 'view_timezone' ], array ( 'year' , 'month' , 'day' )
2012-07-01 07:52:20 +00:00
))
2012-07-16 19:40:19 +00:00
-> setAttribute ( 'formatter' , $formatter )
2012-07-01 07:52:20 +00:00
;
}
if ( 'string' === $options [ 'input' ]) {
$builder -> addModelTransformer ( new ReversedTransformer (
2012-07-16 19:40:19 +00:00
new DateTimeToStringTransformer ( $options [ 'model_timezone' ], $options [ 'model_timezone' ], 'Y-m-d' )
2012-07-01 07:52:20 +00:00
));
} elseif ( 'timestamp' === $options [ 'input' ]) {
$builder -> addModelTransformer ( new ReversedTransformer (
2012-07-16 19:40:19 +00:00
new DateTimeToTimestampTransformer ( $options [ 'model_timezone' ], $options [ 'model_timezone' ])
2012-07-01 07:52:20 +00:00
));
} elseif ( 'array' === $options [ 'input' ]) {
$builder -> addModelTransformer ( new ReversedTransformer (
2012-07-16 19:40:19 +00:00
new DateTimeToArrayTransformer ( $options [ 'model_timezone' ], $options [ 'model_timezone' ], array ( 'year' , 'month' , 'day' ))
2012-07-01 07:52:20 +00:00
));
}
}
/**
* { @ inheritdoc }
*/
public function finishView ( FormViewInterface $view , FormInterface $form , array $options )
{
$view -> setVar ( 'widget' , $options [ 'widget' ]);
2012-07-16 19:40:19 +00:00
// Change the input to a HTML5 date input if
// * the widget is set to "single_text"
// * the format matches the one expected by HTML5
if ( 'single_text' === $options [ 'widget' ] && self :: HTML5_FORMAT === $options [ 'format' ]) {
2012-07-01 07:52:20 +00:00
$view -> setVar ( 'type' , 'date' );
}
2012-07-16 19:40:19 +00:00
if ( $form -> getConfig () -> hasAttribute ( 'formatter' )) {
2012-07-01 07:52:20 +00:00
$pattern = $form -> getConfig () -> getAttribute ( 'formatter' ) -> getPattern ();
// set right order with respect to locale (e.g.: de_DE=dd.MM.yy; en_US=M/d/yy)
// lookup various formats at http://userguide.icu-project.org/formatparse/datetime
if ( preg_match ( '/^([yMd]+).+([yMd]+).+([yMd]+)$/' , $pattern )) {
$pattern = preg_replace ( array ( '/y+/' , '/M+/' , '/d+/' ), array ( '{{ year }}' , '{{ month }}' , '{{ day }}' ), $pattern );
} else {
// default fallback
$pattern = '{{ year }}-{{ month }}-{{ day }}' ;
}
$view -> setVar ( 'date_pattern' , $pattern );
}
}
/**
* { @ inheritdoc }
*/
public function setDefaultOptions ( OptionsResolverInterface $resolver )
{
$compound = function ( Options $options ) {
return $options [ 'widget' ] !== 'single_text' ;
};
2012-07-16 19:40:19 +00:00
$emptyValue = $emptyValueDefault = function ( Options $options ) {
return $options [ 'required' ] ? null : '' ;
};
2012-07-22 17:38:00 +00:00
$emptyValueNormalizer = function ( Options $options , $emptyValue ) use ( $emptyValueDefault ) {
2012-07-16 19:40:19 +00:00
if ( is_array ( $emptyValue )) {
$default = $emptyValueDefault ( $options );
return array_merge (
array ( 'year' => $default , 'month' => $default , 'day' => $default ),
$emptyValue
);
}
return array (
'year' => $emptyValue ,
'month' => $emptyValue ,
'day' => $emptyValue
);
};
// BC until Symfony 2.3
$modelTimezone = function ( Options $options ) {
return $options [ 'data_timezone' ];
};
// BC until Symfony 2.3
$viewTimezone = function ( Options $options ) {
return $options [ 'user_timezone' ];
};
2012-07-01 07:52:20 +00:00
$resolver -> setDefaults ( array (
'years' => range ( date ( 'Y' ) - 5 , date ( 'Y' ) + 5 ),
'months' => range ( 1 , 12 ),
'days' => range ( 1 , 31 ),
'widget' => 'choice' ,
'input' => 'datetime' ,
2012-07-16 19:40:19 +00:00
'format' => self :: HTML5_FORMAT ,
'model_timezone' => $modelTimezone ,
'view_timezone' => $viewTimezone ,
// Deprecated timezone options
2012-07-01 07:52:20 +00:00
'data_timezone' => null ,
'user_timezone' => null ,
2012-07-16 19:40:19 +00:00
'empty_value' => $emptyValue ,
2012-07-01 07:52:20 +00:00
// Don't modify \DateTime classes by reference, we treat
// them like immutable value objects
'by_reference' => false ,
'error_bubbling' => false ,
// If initialized with a \DateTime object, FormType initializes
// this option to "\DateTime". Since the internal, normalized
// representation is not \DateTime, but an array, we need to unset
// this option.
'data_class' => null ,
'compound' => $compound ,
));
2012-07-22 17:38:00 +00:00
$resolver -> setNormalizers ( array (
'empty_value' => $emptyValueNormalizer ,
2012-07-16 19:40:19 +00:00
));
2012-07-01 07:52:20 +00:00
$resolver -> setAllowedValues ( array (
'input' => array (
'datetime' ,
'string' ,
'timestamp' ,
'array' ,
),
'widget' => array (
'single_text' ,
'text' ,
'choice' ,
),
));
2012-07-16 19:40:19 +00:00
$resolver -> setAllowedTypes ( array (
'format' => array ( 'int' , 'string' ),
));
2012-07-01 07:52:20 +00:00
}
/**
* { @ inheritdoc }
*/
public function getParent ()
{
return 'field' ;
}
/**
* { @ inheritdoc }
*/
public function getName ()
{
return 'date' ;
}
2012-07-16 19:40:19 +00:00
private function formatTimestamps ( \IntlDateFormatter $formatter , $regex , array $timestamps )
2012-07-01 07:52:20 +00:00
{
$pattern = $formatter -> getPattern ();
$timezone = $formatter -> getTimezoneId ();
$formatter -> setTimezoneId ( \DateTimeZone :: UTC );
2012-07-16 19:40:19 +00:00
if ( preg_match ( $regex , $pattern , $matches )) {
2012-07-01 07:52:20 +00:00
$formatter -> setPattern ( $matches [ 0 ]);
2012-07-16 19:40:19 +00:00
foreach ( $timestamps as $key => $timestamp ) {
$timestamps [ $key ] = $formatter -> format ( $timestamp );
2012-07-01 07:52:20 +00:00
}
// I'd like to clone the formatter above, but then we get a
// segmentation fault, so let's restore the old state instead
$formatter -> setPattern ( $pattern );
}
$formatter -> setTimezoneId ( $timezone );
2012-07-16 19:40:19 +00:00
return $timestamps ;
}
private function listYears ( array $years )
{
$result = array ();
foreach ( $years as $year ) {
$result [ $year ] = gmmktime ( 0 , 0 , 0 , 6 , 15 , $year );
}
return $result ;
}
private function listMonths ( array $months )
{
$result = array ();
foreach ( $months as $month ) {
$result [ $month ] = gmmktime ( 0 , 0 , 0 , $month , 15 );
}
return $result ;
}
private function listDays ( array $days )
{
$result = array ();
foreach ( $days as $day ) {
$result [ $day ] = gmmktime ( 0 , 0 , 0 , 5 , $day );
}
return $result ;
2012-07-01 07:52:20 +00:00
}
}