Don't Panic Don't Panic - 8 months ago 135
PHP Question

Laravel checkbox state after failed validation

There are many many questions here on SO about how to properly persist checkbox fields in Laravel after form submission (
example,
example,
example
), this question is not about that.

Please note I am also well aware of Laravel's

method, and that it accepts a default value. My question is about a particular case where
old()
does not seem to work for a checkbox.

I'm working on an edit form, updating data already in the DB. The form will initially be populated with the values from the DB.

The standard approach to re-populating form inputs after failed validation is to use
old()
. For any type of field which always returns a value even if the submitted value was empty (like text fields), something like the following works:

<input type='text' name='unicorns' value='{{ old('unicorns', $model->unicorns) }}'>


On initial page load (before the form is submitted), this works fine as
old('unicorns')
is
null
so the default of
$model->unicorns
will be used, and the text field's value reflects the DB state.

After submitting a new value, which fails validation,
old('unicorns')
will be the new value, even if the new value was nothing, and again the text field's value reflects the submitted state, as we want.

However this breaks down for checkboxes, when the unchecked checkbox does not appear in the request at all. Consider:

<input type='checkbox' name='unicorns' value='1' @if old('unicorns', $model->unicorns) checked @endif>


On initial page load this works fine, just like for text fields. But after failed validation:


  • Case 1: DB state
    0
    (unchecked), submitted state checked.
    old('unicorns')
    will be
    1
    , so the test evaluates
    true
    , the checkbox is checked - OK!

  • Case 2: DB state
    1
    (checked), submitted state unchecked. Since
    old('unicorns')
    is null,
    old()
    falls back to the default value
    $model->unicorns
    (
    1
    ), so the test evaluates
    true
    , the checkbox is checked - FAIL!



How to solve this?

I initially thought the best way around this would be to test if there has been a validation error - if so, use the
old()
value, with no default fallback; if not, use
old()
with default fallback. One way to test for failed validation is to check
$errors
. This seems to work, but it only works within a view AFAICT, as:


The $errors variable is bound to the view ...


I'd like to create a global helper for this, and
$errors
won't work there. Anyway, this approach feels ... clunky and too complicated.

Am I missing something? Is there a neat Laravel way of solving this? It seems odd that
old()
simply does not work for this particular case.

This is not a new problem, how do others handle this?

Answer Source

This is the clunky and complicated solution I referred to in the question, which I'm using as I can't find anything better. If anyone can suggest a better solution I'd be happy to accept (and use!) it.

I have an Html helper, created as described in this question, and aliased in config/app.php as described in the first part of this answer to that question. I added a static method to it:

namespace App\Helpers;
use Illuminate\Support\ViewErrorBag;

class Html {
    /**
     * Shortcut for populating checkbox state on forms.
     *
     * If there are failed validation errors, we should use the old() state
     * of the checkbox, ignoring the current DB state of the field; otherwise
     * just use the DB value.
     *
     * @param string $name The input field name
     * @param string $value The current DB value
     * @return string 'checked' or null;
     */
    public static function checked($name, $value) {
        $errors = session('errors');

        if (isset($errors) && $errors instanceof ViewErrorBag && $errors->any()) {
            return old($name) ? 'checked' : '';
        }

        return ($value) ? 'checked' : '';
    }
}

Now in my views I can use it like so:

<input type='checkbox' name='unicorns' value='1' {{ \Html::checked('unicorns', $model->unicorns) }}>

This correctly handles all combinations of DB and submitted state, on initial load and after failed validation.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download