You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 11:03:17