<p dir="auto">From <a href="http://symfony.com/doc/current/reference/forms/types/repeated.html" rel="nofollow">the docs</a>:</p>
<blockquote>
<p dir="auto">This is a special field "group", that creates two identical fields whose values must match (or a validation error is thrown). The most common use is when you need the user to repeat their password or email to verify accuracy.</p>
</blockquote>
<p dir="auto">In other words, the form should not be valid if values are mismatched. But when a form is submitted with mismatched values in a repeated type <code class="notranslate">Form::isValid</code> returns true. No validation error is thrown, and the value from the repeated field is null. The null value <a href="https://github.com/symfony/symfony/blob/f8fb8a98b2f9d797483a020cee3ec6ad2e82c366/src/Symfony/Component/Form/Tests/Extension/Core/Type/RepeatedTypeTest.php#L167">seems to be expected</a>, but <strong>why is the form a valid submission?</strong></p>
<p dir="auto">There is the correct error in <code class="notranslate">Form::$transformationFailure</code> but it doesn't seem to impact the form errors.</p>
<p dir="auto"><a href="https://gist.github.com/chrisguitarguy/25436e46764719aa0f54">Here is a little silex app</a> with a repeated field to illustrate.</p>
<p dir="auto">If this is expected behavior feel free to close. If that is the case the docs need some work to clear up expectations about what the repeated type does.</p>
<p dir="auto">I was using Silex with Symfony 2.5.7. Form validation broke when I updated to version 2.6.0.</p>
<p dir="auto">Here's the <code class="notranslate">buildForm</code> method defined for my <code class="notranslate">PasswordResetType</code>:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="function buildForm(FormBuilderInterface $builder, array $options) {
        $builder
            -&gt;add('password', 'repeated', array(
                'type' =&gt; 'password',
                'invalid_message' =&gt; $this-&gt;app['translator']-&gt;trans('form_errors.pass_must_match'),
                'required' =&gt; true,
                'first_options' =&gt; array(
                    'constraints' =&gt; array(
                        new Assert\NotBlank()
                    ),
                ),
                'second_options' =&gt; array(
                    'constraints' =&gt; array(
                        new Assert\NotBlank()
                    )
                )
            ))
        ;
    }
}"><pre class="notranslate"><code class="notranslate">function buildForm(FormBuilderInterface $builder, array $options) {
        $builder
            -&gt;add('password', 'repeated', array(
                'type' =&gt; 'password',
                'invalid_message' =&gt; $this-&gt;app['translator']-&gt;trans('form_errors.pass_must_match'),
                'required' =&gt; true,
                'first_options' =&gt; array(
                    'constraints' =&gt; array(
                        new Assert\NotBlank()
                    ),
                ),
                'second_options' =&gt; array(
                    'constraints' =&gt; array(
                        new Assert\NotBlank()
                    )
                )
            ))
        ;
    }
}
</code></pre></div>
<p dir="auto">The Twig template associated to my form:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="{{ form_start(form) }}
    {{ form_widget(form._token) }}
    {{ form_errors(form) }}

    &lt;div class=&quot;form-group&quot;&gt;
        {{ form_label(form.password.first, 'user.new_password'|trans, {'label_attr': {'class': 'control-label'}}) }}
        {{ form_errors(form.password.first) }}
        {{ form_widget(form.password.first, {'attr': {'class': 'form-control', 'placeholder' : 'user.new_password'|trans}}) }}
    &lt;/div&gt;

    &lt;div class=&quot;form-group&quot;&gt;
        {{ form_label(form.password.second, 'user.new_password_confirmation'|trans, {'label_attr': {'class': 'control-label'}}) }}
        {{ form_errors(form.password.second) }}
        {{ form_widget(form.password.second, {'attr': {'class': 'form-control', 'placeholder' : 'user.new_password_confirmation'|trans}}) }}
    &lt;/div&gt;
    &lt;button type=&quot;submit&quot; class=&quot;btn btn-primary btn-block&quot;&gt;{{'user.reset_password'|trans}}&lt;/button&gt;
{{ form_end(form) }}"><pre class="notranslate"><code class="notranslate">{{ form_start(form) }}
    {{ form_widget(form._token) }}
    {{ form_errors(form) }}

    &lt;div class="form-group"&gt;
        {{ form_label(form.password.first, 'user.new_password'|trans, {'label_attr': {'class': 'control-label'}}) }}
        {{ form_errors(form.password.first) }}
        {{ form_widget(form.password.first, {'attr': {'class': 'form-control', 'placeholder' : 'user.new_password'|trans}}) }}
    &lt;/div&gt;

    &lt;div class="form-group"&gt;
        {{ form_label(form.password.second, 'user.new_password_confirmation'|trans, {'label_attr': {'class': 'control-label'}}) }}
        {{ form_errors(form.password.second) }}
        {{ form_widget(form.password.second, {'attr': {'class': 'form-control', 'placeholder' : 'user.new_password_confirmation'|trans}}) }}
    &lt;/div&gt;
    &lt;button type="submit" class="btn btn-primary btn-block"&gt;{{'user.reset_password'|trans}}&lt;/button&gt;
{{ form_end(form) }}
</code></pre></div>
<p dir="auto">The associated controllers:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="$app-&gt;get('/{_locale}/reset_password/{token}', function(Request $request, $token) use ($app) {
    /* ... */

    $form = $app['form.factory']-&gt;create(new PasswordResetType($app), array());

    return $app['twig']-&gt;render('password_reset.twig', array(
        'title' =&gt; 'Page title',
        'form' =&gt; $form-&gt;createView()
    ));
})-&gt;bind('reset_password');

$app-&gt;post('/{_locale}/reset_password/{token}', function(Request $request, $token) use ($app) {
    /* ... */

    $form = $app['form.factory']-&gt;create(new PasswordResetType($app), array());

    $form-&gt;handleRequest($request);

    if ($form-&gt;isValid()) {
        $data = $form-&gt;getData();

        $password = $data['password']; // this is line 113

        /* ... */
    }
});"><pre class="notranslate"><code class="notranslate">$app-&gt;get('/{_locale}/reset_password/{token}', function(Request $request, $token) use ($app) {
    /* ... */

    $form = $app['form.factory']-&gt;create(new PasswordResetType($app), array());

    return $app['twig']-&gt;render('password_reset.twig', array(
        'title' =&gt; 'Page title',
        'form' =&gt; $form-&gt;createView()
    ));
})-&gt;bind('reset_password');

$app-&gt;post('/{_locale}/reset_password/{token}', function(Request $request, $token) use ($app) {
    /* ... */

    $form = $app['form.factory']-&gt;create(new PasswordResetType($app), array());

    $form-&gt;handleRequest($request);

    if ($form-&gt;isValid()) {
        $data = $form-&gt;getData();

        $password = $data['password']; // this is line 113

        /* ... */
    }
});
</code></pre></div>
<p dir="auto">This generates the following error when passwords don't match: <code class="notranslate">Error: ContextErrorException in controllers_users.php line 113: Notice: Undefined index: password</code>. This indicates <code class="notranslate">$form-&gt;isValid()</code> returned <code class="notranslate">true</code> while it shouldn't have, since the passwords don't match.</p>