Skip to content

Commit 2a80c5c

Browse files
dFayetxabbuh
authored andcommitted
Add new Form WeekType
1 parent 82624ad commit 2a80c5c

File tree

7 files changed

+824
-0
lines changed

7 files changed

+824
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
4.4.0
55
-----
66

7+
* add new `WeekType`
78
* using different values for the "model_timezone" and "view_timezone" options of the `TimeType` without configuring a
89
reference date is deprecated
910
* preferred choices are repeated in the list of all choices

Extension/Core/CoreExtension.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ protected function loadTypes()
8383
new Type\CurrencyType(),
8484
new Type\TelType(),
8585
new Type\ColorType(),
86+
new Type\WeekType(),
8687
];
8788
}
8889

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Extension\Core\DataTransformer;
13+
14+
use Symfony\Component\Form\DataTransformerInterface;
15+
use Symfony\Component\Form\Exception\TransformationFailedException;
16+
17+
/**
18+
* Transforms between an ISO 8601 week date string and an array.
19+
*
20+
* @author Damien Fayet <[email protected]>
21+
*/
22+
class WeekToArrayTransformer implements DataTransformerInterface
23+
{
24+
/**
25+
* Transforms a string containing an ISO 8601 week date into an array.
26+
*
27+
* @param string|null $value A week date string
28+
*
29+
* @return array A value containing year and week
30+
*
31+
* @throws TransformationFailedException If the given value is not a string,
32+
* or if the given value does not follow the right format
33+
*/
34+
public function transform($value)
35+
{
36+
if (null === $value) {
37+
return ['year' => null, 'week' => null];
38+
}
39+
40+
if (!\is_string($value)) {
41+
throw new TransformationFailedException(sprintf('Value is expected to be a string but was "%s".', \is_object($value) ? \get_class($value) : \gettype($value)));
42+
}
43+
44+
if (0 === preg_match('/^(?P<year>\d{4})-W(?P<week>\d{2})$/', $value, $matches)) {
45+
throw new TransformationFailedException('Given data does not follow the date format "Y-\WW".');
46+
}
47+
48+
return [
49+
'year' => (int) $matches['year'],
50+
'week' => (int) $matches['week'],
51+
];
52+
}
53+
54+
/**
55+
* Transforms an array into a week date string.
56+
*
57+
* @param array $value An array containing a year and a week number
58+
*
59+
* @return string|null A week date string following the format Y-\WW
60+
*
61+
* @throws TransformationFailedException If the given value can not be merged in a valid week date string,
62+
* or if the obtained week date does not exists
63+
*/
64+
public function reverseTransform($value)
65+
{
66+
if (null === $value || [] === $value) {
67+
return null;
68+
}
69+
70+
if (!\is_array($value)) {
71+
throw new TransformationFailedException(sprintf('Value is expected to be an array, but was "%s".', \is_object($value) ? \get_class($value) : \gettype($value)));
72+
}
73+
74+
if (!\array_key_exists('year', $value)) {
75+
throw new TransformationFailedException('Key "year" is missing.');
76+
}
77+
78+
if (!\array_key_exists('week', $value)) {
79+
throw new TransformationFailedException('Key "week" is missing.');
80+
}
81+
82+
if ($additionalKeys = array_diff(array_keys($value), ['year', 'week'])) {
83+
throw new TransformationFailedException(sprintf('Expected only keys "year" and "week" to be present, but also got ["%s"].', implode('", "', $additionalKeys)));
84+
}
85+
86+
if (null === $value['year'] && null === $value['week']) {
87+
return null;
88+
}
89+
90+
if (!\is_int($value['year'])) {
91+
throw new TransformationFailedException(sprintf('Year is expected to be an integer, but was "%s".', \is_object($value['year']) ? \get_class($value['year']) : \gettype($value['year'])));
92+
}
93+
94+
if (!\is_int($value['week'])) {
95+
throw new TransformationFailedException(sprintf('Week is expected to be an integer, but was "%s".', \is_object($value['week']) ? \get_class($value['week']) : \gettype($value['week'])));
96+
}
97+
98+
// The 28th December is always in the last week of the year
99+
if (date('W', strtotime('28th December '.$value['year'])) < $value['week']) {
100+
throw new TransformationFailedException(sprintf('Week "%d" does not exist for year "%d".', $value['week'], $value['year']));
101+
}
102+
103+
return sprintf('%d-W%02d', $value['year'], $value['week']);
104+
}
105+
}

