enum-eloquent icon indicating copy to clipboard operation
enum-eloquent copied to clipboard

Proper Support For Laravel Forms

Open fulopattila122 opened this issue 6 years ago • 0 comments

Problem

Scenario

  • Using an enum on a model class;
  • Using the Laravel Forms package;
  • Invoking the form with Form::model();
  • Rendering radio buttons within the form.

Blade Example:

{!! Form::model($issue, ['route' => ['stift.issue.update', $issue], 'method' => 'PUT']) !!}

@foreach($statuses as $status => $statusLabel)
    <label class="radio-inline" for="status_{{ $status }}">
        {{ Form::radio('status', $status, ($issue->status->value() == $status), ['id' => "status_$status"]) }}
        {{ $statusLabel }}
    </label>
@endforeach

{!! Form::close() !!}

Behavior

  • Since the form is using the model binding, the 3rd parameter ($checked) of Form::radio() actually gets ignored.
  • The checked state gets calculated by comparing the value of Form::radio()'s second parameter ($value) and the value of the field retrieved from the model.
  • The model will return an enum object as the field value.
  • When comparing the string with the object, PHP uses Enum::__toString() method that returns the label of the enum and not the value.
  • It results that the radio buttons are never checked.

Expected Behavior

The radio buttons are checked if the value of the model matches the value of the radio button.

NOTE: the problem definitely applies to checkboxes (although that's not a typical use case) and most probably to selects as well.

Possible Solutions

Variant 1

Laravel Forms library has an undocumented feature. If the model has a getFormValue() method, then the value of the form will be obtained from that method.

This can be used on any model like this:

class Issue
{
    public function getFormValue(string $key)
    {
        if ($this->isEnumAttribute($key)) {
            return $this->{$key}->value();
        }

        return $this->{$key};
    }
}

This works immediately, but it has to be added to every single model manually. As a generic solution this library should have another trait eg. CastedEnumsSupportForms

The trait should define the getFormValue($key) method shown above.

Problems With Variant 1

This works but there are some problems when you want to define your own getFormValue method on the model:

  • the model classes own getFormValue() method will override the trait's method with the same name.
  • as a result the behavior will be the same as the one we have now.

Possible workarounds: Import the method from the trait with a different name:

class Model
{
    use CastsEnums, CastedEnumsSupportForms {
        CastedEnumsSupportForms::getFormValue as private getEnumFormValue;
    }

    public function getFormValue($key)
    {
        if ('myvalue' == $key) {
            return 'whatever';
        }

        return $this->getEnumFormValue($key);
    }
}

The only problem with that is that it's fragile because the models need to be aware of this bevaior.

Variant 2

Using form model accessors: https://laravelcollective.com/docs/5.4/html#form-model-accessors I haven't elaborated this, but it should be taken into consideration as well.

fulopattila122 avatar Oct 07 '18 07:10 fulopattila122