enum-eloquent
enum-eloquent copied to clipboard
Proper Support For Laravel Forms
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
) ofForm::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.