8000 Merge branch '3.4' into 4.1 · symfony/symfony@c8677f3 · GitHub
[go: up one dir, main page]

Skip to content

Commit c8677f3

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 e59e4e0 + 4f18e76 commit c8677f3

26 files changed

+471
-61
lines changed

src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class DeprecationErrorHandler
3333
* - use "/some-regexp/" to stop the test suite whenever a deprecation
3434
* message matches the given regular expression;
3535
* - use a number to define the upper bound of allowed deprecations,
36-
* making the test suite fail whenever more notices are trigerred.
36+
* making the test suite fail whenever more notices are triggered.
3737
*
3838
* @param int|string|false $mode The reporting mode, defaults to not allowing any deprecations
3939
*/

src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@
7171
<argument type="service" id="form.choice_list_factory"/>
7272
</service>
7373

74+
<service id="form.type_extension.form.transformation_failure_handling" class="Symfony\Component\Form\Extension\Core\Type\TransformationFailureExtension">
75+
<tag name="form.type_extension" extended-type="Symfony\Component\Form\Extension\Core\Type\FormType" />
76+
<argument type="service" id="translator" on-invalid="ignore" />
77+
</service>
78+
7479
<!-- FormTypeHttpFoundationExtension -->
7580
<service id="form.type_extension.form.http_foundation" class="Symfony\Component\Form\Extension\HttpFoundation\Type\FormTypeHttpFoundationExtension">
7681
<argument type="service" id="form.type_extension.form.request_handler" />

src/Symfony/Component/Config/Definition/ArrayNode.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,10 @@ protected function normalizeValue($value)
292292
$normalized = array();
293293
foreach ($value as $name => $val) {
294294
if (isset($this->children[$name])) {
295-
$normalized[$name] = $this->children[$name]->normalize($val);
295+
try {
296+
$normalized[$name] = $this->children[$name]->normalize($val);
297+
} catch (UnsetKeyException $e) {
298+
}
296299
unset($value[$name]);
297300
} elseif (!$this->removeExtraKeys) {
298301
$normalized[$name] = $val;

src/Symfony/Component/Config/Definition/Builder/ExprBuilder.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ public function thenEmptyArray()
174174
}
175175

176176
/**
177-
* Sets a closure marking the value as invalid at validation time.
177+
* Sets a closure marking the value as invalid at processing time.
178178
*
179179
* if you want to add the value of the node in your message just use a %s placeholder.
180180
*
@@ -192,7 +192,7 @@ public function thenInvalid($message)
192192
}
193193

194194
/**
195-
* Sets a closure unsetting this key of the array at validation time.
195+
* Sets a closure unsetting this key of the array at processing time.
196196
*
197197
* @return $this
198198
*

src/Symfony/Component/Config/Tests/Definition/Builder/ArrayNodeDefinitionTest.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,25 @@ public function testNormalizeKeys()
232232
$this->assertFalse($this->getField($node, 'normalizeKeys'));
233233
}
234234

235+
public function testUnsetChild()
236+
{
237+
$node = new ArrayNodeDefinition('root');
238+
$node
239+
->children()
240+
->scalarNode('value')
241+
->beforeNormalization()
242+
->ifTrue(function ($value) {
243+
return empty($value);
244+
})
245+
->thenUnset()
246+
->end()
247+
->end()
248+
->end()
249+
;
250+
251+
$this->assertSame(array(), $node->getNode()->normalize(array('value' => null)));
252+
}
253+
235254
public function testPrototypeVariable()
236255
{
237256
$node = new ArrayNodeDefinition('root');

src/Symfony/Component/Console/Command/Command.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -379,11 +379,11 @@ public function addArgument($name, $mode = null, $description = '', $default = n
379379
/**
380380
* Adds an option.
381381
*
382-
* @param string $name The option name
383-
* @param string|array $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
384-
* @param int|null $mode The option mode: One of the VALUE_* constants
385-
* @param string $description A description text
386-
* @param string|string[]|bool|null $default The default value (must be null for self::VALUE_NONE)
382+
* @param string $name The option name
383+
* @param string|array $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
384+
* @param int|null $mode The option mode: One of the VALUE_* constants
385+
* @param string $description A description text
386+
* @param string|string[]|int|bool|null $default The default value (must be null for self::VALUE_NONE)
387387
*
388388
* @throws InvalidArgumentException If option mode is invalid or incompatible
389389
*

src/Symfony/Component/Form/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
}

src/Symfony/Component/Form/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 <fabien@symfony.com>
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 <christian.flothmann@sensiolabs.de>
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+
}

src/Symfony/Component/Form/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

src/Symfony/Component/Form/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

src/Symfony/Component/Form/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 <fabien@symfony.com>
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 <christian.flothmann@sensiolabs.de>
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+
}

0 commit comments

Comments
 (0)
0