diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig
index 6d6d07d240ab..736f718ccf13 100644
--- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig
+++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig
@@ -473,14 +473,28 @@
{% endif %}
- {% if error.cause is empty %}
- Unknown.
- {% elseif error.cause.root is defined %}
- Constraint Violation
- {{ error.cause.root }}{% if error.cause.path is not empty %}{% if error.cause.path|first != '[' %}.{% endif %}{{ error.cause.path }}{% endif %} = {{ error.cause.value }}
+ {% for trace in error.trace %}
+ {% if not loop.first %}
+ Caused by:
+ {% endif %}
+ {% if trace.root is defined %}
+ {{ trace.class }}
+
+ {{- trace.root -}}
+ {%- if trace.path is not empty -%}
+ {%- if trace.path|first != '[' %}.{% endif -%}
+ {{- trace.path -}}
+ {%- endif %} = {{ trace.value -}}
+
+ {% elseif trace.message is defined %}
+ {{ trace.class }}
+ {{ trace.message }}
+ {% else %}
+ {{ trace }}
+ {% endif %}
{% else %}
- {{ error.cause }}
- {% endif %}
+ Unknown.
+ {% endfor %}
|
{% endfor %}
diff --git a/src/Symfony/Component/Form/Button.php b/src/Symfony/Component/Form/Button.php
index 380b20f4ddc2..0ba4c8138cf3 100644
--- a/src/Symfony/Component/Form/Button.php
+++ b/src/Symfony/Component/Form/Button.php
@@ -344,6 +344,15 @@ public function isSynchronized()
return true;
}
+ /**
+ * Unsupported method.
+ *
+ * @return null Always returns null
+ */
+ public function getTransformationFailure()
+ {
+ }
+
/**
* Unsupported method.
*
diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md
index 4381726126d4..dfc2e89ea7cd 100644
--- a/src/Symfony/Component/Form/CHANGELOG.md
+++ b/src/Symfony/Component/Form/CHANGELOG.md
@@ -21,6 +21,7 @@ CHANGELOG
* ObjectChoiceList now compares choices by their value, if a value path is
given
* you can now pass interface names in the "data_class" option
+ * [BC BREAK] added `FormInterface::getTransformationFailure()`
2.4.0
-----
diff --git a/src/Symfony/Component/Form/Extension/DataCollector/FormDataExtractor.php b/src/Symfony/Component/Form/Extension/DataCollector/FormDataExtractor.php
index 80e910177a89..0ae5560afeab 100644
--- a/src/Symfony/Component/Form/Extension/DataCollector/FormDataExtractor.php
+++ b/src/Symfony/Component/Form/Extension/DataCollector/FormDataExtractor.php
@@ -115,18 +115,39 @@ public function extractSubmittedData(FormInterface $form)
'origin' => is_object($error->getOrigin())
? spl_object_hash($error->getOrigin())
: null,
+ 'trace' => array(),
);
$cause = $error->getCause();
- if ($cause instanceof ConstraintViolationInterface) {
- $errorData['cause'] = array(
- 'root' => $this->valueExporter->exportValue($cause->getRoot()),
- 'path' => $this->valueExporter->exportValue($cause->getPropertyPath()),
- 'value' => $this->valueExporter->exportValue($cause->getInvalidValue()),
- );
- } else {
- $errorData['cause'] = null !== $cause ? $this->valueExporter->exportValue($cause) : null;
+ while (null !== $cause) {
+ if ($cause instanceof ConstraintViolationInterface) {
+ $errorData['trace'][] = array(
+ 'class' => $this->valueExporter->exportValue(get_class($cause)),
+ 'root' => $this->valueExporter->exportValue($cause->getRoot()),
+ 'path' => $this->valueExporter->exportValue($cause->getPropertyPath()),
+ 'value' => $this->valueExporter->exportValue($cause->getInvalidValue()),
+ );
+
+ $cause = method_exists($cause, 'getCause') ? $cause->getCause() : null;
+
+ continue;
+ }
+
+ if ($cause instanceof \Exception) {
+ $errorData['trace'][] = array(
+ 'class' => $this->valueExporter->exportValue(get_class($cause)),
+ 'message' => $this->valueExporter->exportValue($cause->getMessage()),
+ );
+
+ $cause = $cause->getPrevious();
+
+ continue;
+ }
+
+ $errorData['trace'][] = $cause;
+
+ break;
}
$data['errors'][] = $errorData;
diff --git a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php
index 1fe114705107..7c0dc44ab180 100644
--- a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php
+++ b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php
@@ -103,6 +103,7 @@ public function validate($form, Constraint $constraint)
->setParameters(array_replace(array('{{ value }}' => $clientDataAsString), $config->getOption('invalid_message_parameters')))
->setInvalidValue($form->getViewData())
->setCode(Form::ERR_INVALID)
+ ->setCause($form->getTransformationFailure())
->addViolation();
}
}
diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php
index 90048d3fb558..9b259e820b13 100644
--- a/src/Symfony/Component/Form/Form.php
+++ b/src/Symfony/Component/Form/Form.php
@@ -121,12 +121,10 @@ class Form implements \IteratorAggregate, FormInterface
private $extraData = array();
/**
- * Whether the data in model, normalized and view format is
- * synchronized. Data may not be synchronized if transformation errors
- * occur.
- * @var bool
+ * Returns the transformation failure generated during submission, if any
+ * @var TransformationFailedException|null
*/
- private $synchronized = true;
+ private $transformationFailure;
/**
* Whether the form's data has been initialized.
@@ -634,7 +632,7 @@ public function submit($submittedData, $clearMissing = true)
$viewData = $this->normToView($normData);
}
} catch (TransformationFailedException $e) {
- $this->synchronized = false;
+ $this->transformationFailure = $e;
// If $viewData was not yet set, set it to $submittedData so that
// the erroneous data is accessible on the form.
@@ -711,7 +709,15 @@ public function isBound()
*/
public function isSynchronized()
{
- return $this->synchronized;
+ return null === $this->transformationFailure;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getTransformationFailure()
+ {
+ return $this->transformationFailure;
}
/**
diff --git a/src/Symfony/Component/Form/FormInterface.php b/src/Symfony/Component/Form/FormInterface.php
index 274ce04e620a..1b9e6e47775d 100644
--- a/src/Symfony/Component/Form/FormInterface.php
+++ b/src/Symfony/Component/Form/FormInterface.php
@@ -11,6 +11,8 @@
namespace Symfony\Component\Form;
+use Symfony\Component\Form\Exception\TransformationFailedException;
+
/**
* A form group bundling multiple forms in a hierarchical structure.
*
@@ -230,10 +232,20 @@ public function isEmpty();
/**
* Returns whether the data in the different formats is synchronized.
*
+ * If the data is not synchronized, you can get the transformation failure
+ * by calling {@link getTransformationFailure()}.
+ *
* @return bool
*/
public function isSynchronized();
+ /**
+ * Returns the data transformation failure, if any.
+ *
+ * @return TransformationFailedException|null The transformation failure
+ */
+ public function getTransformationFailure();
+
/**
* Initializes the form tree.
*
diff --git a/src/Symfony/Component/Form/Tests/Extension/DataCollector/FormDataExtractorTest.php b/src/Symfony/Component/Form/Tests/Extension/DataCollector/FormDataExtractorTest.php
index 77b2d4472a06..deb6069ee2f5 100644
--- a/src/Symfony/Component/Form/Tests/Extension/DataCollector/FormDataExtractorTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/DataCollector/FormDataExtractorTest.php
@@ -319,7 +319,7 @@ public function testExtractSubmittedDataStoresErrors()
'norm' => "'Foobar'",
),
'errors' => array(
- array('message' => 'Invalid!', 'origin' => null, 'cause' => null),
+ array('message' => 'Invalid!', 'origin' => null, 'trace' => array()),
),
'synchronized' => 'true',
), $this->dataExtractor->extractSubmittedData($form));
@@ -340,7 +340,7 @@ public function testExtractSubmittedDataStoresErrorOrigin()
'norm' => "'Foobar'",
),
'errors' => array(
- array('message' => 'Invalid!', 'origin' => spl_object_hash($form), 'cause' => null),
+ array('message' => 'Invalid!', 'origin' => spl_object_hash($form), 'trace' => array()),
),
'synchronized' => 'true',
), $this->dataExtractor->extractSubmittedData($form));
@@ -360,7 +360,12 @@ public function testExtractSubmittedDataStoresErrorCause()
'norm' => "'Foobar'",
),
'errors' => array(
- array('message' => 'Invalid!', 'origin' => null, 'cause' => 'object(Exception)'),
+ array('message' => 'Invalid!', 'origin' => null, 'trace' => array(
+ array(
+ 'class' => "'Exception'",
+ 'message' => "''",
+ ),
+ )),
),
'synchronized' => 'true',
), $this->dataExtractor->extractSubmittedData($form));
diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php
index 99c5e584c1ae..877797f16389 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php
@@ -225,11 +225,14 @@ function () { throw new TransformationFailedException(); }
$this->validator->validate($form, new Form());
+ $is2Dot4Api = Validation::API_VERSION_2_4 === $this->getApiVersion();
+
$this->buildViolation('invalid_message_key')
->setParameter('{{ value }}', 'foo')
->setParameter('{{ foo }}', 'bar')
->setInvalidValue('foo')
->setCode(Form::ERR_INVALID)
+ ->setCause($is2Dot4Api ? null : $form->getTransformationFailure())
->assertRaised();
}
@@ -259,11 +262,14 @@ function () { throw new TransformationFailedException(); }
$this->validator->validate($form, new Form());
+ $is2Dot4Api = Validation::API_VERSION_2_4 === $this->getApiVersion();
+
$this->buildViolation('invalid_message_key')
->setParameter('{{ value }}', 'foo')
->setParameter('{{ foo }}', 'bar')
->setInvalidValue('foo')
->setCode(Form::ERR_INVALID)
+ ->setCause($is2Dot4Api ? null : $form->getTransformationFailure())
->assertRaised();
}
@@ -293,10 +299,13 @@ function () { throw new TransformationFailedException(); }
$this->validator->validate($form, new Form());
+ $is2Dot4Api = Validation::API_VERSION_2_4 === $this->getApiVersion();
+
$this->buildViolation('invalid_message_key')
->setParameter('{{ value }}', 'foo')
->setInvalidValue('foo')
->setCode(Form::ERR_INVALID)
+ ->setCause($is2Dot4Api ? null : $form->getTransformationFailure())
->assertRaised();
}