Skip to content

Commit 30269fa

Browse files
Merge branch '3.4' into 4.1
* 3.4: Command::addOption should allow int in $default [Form] Fixed keeping hash of equal \DateTimeInterface on submit [PhpUnitBridge] Fix typo [Form] Minor fixes in docs and cs [Config] Unset key during normalization [Form] Fixed empty data for compound date types invalidate forms on transformation failures [PropertyAccessor] Fix unable to write to singular property using setter while plural adder/remover exist
2 parents cb16d4a + 6a9d39c commit 30269fa

17 files changed

+331
-32
lines changed

Extension/Core/CoreExtension.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface;
1717
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
1818
use Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator;
19+
use Symfony\Component\Form\Extension\Core\Type\TransformationFailureExtension;
1920
use Symfony\Component\PropertyAccess\PropertyAccess;
2021
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
22+
use Symfony\Component\Translation\TranslatorInterface;
2123

2224
/**
2325
* Represents the main form extension, which loads the core functionality.
@@ -28,11 +30,13 @@ class CoreExtension extends AbstractExtension
2830
{
2931
private $propertyAccessor;
3032
private $choiceListFactory;
33+
private $translator;
3134

32-
public function __construct(PropertyAccessorInterface $propertyAccessor = null, ChoiceListFactoryInterface $choiceListFactory = null)
35+
public function __construct(PropertyAccessorInterface $propertyAccessor = null, ChoiceListFactoryInterface $choiceListFactory = null, TranslatorInterface $translator = null)
3336
{
3437
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
3538
$this->choiceListFactory = $choiceListFactory ?: new CachingFactoryDecorator(new PropertyAccessDecorator(new DefaultChoiceListFactory(), $this->propertyAccessor));
39+
$this->translator = $translator;
3640
}
3741

3842
protected function loadTypes()
@@ -74,4 +78,11 @@ protected function loadTypes()
7478
new Type\ColorType(),
7579
);
7680
}
81+
82+
protected function loadTypeExtensions()
83+
{
84+
return array(
85+
new TransformationFailureExtension($this->translator),
86+
);
87+
}
7788
}

Extension/Core/DataMapper/PropertyPathMapper.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,16 +73,17 @@ public function mapFormsToData($forms, &$data)
7373
// Write-back is disabled if the form is not synchronized (transformation failed),
7474
// if the form was not submitted and if the form is disabled (modification not allowed)
7575
if (null !== $propertyPath && $config->getMapped() && $form->isSubmitted() && $form->isSynchronized() && !$form->isDisabled()) {
76-
// If the field is of type DateTime and the data is the same skip the update to
76+
$propertyValue = $form->getData();
77+
// If the field is of type DateTimeInterface and the data is the same skip the update to
7778
// keep the original object hash
78-
if ($form->getData() instanceof \DateTime && $form->getData() == $this->propertyAccessor->getValue($data, $propertyPath)) {
79+
if ($propertyValue instanceof \DateTimeInterface && $propertyValue == $this->propertyAccessor->getValue($data, $propertyPath)) {
7980
continue;
8081
}
8182

8283
// If the data is identical to the value in $data, we are
8384
// dealing with a reference
84-
if (!\is_object($data) || !$config->getByReference() || $form->getData() !== $this->propertyAccessor->getValue($data, $propertyPath)) {
85-
$this->propertyAccessor->setValue($data, $propertyPath, $form->getData());
85+
if (!\is_object($data) || !$config->getByReference() || $propertyValue !== $this->propertyAccessor->getValue($data, $propertyPath)) {
86+
$this->propertyAccessor->setValue($data, $propertyPath, $propertyValue);
8687
}
8788
}
8889
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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\EventListener;
13+
14+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
15+
use Symfony\Component\Form\FormError;
16+
use Symfony\Component\Form\FormEvent;
17+
use Symfony\Component\Form\FormEvents;
18+
use Symfony\Component\Translation\TranslatorInterface;
19+
20+
/**
21+
* @author Christian Flothmann <[email protected]>
22+
*/
23+
class TransformationFailureListener implements EventSubscriberInterface
24+
{
25+
private $translator;
26+
27+
public function __construct(TranslatorInterface $translator = null)
28+
{
29+
$this->translator = $translator;
30+
}
31+
32+
public static function getSubscribedEvents()
33+
{
34+
return array(
35+
FormEvents::POST_SUBMIT => array('convertTransformationFailureToFormError', -1024),
36+
);
37+
}
38+
39+
public function convertTransformationFailureToFormError(FormEvent $event)
40+
{
41+
$form = $event->getForm();
42+
43+
if (null === $form->getTransformationFailure() || !$form->isValid()) {
44+
return;
45+
}
46+
47+
foreach ($form as $child) {
48+
if (!$child->isSynchronized()) {
49+
return;
50+
}
51+
}
52+
53+
$clientDataAsString = is_scalar($form->getViewData()) ? (string) $form->getViewData() : \gettype($form->getViewData());
54+
$messageTemplate = 'The value {{ value }} is not valid.';
55+
56+
if (null !== $this->translator) {
57+
$message = $this->translator->trans($messageTemplate, array('{{ value }}' => $clientDataAsString));
58+
} else {
59+
$message = strtr($messageTemplate, array('{{ value }}' => $clientDataAsString));
60+
}
61+
62+
$form->addError(new FormError($message, $messageTemplate, array('{{ value }}' => $clientDataAsString), null, $form->getTransformationFailure()));
63+
}
64+
}