Extension/Core/Type/WeekType.php

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Extension\Core\Type;
13+
14+
use Symfony\Component\Form\AbstractType;
15+
use Symfony\Component\Form\Exception\LogicException;
16+
use Symfony\Component\Form\Extension\Core\DataTransformer\WeekToArrayTransformer;
17+
use Symfony\Component\Form\FormBuilderInterface;
18+
use Symfony\Component\Form\FormInterface;
19+
use Symfony\Component\Form\FormView;
20+
use Symfony\Component\Form\ReversedTransformer;
21+
use Symfony\Component\OptionsResolver\Options;
22+
use Symfony\Component\OptionsResolver\OptionsResolver;
23+
24+
class WeekType extends AbstractType
25+
{
26+
private static $widgets = [
27+
'text' => IntegerType::class,
28+
'choice' => ChoiceType::class,
29+
];
30+
31+
/**
32+
* {@inheritdoc}
33+
*/
34+
public function buildForm(FormBuilderInterface $builder, array $options)
35+
{
36+
if ('string' === $options['input']) {
37+
$builder->addModelTransformer(new WeekToArrayTransformer());
38+
}
39+
40+
if ('single_text' === $options['widget']) {
41+
$builder->addViewTransformer(new ReversedTransformer(new WeekToArrayTransformer()));
42+
} else {
43+
$yearOptions = $weekOptions = [
44+
'error_bubbling' => true,
45+
'empty_data' => '',
46+
];
47+
// when the form is compound the entries of the array are ignored in favor of children data
48+
// so we need to handle the cascade setting here
49+
$emptyData = $builder->getEmptyData() ?: [];
50+
51+
$yearOptions['empty_data'] = $emptyData['year'] ?? '';
52+
$weekOptions['empty_data'] = $emptyData['week'] ?? '';
53+
54+
if (isset($options['invalid_message'])) {
55+
$yearOptions['invalid_message'] = $options['invalid_message'];
56+
$weekOptions['invalid_message'] = $options['invalid_message'];
57+
}
58+
59+
if (isset($options['invalid_message_parameters'])) {
60+
$yearOptions['invalid_message_parameters'] = $options['invalid_message_parameters'];
61+
$weekOptions['invalid_message_parameters'] = $options['invalid_message_parameters'];
62+
}
63+
64+
if ('choice' === $options['widget']) {
65+
// Only pass a subset of the options to children
66+
$yearOptions['choices'] = array_combine($options['years'], $options['years']);
67+
$yearOptions['placeholder'] = $options['placeholder']['year'];
68+
$yearOptions['choice_translation_domain'] = $options['choice_translation_domain']['year'];
69+
70+
$weekOptions['choices'] = array_combine($options['weeks'], $options['weeks']);
71+
$weekOptions['placeholder'] = $options['placeholder']['week'];
72+
$weekOptions['choice_translation_domain'] = $options['choice_translation_domain']['week'];
73+
74+
// Append generic carry-along options
75+
foreach (['required', 'translation_domain'] as $passOpt) {
76+
$yearOptions[$passOpt] = $options[$passOpt];
77+
$weekOptions[$passOpt] = $options[$passOpt];
78+
}
79+
}
80+
81+
$builder->add('year', self::$widgets[$options['widget']], $yearOptions);
82+
$builder->add('week', self::$widgets[$options['widget']], $weekOptions);
83+
}
84+
}
85+
86+
/**
87+
* {@inheritdoc}
88+
*/
89+
public function buildView(FormView $view, FormInterface $form, array $options)
90+
{
91+
$view->vars['widget'] = $options['widget'];
92+
93+
if ($options['html5']) {
94+
$view->vars['type'] = 'week';
95+
}
96+
}
97+
98+
/**
99+
* {@inheritdoc}
100+
*/
101+
public function configureOptions(OptionsResolver $resolver)
102+
{
103+
$compound = function (Options $options) {
104+
return 'single_text' !== $options['widget'];
105+
};
106+
107+
$placeholderDefault = function (Options $options) {
108+
return $options['required'] ? null : '';
109+
};
110+
111+
$placeholderNormalizer = function (Options $options, $placeholder) use ($placeholderDefault) {
112+
if (\is_array($placeholder)) {
113+
$default = $placeholderDefault($options);
114+
115+
return array_merge(
116+
['year' => $default, 'week' => $default],
117+
$placeholder
118+
);
119+
}
120+
121+
return [
122+
'year' => $placeholder,
123+
'week' => $placeholder,
124+
];
125+
};
126+
127+
$choiceTranslationDomainNormalizer = function (Options $options, $choiceTranslationDomain) {
128+
if (\is_array($choiceTranslationDomain)) {
129+
$default = false;
130+
131+
return array_replace(
132+
['year' => $default, 'week' => $default],
133+
$choiceTranslationDomain
134+
);
135+
}
136+
137+
return [
138+
'year' => $choiceTranslationDomain,
139+
'week' => $choiceTranslationDomain,
140+
];
141+
};
142+
143+
$resolver->setDefaults([
144+
'years' => range(date('Y') - 10, date('Y') + 10),
145+
'weeks' => array_combine(range(1, 53), range(1, 53)),
146+
'widget' => 'single_text',
147+
'input' => 'array',
148+
'placeholder' => $placeholderDefault,
149+
'html5' => static function (Options $options) {
150+
return 'single_text' === $options['widget'];
151+
},
152+
'error_bubbling' => false,
153+
'empty_data' => function (Options $options) {
154+
return $options['compound'] ? [] : '';
155+
},
156+
'compound' => $compound,
157+
'choice_translation_domain' => false,
158+
]);
159+
160+
$resolver->setNormalizer('placeholder', $placeholderNormalizer);
161+
$resolver->setNormalizer('choice_translation_domain', $choiceTranslationDomainNormalizer);
162+
$resolver->setNormalizer('html5', function (Options $options, $html5) {
163+
if ($html5 && 'single_text' !== $options['widget']) {
164+
throw new LogicException(sprintf('The "widget" option of %s must be set to "single_text" when the "html5" option is enabled.', self::class));
165+
}
166+
167+
return $html5;
168+
});
169+
170+
$resolver->setAllowedValues('input', [
171+
'string',
172+
'array',
173+
]);
174+
175+
$resolver->setAllowedValues('widget', [
176+
'single_text',
177+
'text',
178+
'choice',
179+
]);
180+
181+
$resolver->setAllowedTypes('years', 'int[]');
182+
$resolver->setAllowedTypes('weeks', 'int[]');
183+
}
184+
185+
/**
186+
* {@inheritdoc}
187+
*/
188+
public function getBlockPrefix()
189+
{
190+
return 'week';
191+
}
192+
}

0 commit comments

Comments
 (0)