8000 [Form] Errors now reference the field they were added to and the viol… · symfony/symfony@c8a0ee6 · GitHub
[go: up one dir, main page]

Skip to content

Commit c8a0ee6

Browse files
committed
[Form] Errors now reference the field they were added to and the violation/exception that caused them
1 parent a596ba3 commit c8a0ee6

File tree

10 files changed

+384
-130
lines changed
  • src/Symfony
  • Validator/ViolationMapper
  • Tests/Extension
  • 10 files changed

    +384
    -130
    lines changed

    src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig

    Lines changed: 24 additions & 5 deletions
    Original file line numberDiff line numberDiff line change
    @@ -191,7 +191,7 @@
    191191
    </div>
    192192

    193193
    {% for formName, formData in collector.data.forms %}
    194-
    {{ form_tree_details(formName, formData) }}
    194+
    {{ form_tree_details(formName, formData, collector.data.forms_by_hash) }}
    195195
    {% endfor %}
    196196
    </div>
    197197
    {% else %}
    @@ -366,7 +366,7 @@
    366366
    </li>
    367367
    {% endmacro %}
    368368

    369-
    {% macro form_tree_details(name, data) %}
    369+
    {% macro form_tree_details(name, data, forms_by_hash) %}
    370370
    <div class="tree-details" id="{{ data.id }}-details">
    371371
    <h2>
    372372
    {{ name }}
    @@ -386,13 +386,32 @@
    386386

    387387
    <table id="{{ data.id }}-errors">
    388388
    <tr>
    389-
    <th width="50%">Message</th>
    389+
    <th>Message</th>
    390+
    <th>Origin</th>
    390391
    <th>Cause</th>
    391392
    </tr>
    392393
    {% for error in data.errors %}
    393394
    <tr>
    394395
    <td>{{ error.message }}</td>
    395-
    <td><em>Unknown.</em></td>
    396+
    <td>
    397+
    {% if error.origin is empty %}
    398+
    <em>This form.</em>
    399+
    {% elseif forms_by_hash[error.origin] is not defined %}
    400+
    <em>Unknown.</em>
    401+
    {% else %}
    402+
    {{ forms_by_hash[error.origin].name }}
    403+
    {% endif %}
    404+
    </td>
    405+
    <td>
    406+
    {% if error.cause is empty %}
    407+
    <em>Unknown.</em>
    408+
    {% elseif error.cause.root is defined %}
    409+
    <strong>Constraint Violation</strong><br/>
    410+
    <pre>{{ error.cause.root }}{% if error.cause.path is not empty %}{% if error.cause.path|first != '[' %}.{% endif %}{{ error.cause.path }}{% endif %} = {{ error.cause.value }}</pre>
    411+
    {% else %}
    412+
    <pre>{{ error.cause }}</pre>
    413+
    {% endif %}
    414+
    </td>
    396415
    </tr>
    397416
    {% endfor %}
    398417
    </table>
    @@ -565,6 +584,6 @@
    565584
    </div>
    566585

    567586
    {% for childName, childData in data.children %}
    568-
    {{ _self.form_tree_details(childName, childData) }}
    587+
    {{ _self.form_tree_details(childName, childData, forms_by_hash) }}
    569588
    {% endfor %}
    570589
    {% endmacro %}

    src/Symfony/Component/Form/CHANGELOG.md

    Lines changed: 2 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -5,6 +5,8 @@ CHANGELOG
    55
    ------
    66

    77
    * added an option for multiple files upload
    8+
    * form errors now reference their cause (constraint violation, exception, ...)
    9+
    * form errors now remember which form they were originally added to
    810

    911
    2.4.0
    1012
    -----

    src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php

    Lines changed: 11 additions & 6 deletions
    Original file line numberDiff line numberDiff line change
    @@ -69,6 +69,7 @@ public function __construct(FormDataExtractorInterface $dataExtractor)
    6969
    $this->dataExtractor = $dataExtractor;
    7070
    $this->data = array(
    7171
    'forms' => array(),
    72+
    'forms_by_hash' => array(),
    7273
    'nb_errors' => 0,
    7374
    );
    7475
    }
    @@ -184,7 +185,7 @@ public function buildPreliminaryFormTree(FormInterface $form)
    184185
    {
    185186
    $this->data['forms'][$form->getName()] = array();
    186187

    187-
    $this->recursiveBuildPreliminaryFormTree($form, $this->data['forms'][$form->getName()]);
    188+
    $this->recursiveBuildPreliminaryFormTree($form, $this->data['forms'][$form->getName()], $this->data['forms_by_hash']);
    188189
    }
    189190

    190191
    /**
    @@ -194,7 +195,7 @@ public function buildFinalFormTree(FormInterface $form, FormView $view)
    194195
    {
    195196
    $this->data['forms'][$form->getName()] = array();
    196197

    197-
    $this->recursiveBuildFinalFormTree($form, $view, $this->data['forms'][$form->getName()]);
    198+
    $this->recursiveBuildFinalFormTree($form, $view, $this->data['forms'][$form->getName()], $this->data['forms_by_hash']);
    198199
    }
    199200

    200201
    /**
    @@ -213,24 +214,26 @@ public function getData()
    213214
    return $this->data;
    214215
    }
    215216

    216-
    private function recursiveBuildPreliminaryFormTree(FormInterface $form, &$output = null)
    217+
    private function recursiveBuildPreliminaryFormTree(FormInterface $form, &$output = null, array &$outputByHash)
    217218
    {
    218219
    $hash = spl_object_hash($form);
    219220

    220221
    $output = isset($this->dataByForm[$hash])
    221222
    ? $this->dataByForm[$hash]
    222223
    : array();
    223224

    225+
    $outputByHash[$hash] = &$output;
    226+
    224227
    $output['children'] = array();
    225228

    226229
    foreach ($form as $name => $child) {
    227230
    $output['children'][$name] = array();
    228231

    229-
    $this->recursiveBuildPreliminaryFormTree($child, $output['children'][$name]);
    232+
    $this->recursiveBuildPreliminaryFormTree($child, $output['children'][$name], $outputByHash);
    230233
    }
    231234
    }
    232235

    233-
    private function recursiveBuildFinalFormTree(FormInterface $form = null, FormView $view, &$output = null)
    236+
    private function recursiveBuildFinalFormTree(FormInterface $form = null, FormView $view, &$output = null, array &$outputByHash)
    234237
    {
    235238
    $viewHash = spl_object_hash($view);
    236239
    $formHash = null;
    @@ -255,6 +258,8 @@ private function recursiveBuildFinalFormTree(FormInterface $form = null, FormVie
    255258
    ? $this->dataByForm[$formHash]
    256259
    : array()
    257260
    );
    261+
    262+
    $outputByHash[$formHash] = &$output;
    258263
    }
    259264

    260265
    $output['children'] = array();
    @@ -268,7 +273,7 @@ private function recursiveBuildFinalFormTree(FormInterface $form = null, FormVie
    268273

    269274
    $output['children'][$name] = array();
    270275

    271-
    $this->recursiveBuildFinalFormTree($childForm, $childView, $output['children'][$name]);
    276+
    $this->recursiveBuildFinalFormTree($childForm, $childView, $output['children'][$name], $outputByHash);
    272277
    }
    273278
    }
    274279
    }

    src/Symfony/Component/Form/Extension/DataCollector/FormDataExtractor.php

    Lines changed: 26 additions & 3 deletions
    Original file line numberDiff line numberDiff line change
    @@ -14,6 +14,7 @@
    1414
    use Symfony\Component\Form\FormInterface;
    1515
    use Symfony\Component\Form\FormView;
    1616
    use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter;
    17+
    use Symfony\Component\Validator\ConstraintViolationInterface;
    1718

    1819
    /**
    1920
    * Default implementation of {@link FormDataExtractorInterface}.
    @@ -43,6 +44,7 @@ public function extractConfiguration(FormInterface $form)
    4344
    {
    4445
    $data = array(
    4546
    'id' => $this->buildId($form),
    47+
    'name' => $form->getName(),
    4648
    'type' => $form->getConfig()->getType()->getName(),
    4749
    'type_class' => get_class($form->getConfig()->getType()->getInnerType()),
    4850
    'synchronized' => $this->valueExporter->exportValue($form->isSynchronized()),
    @@ -108,9 +110,26 @@ public function extractSubmittedData(FormInterface $form)
    108110
    }
    109111

    110112
    foreach ($form->getErrors() as $error) {
    111-
    $data['errors'][] = array(
    113+
    $errorData = array(
    112114
    'message' => $error->getMessage(),
    115+
    'origin' => is_object($error->getOrigin())
    116+
    ? spl_object_hash($error->getOrigin())
    117+
    : null,
    113118
    );
    119+
    120+
    $cause = $error->getCause();
    121+
    122+
    if ($cause instanceof ConstraintViolationInterface) {
    123+
    $errorData['cause'] = array(
    124+
    'root' => $this->valueExporter->exportValue($cause->getRoot()),
    125+
    'path' => $this->valueExporter->exportValue($cause->getPropertyPath()),
    126+
    'value' => $this->valueExporter->exportValue($cause->getInvalidValue()),
    127+
    );
    128+
    } else {
    129+
    $errorData['cause'] = null !== $cause ? $this->valueExporter->exportValue($cause) : null;
    130+
    }
    131+
    132+
    $data['errors'][] = $errorData;
    114133
    }
    115134

    116135
    $data['synchronized'] = $this->valueExporter->exportValue($form->isSynchronized());
    @@ -127,8 +146,12 @@ public function extractViewVariables(FormView $view)
    127146

    128147
    // Set the ID in case no FormInterface object was collected for this
    129148
    // view
    130-
    if (isset($view->vars['id'])) {
    131-
    $data['id'] = $view->vars['id'];
    149+
    if (!isset($data['id'])) {
    150+
    $data['id'] = isset($view->vars['id']) ? $view->vars['id'] : null;
    151+
    }
    152+
    153+
    if (!isset($data['name'])) {
    154+
    $data['name'] = isset($view->vars['name']) ? $view->vars['name'] : null;
    132155
    }
    133156

    134157
    foreach ($view->vars as $varName => $value) {

    src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php

    Lines changed: 2 additions & 1 deletion
    Original file line numberDiff line numberDiff line change
    @@ -128,7 +128,8 @@ public function mapViolation(ConstraintViolation $violation, FormInterface $form
    128128
    $violation->getMessage(),
    129129
    $violation->getMessageTemplate(),
    130130
    $violation->getMessageParameters(),
    131-
    $violation->getMessagePluralization()
    131+
    $violation->getMessagePluralization(),
    132+
    $violation
    132133
    ));
    133134
    }
    134135
    }

    src/Symfony/Component/Form/Form.php

    Lines changed: 4 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -673,6 +673,10 @@ public function bind($submittedData)
    673673
    public function addError(FormError $error)
    674674
    {
    675675
    if ($this->parent && $this->config->getErrorBubbling()) {
    676+
    if (null === $error->getOrigin()) {
    677+
    $error->setOrigin($this);
    678+
    }
    679+
    676680
    $this->parent->addError($error);
    677681
    } else {
    678682
    $this->errors[] = $error;

    src/Symfony/Component/Form/FormError.php

    Lines changed: 83 additions & 3 deletions
    Original file line numberDiff line numberDiff line change
    @@ -11,12 +11,14 @@
    1111

    1212
    namespace Symfony\Component\Form;
    1313

    14+
    use Symfony\Component\Form\Exception\BadMethodCallException;
    15+
    1416
    /**
    1517
    * Wraps errors in forms
    1618
    *
    1719
    * @author Bernhard Schussek <bschussek@gmail.com>
    1820
    */
    19-
    class FormError
    21+
    class FormError implements \Serializable
    2022
    {
    2123
    /**
    2224
    * @var string
    @@ -41,6 +43,18 @@ class FormError
    4143
    */
    4244
    protected $messagePluralization;
    4345

    46+
    /**
    47+
    * The cause for this error
    48+
    * @var mixed
    49+
    */
    50+
    private $cause;
    51+
    52+
    /**
    53+
    * The form that spawned this error
    54+
    * @var FormInterface
    55+
    */
    56+
    private $origin;
    57+
    4458
    /**
    4559
    * Constructor
    4660
    *
    @@ -50,17 +64,19 @@ class FormError
    5064
    * @param string $message The translated error message
    5165
    * @param string|null $messageTemplate The template for the error message
    5266
    * @param array $messageParameters The parameters that should be
    53-
    * substituted in the message template.
    67+
    * substituted in the message template
    5468
    * @param integer|null $messagePluralization The value for error message pluralization
    69+
    * @param mixed $cause The cause of the error
    5570
    *
    5671
    * @see \Symfony\Component\Translation\Translator
    5772
    */
    58-
    public function __construct($message, $messageTemplate = null, array $messageParameters = array(), $messagePluralization = null)
    73+
    public function __construct($message, $messageTemplate = null, array $messageParameters = array(), $messagePluralization = null, $cause = null)
    5974
    {
    6075
    $this->message = $message;
    6176
    $this->messageTemplate = $messageTemplate ?: $message;
    6277
    $this->messageParameters = $messageParameters;
    6378
    $this->messagePluralization = $messagePluralization;
    79+
    $this->cause = $cause;
    6480
    }
    6581

    6682
    /**
    @@ -102,4 +118,68 @@ public function getMessagePluralization()
    102118
    {
    103119
    return $this->messagePluralization;
    104120
    }
    121+
    122+
    /**
    123+
    * Returns the cause of this error.
    124+
    *
    125+
    * @return mixed The cause of this error
    126+
    */
    127+
    public function getCause()
    128+
    {
    129+
    return $this->cause;
    130+
    }
    131+
    132+
    /**
    133+
    * Sets the form that caused this error.
    134+
    *
    135+
    * This method must only be called once.
    136+
    *
    137+
    * @param FormInterface $origin The form that caused this error
    138+
    *
    139+
    * @throws BadMethodCallException If the method is called more than once
    140+
    */
    141+
    public function setOrigin(FormInterface $origin)
    142+
    {
    143+
    if (null !== $this->origin) {
    144+
    throw new BadMethodCallException('setOrigin() must only be called once.');
    145+
    }
    146+
    147+
    $this->origin = $origin;
    148+
    }
    149+
    150+
    /**
    151+
    * Returns the form that caused this error.
    152+
    *
    153+
    * @return FormInterface The form that caused this error
    154+
    */
    155+
    public function getOrigin()
    156+
    {
    157+
    return $this->origin;
    158+
    }
    159+
    160+
    /**
    161+
    * Serializes this error.
    162+
    *
    163+
    * @return string The serialized error
    164+
    */
    165+
    public function serialize()
    166+
    {
    167+
    return serialize(array(
    168+
    $this->message,
    169+
    $this->messageTemplate,
    170+
    $this->messageParameters,
    171+
    $this->messagePluralization,
    172+
    $this->cause
    173+
    ));
    174+
    }
    175+
    176+
    /**
    177+
    * Unserializes a serialized error.
    178+
    *
    179+
    * @param string $serialized The serialized error
    180+
    */
    181+
    public function unserialize($serialized)
    182+
    {
    183+
    list($this->message, $this->messageTemplate, $this->messageParameters, $this->messagePluralization, $this->cause) = unserialize($serialized);
    184+
    }
    105185
    }

    0 commit comments

    Comments
     (0)
    0