Extension/Core/Type/DateTimeType.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ public function buildForm(FormBuilderInterface $builder, array $options)
9191
));
9292
}
9393
} else {
94+
// when the form is compound the entries of the array are ignored in favor of children data
95+
// so we need to handle the cascade setting here
96+
$emptyData = $builder->getEmptyData() ?: array();
9497
// Only pass a subset of the options to children
9598
$dateOptions = array_intersect_key($options, array_flip(array(
9699
'years',
@@ -105,6 +108,10 @@ public function buildForm(FormBuilderInterface $builder, array $options)
105108
'invalid_message_parameters',
106109
)));
107110

111+
if (isset($emptyData['date'])) {
112+
$dateOptions['empty_data'] = $emptyData['date'];
113+
}
114+
108115
$timeOptions = array_intersect_key($options, array_flip(array(
109116
'hours',
110117
'minutes',
@@ -120,6 +127,10 @@ public function buildForm(FormBuilderInterface $builder, array $options)
120127
'invalid_message_parameters',
121128
)));
122129

130+
if (isset($emptyData['time'])) {
131+
$timeOptions['empty_data'] = $emptyData['time'];
132+
}
133+
123134
if (false === $options['label']) {
124135
$dateOptions['label'] = false;
125136
$timeOptions['label'] = false;
@@ -227,6 +238,9 @@ public function configureOptions(OptionsResolver $resolver)
227238
// this option.
228239
'data_class' => null,
229240
'compound' => $compound,
241+
'empty_data' => function (Options $options) {
242+
return $options['compound'] ? array() : '';
243+
},
230244
));
231245

232246
// Don't add some defaults in order to preserve the defaults

Extension/Core/Type/DateType.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,21 @@ public function buildForm(FormBuilderInterface $builder, array $options)
7676

7777
$yearOptions = $monthOptions = $dayOptions = array(
7878
'error_bubbling' => true,
79+
'empty_data' => '',
7980
);
81+
// when the form is compound the entries of the array are ignored in favor of children data
82+
// so we need to handle the cascade setting here
83+
$emptyData = $builder->getEmptyData() ?: array();
84+
85+
if (isset($emptyData['year'])) {
86+
$yearOptions['empty_data'] = $emptyData['year'];
87+
}
88+
if (isset($emptyData['month'])) {
89+
$monthOptions['empty_data'] = $emptyData['month'];
90+
}
91+
if (isset($emptyData['day'])) {
92+
$dayOptions['empty_data'] = $emptyData['day'];
93+
}
8094

