FilamentPHP表单更新laravel-model-state触发Wireable foreach报错求助
Filament v3 + spatie/laravel-model-states 表单提交错误解决方案
问题场景
在FilamentPHP v3项目中使用spatie/laravel-model-states扩展时,为使状态在表单中正常显示,给抽象状态类实现了Wireable接口。状态类代码如下:
abstract class InventoryItemState extends State implements Wireable { public static function config(): StateConfig { return parent::config() ->default(Received::class) ->allowTransition(Received::class, Sanitized::class) ->allowTransition(Sanitized::class, Sold::class) ->allowTransition(Sold::class, Sanitized::class); } public function toLivewire() { return [ 'name' => $this->getValue() ]; } public static function fromLivewire($value) { return new static($value); } }
示例状态类:
class Received extends InventoryItemState { public static $name = 'received'; }
在Filament资源表单中,展示当前条目可切换的状态单选按钮:
namespace App\Filament\Resources; class InventoryItemResource extends Resource { protected static ?string $model = InventoryItem::class; protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack'; public static function form(Form $form): Form { $potential_states = $form->getRecord()->state->transitionableStates(); // 无关代码省略 $formSchema = [ // 其他表单字段 ]; if (!empty($potential_states)) { $options = collect($potential_states) ->keyBy(fn ($item) => $item) ->toArray(); $formSchema[] = Forms\Components\Radio::make('state') ->options($options); } $schema[] = Section::make() ->compact() ->columns(2) ->schema($formSchema); return $form ->schema($schema); } // 其他代码省略 }
表单可正常显示单选按钮,但保存时抛出错误:
foreach() argument must be of type array|object, string given
错误来自Livewire\Features\SupportWireables\WireableSynth.php的hydrate方法:
function hydrate($value, $meta, $hydrateChild) { foreach ($value as $key => $child) { $value[$key] = $hydrateChild($key, $child); } return $meta['class']::fromLivewire($value); }
问题根源:初始状态通过toLivewire()返回数组,但表单提交的是状态字符串,导致WireableSynth::hydrate无法处理。
解决方法
方案1:兼容字符串输入的fromLivewire实现
修改抽象状态类的fromLivewire方法,使其同时支持数组和字符串输入:
public static function fromLivewire($value) { $stateValue = is_string($value) ? $value : $value['name']; return static::make($stateValue); }
使用static::make()替代new static(),符合spatie状态类的实例化规范。
方案2:Filament表单字段转换器
无需修改Wireable接口实现,直接在Radio组件中添加状态转换逻辑:
$formSchema[] = Forms\Components\Radio::make('state') ->options($options) ->hydrateStateUsing(fn ($state) => $state->getValue()) ->dehydrateStateUsing(fn ($stateName) => InventoryItemState::make($stateName));
该方案贴合Filament表单处理流程,降低状态类与表单的耦合度。
方案3:优化表单选项生成(可选)
调整选项生成逻辑,让单选按钮显示友好的状态名称而非类名:
$options = collect($potential_states) ->mapWithKeys(fn ($stateClass) => [$stateClass => $stateClass::$name]) ->toArray();
建议方案
优先选择方案2,既遵循Filament设计逻辑,又无需改动状态类核心实现。若需保持Wireable兼容性,可结合方案1使用。
内容的提问来源于stack exchange,提问作者Chris Rockwell
相关产品推荐
相关产品推荐

