Skip to content

Commit 2fc85b0

Browse files
committed
[Form] Support catching exceptions from an accessor
1 parent 2055af5 commit 2fc85b0

File tree

2 files changed

+71
-1
lines changed

2 files changed

+71
-1
lines changed

src/Symfony/Component/Form/Extension/Core/DataMapper/AccessorMapper.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
namespace Symfony\Component\Form\Extension\Core\DataMapper;
44

55
use Symfony\Component\Form\DataMapperInterface;
6+
use Symfony\Component\Form\Exception\ExceptionInterface;
67
use Symfony\Component\Form\Exception\UnexpectedTypeException;
8+
use Symfony\Component\Form\FormError;
9+
use TypeError;
710

811
class AccessorMapper implements DataMapperInterface
912
{
@@ -69,7 +72,13 @@ public function mapFormsToData(iterable $forms, &$data)
6972
// Write-back is disabled if the form is not synchronized (transformation failed),
7073
// if the form was not submitted and if the form is disabled (modification not allowed)
7174
if (null !== $this->set && $config->getMapped() && $form->isSubmitted() && $form->isSynchronized() && !$form->isDisabled()) {
72-
$returnValue = ($this->set)($data, $form->getData());
75+
try {
76+
$returnValue = ($this->set)($data, $form->getData());
77+
} catch (ExceptionInterface | TypeError $e) {
78+
$form->addError(new FormError($e->getMessage()));
79+
continue;
80+
}
81+
7382
$type = is_object($returnValue) ? get_class($returnValue) : gettype($returnValue);
7483

7584
if (

src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/AccessorMapperTest.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
<?php
22

3+
// Declare strict is necessary here to provoke type errors
4+
declare(strict_types = 1);
5+
36
/*
47
* This file is part of the Symfony package.
58
*
@@ -17,6 +20,7 @@
1720
use Symfony\Component\EventDispatcher\EventDispatcher;
1821
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
1922
use Symfony\Component\Form\DataMapperInterface;
23+
use Symfony\Component\Form\Exception\RuntimeException;
2024
use Symfony\Component\Form\Extension\Core\DataMapper\AccessorMapper;
2125
use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper;
2226
use Symfony\Component\Form\Form;
@@ -236,6 +240,63 @@ public function getEngineClosure()
236240
$this->assertSame('petrol', $data->getEngineClosure());
237241
}
238242

243+
public static function invalidValueProvider(): \Generator
244+
{
245+
yield 'validation error' => ['#Corn is not a valid engine type#', 'corn'];
246+
yield 'type error' => ['#Argument 1 passed to class@anonymous::setEngineClosure\(\) must be of the type string, object given#', new stdClass()];
247+
}
248+
249+
/**
250+
* @dataProvider InvalidValueProvider
251+
*/
252+
public function testSetAccessorCatchesExceptions(string $errorMessagePattern, $value)
253+
{
254+
$this->setupPropertyPathMapper($this->any(), $this->any());
255+
256+
$data = new class('petrol') {
257+
private $engine;
258+
259+
public function __construct(string $engine)
260+
{
261+
$this->engine = $engine;
262+
}
263+
264+
public function setEngineClosure(string $data)
265+
{
266+
if ($data === 'corn') {
267+
throw new class extends RuntimeException
268+
{
269+
public function __construct()
270+
{
271+
parent::__construct('Corn is not a valid engine type');
272+
}
273+
};
274+
}
275+
276+
$this->engine = $data;
277+
}
278+
279+
public function getEngineClosure()
280+
{
281+
return $this->engine;
282+
}
283+
};
284+
285+
$config = new FormConfigBuilder('car', null, $this->dispatcher);
286+
$config->setCompound(true);
287+
$config->setDataMapper($this->createMapper(true, true));
288+
$config->setData($data);
289+
$form = new Form($config);
290+
$form
291+
->add(new Form(new FormConfigBuilder('engine', null, $this->dispatcher)));
292+
293+
$form->submit(['engine' => $value]);
294+
$this->assertFalse($form->isValid());
295+
$this->assertSame('petrol', $data->getEngineClosure());
296+
297+
$this->assertMatchesRegularExpression($errorMessagePattern, (string) $form->get('engine')->getErrors());
298+
}
299+
239300
private function setupPropertyPathMapper(Invocation $dataToFormsMatcher, Invocation $formsToDataMatcher): void
240301
{
241302
$propertyPathMapper = new PropertyPathMapper($this->propertyAccessor);

0 commit comments

Comments
 (0)