8195
if (isset($options['invalid_message'])) {
8296
$dayOptions['invalid_message'] = $options['invalid_message'];
@@ -265,6 +279,9 @@ public function configureOptions(OptionsResolver $resolver)
265279
// this option.
266280
'data_class' => null,
267281
'compound' => $compound,
282+
'empty_data' => function (Options $options) {
283+
return $options['compound'] ? array() : '';
284+
},
268285
'choice_translation_domain' => false,
269286
));
270287

Extension/Core/Type/TimeType.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,15 @@ public function buildForm(FormBuilderInterface $builder, array $options)
7171
} else {
7272
$hourOptions = $minuteOptions = $secondOptions = array(
7373
'error_bubbling' => true,
74+
'empty_data' => '',
7475
);
76+
// when the form is compound the entries of the array are ignored in favor of children data
77+
// so we need to handle the cascade setting here
78+
$emptyData = $builder->getEmptyData() ?: array();
79+
80+
if (isset($emptyData['hour'])) {
81+
$hourOptions['empty_data'] = $emptyData['hour'];
82+
}
7583

7684
if (isset($options['invalid_message'])) {
7785
$hourOptions['invalid_message'] = $options['invalid_message'];
@@ -136,10 +144,16 @@ public function buildForm(FormBuilderInterface $builder, array $options)
136144
$builder->add('hour', self::$widgets[$options['widget']], $hourOptions);
137145

138146
if ($options['with_minutes']) {
147+
if (isset($emptyData['minute'])) {
148+
$minuteOptions['empty_data'] = $emptyData['minute'];
149+
}
139150
$builder->add('minute', self::$widgets[$options['widget']], $minuteOptions);
140151
}
141152

142153
if ($options['with_seconds']) {
154+
if (isset($emptyData['second'])) {
155+
$secondOptions['empty_data'] = $emptyData['second'];
156+
}
143157
$builder->add('second', self::$widgets[$options['widget']], $secondOptions);
144158
}
145159

@@ -258,6 +272,9 @@ public function configureOptions(OptionsResolver $resolver)
258272
// representation is not \DateTime, but an array, we need to unset
259273
// this option.
260274
'data_class' => null,
275+
'empty_data' => function (Options $options) {
276+
return $options['compound'] ? array() : '';
277+
},
261278
'compound' => $compound,
262279
'choice_translation_domain' => false,
263280
));
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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\AbstractTypeExtension;
15+
use Symfony\Component\Form\Extension\Core\EventListener\TransformationFailureListener;
16+
use Symfony\Component\Form\FormBuilderInterface;
17+
use Symfony\Component\Translation\TranslatorInterface;
18+
19+
/**
20+
* @author Christian Flothmann <[email protected]>
21+
*/
22+
class TransformationFailureExtension extends AbstractTypeExtension
23+
{
24+
private $translator;
25+
26+
public function __construct(TranslatorInterface $translator = null)
27+
{
28+
$this->translator = $translator;
29+
}
30+
31+
public function buildForm(FormBuilderInterface $builder, array $options)
32+
{
33+
if (!isset($options['invalid_message']) && !isset($options['invalid_message_parameters'])) {
34+
$builder->addEventSubscriber(new TransformationFailureListener($this->translator));
35+
}
36+
}
37+
38+
public function getExtendedType()
39+
{
40+
return 'Symfony\Component\Form\Extension\Core\Type\FormType';
41+
}
42+
}

