0

I'm working with forms where the checkboxes are present, but the target model's fields must be in boolean type, since they are defined in my migrations as boolean. For example:

$table->boolean('is_active')->default(true); 

The Model's values are filled in this way:

 foreach (static::getFillableFields() as $field) { $entry->$field = $request->input($field); } 

So I added the cast to make this field boolean:

class Entry extends Model { protected $casts = [ 'is_active' => 'boolean', ]; 

But now what I see: when the form's checkbox is checked and I have '1' string in the request, it works well - '1' gives 'true' when I access $entry->is_active then. But when checkbox isn't checked, it gives the 'null' value, and - I don't know why - when the model's field is set to null, then it returns null (not 'false', as I expected). Why is it so? This makes casts useless in my case. Can I change this behavior?

I'm not too inspired with idea of adding this (accessors/mutators) for every boolean field (but in fact this results in what I need):

public function setIsActiveAttribute($value) { $this->attributes['is_active'] = (bool)$value; } public function getIsActiveAttribute(bool $value): bool { return $value; } 
3
  • You could do $this->attributes['is_active'] = is_null($value) ? false : (bool)$value? Commented Aug 7, 2017 at 20:17
  • @Omisakin Oluwatobi, thanks. See my comment to Devon's reply. Commented Aug 7, 2017 at 20:26
  • seems the issue has been on ground github.com/laravel/framework/issues/14942 Someone suggested strict to be on here: github.com/laravel/framework/issues/… (even though it relates to groupBy() yet its actually the solution to my understanding) Commented Aug 7, 2017 at 20:42

3 Answers 3

2

As @Devon mentioned, checkboxes that are not checked are not included in the request data sent to the controller. The HTML spec deems unchecked checkboxes as unsuccessful, and therefore does not submit them.

One trick that is used to get around this "limitation", however, is to add a hidden input to your HTML that has the same name as your checkbox, but contains the false value. This hidden input must come before your checkbox input.

This will allow you to continue to use your mass-assignment functionality.

So, your form should look something like:

<input type="hidden" name="is_active" value="0" /> <input type="checkbox" name="is_active" value="1" /> 

Now, when the form is submitted with an unchecked checkbox, the hidden input will ensure the input value exists in the request data with the false value (0).

When the form is submitted with a checked checkbox, both inputs will submit successfully with the same name, but the server side will only take the last value it sees. This is why the checkbox must come after the hidden input field, so that the last value the server sees is the successful value defined on the checkbox (1).

As a side note, this is also how the Ruby on Rails form helper handles checkboxes. From their documentation.

The HTML specification says unchecked check boxes are not successful, and thus web browsers do not send them. Unfortunately this introduces a gotcha: if an Invoice model has a paid flag, and in the form that edits a paid invoice the user unchecks its check box, no paid parameter is sent. So, any mass-assignment idiom like

@invoice.update(params[:invoice])

wouldn’t update the flag.

To prevent this the helper generates an auxiliary hidden field before the very check box. The hidden field has the same name and its attributes mimic an unchecked check box.

This way, the client either sends only the hidden field (representing the check box is unchecked), or both fields. Since the HTML specification says key/value pairs have to be sent in the same order they appear in the form, and parameters extraction gets the last occurrence of any repeated key in the query string, that works for ordinary forms.

Sign up to request clarification or add additional context in comments.

Comments

1

This is a controller issue, not a model issue. HTML checkboxes will not have a value if they are unchecked, this is how they work.

Therefore, when retrieving the value from your Request object in the controller, you should set the default value as false.

Example in controller method:

$model->is_active = $request->input('is_active', false); 

If you leave the second argument of input() empty, it will default to null.

1 Comment

Good idea, thanks. But I'd prefer using simple filling with foreach (not specifing any individual fields), as it makes my store/update actions universal. That's why I decided to use Casts, not a type converting in my controller (as it refers more to the Model logics, not Controller's)
0

Suddenly: casts don't work for Model::save() method. laravel eloquent model casts on save?

So all that's left for me is to use accessors/mutators..

1 Comment

$casts works fine. It's a tool to cast attributes when you hydrate (read a model from a database), but not when you dehydrate it (save). To achieve the result you want, you need either to use default parameters with input or customize save (may be through events / boot) and set what is not set, when necessary.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.