Form.php

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,24 +31,26 @@
3131
*
3232
* (1) the "model" format required by the form's object
3333
* (2) the "normalized" format for internal processing
34-
* (3) the "view" format used for display
34+
* (3) the "view" format used for display simple fields
35+
* or map children model data for compound fields
3536
*
3637
* A date field, for example, may store a date as "Y-m-d" string (1) in the
3738
* object. To facilitate processing in the field, this value is normalized
3839
* to a DateTime object (2). In the HTML representation of your form, a
39-
* localized string (3) is presented to and modified by the user.
40+
* localized string (3) may be presented to and modified by the user, or it could be an array of values
41+
* to be mapped to choices fields.
4042
*
4143
* In most cases, format (1) and format (2) will be the same. For example,
4244
* a checkbox field uses a Boolean value for both internal processing and
43-
* storage in the object. In these cases you simply need to set a value
45+
* storage in the object. In these cases you simply need to set a view
4446
* transformer to convert between formats (2) and (3). You can do this by
4547
* calling addViewTransformer().
4648
*
4749
* In some cases though it makes sense to make format (1) configurable. To
4850
* demonstrate this, let's extend our above date field to store the value
4951
* either as "Y-m-d" string or as timestamp. Internally we still want to
5052
* use a DateTime object for processing. To convert the data from string/integer
51-
* to DateTime you can set a normalization transformer by calling
53+
* to DateTime you can set a model transformer by calling
5254
* addModelTransformer(). The normalized data is then converted to the displayed
5355
* data as described before.
5456
*
@@ -217,7 +219,7 @@ public function getPropertyPath()
217219
}
218220

219221
if (null === $this->getName() || '' === $this->getName()) {
220-
return;
222+
return null;
221223
}
222224

223225
$parent = $this->parent;
@@ -340,8 +342,8 @@ public function setData($modelData)
340342
$modelData = $event->getData();
341343
}
342344

343-
// Treat data as strings unless a value transformer exists
344-
if (!$this->config->getViewTransformers() && !$this->config->getModelTransformers() && is_scalar($modelData)) {
345+
// Treat data as strings unless a transformer exists
346+
if (is_scalar($modelData) && !$this->config->getViewTransformers() && !$this->config->getModelTransformers()) {
345347
$modelData = (string) $modelData;
346348
}
347349

@@ -1014,7 +1016,7 @@ public function createView(FormView $parent = null)
10141016
}
10151017

10161018
/**
1017-
* Normalizes the value if a normalization transformer is set.
1019+
* Normalizes the value if a model transformer is set.
10181020
*
10191021
* @param mixed $value The value to transform
10201022
*
@@ -1036,7 +1038,7 @@ private function modelToNorm($value)
10361038
}
10371039

10381040
/**
1039-
* Reverse transforms a value if a normalization transformer is set.
1041+
* Reverse transforms a value if a model transformer is set.
10401042
*
10411043
* @param string $value The value to reverse transform
10421044
*
@@ -1060,7 +1062,7 @@ private function normToModel($value)
10601062
}
10611063

10621064
/**
1063-
* Transforms the value if a value transformer is set.
1065+
* Transforms the value if a view transformer is set.
10641066
*
10651067
* @param mixed $value The value to transform
10661068
*
@@ -1091,7 +1093,7 @@ private function normToView($value)
10911093
}
10921094

10931095
/**
1094-
* Reverse transforms a value if a value transformer is set.
1096+
* Reverse transforms a value if a view transformer is set.
10951097
*
10961098
* @param string $value The value to reverse transform
10971099
*

NativeRequestHandler.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
use Symfony\Component\Form\Util\ServerParams;
1616

1717
/**
18-
* A request handler using PHP's super globals $_GET, $_POST and $_SERVER.
18+
* A request handler using PHP super globals $_GET, $_POST and $_SERVER.
1919
*
2020
* @author Bernhard Schussek <[email protected]>
2121
*/
@@ -213,7 +213,7 @@ private static function stripEmptyFiles($data)
213213

214214
if (self::$fileKeys === $keys) {
215215
if (UPLOAD_ERR_NO_FILE === $data['error']) {
216-
return;
216+
return null;
217217
}
218218

219219
return $data;

0 commit comments

Comments
 (0)