Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

HenrijsS's avatar

TextInput from Many-To-Many relationship not showing

Hello.

So I have an interesting relationship:

  • Project_Stage (Which is just a name and a description).

  • Project_Stage_Subpoints (Which also have a name and a desc)

  • Project

Now, when a new Project is created, it automatically adds all Project_Stage_Subpoints to the Project. Each project has all Subpoints, and all Subpoints can belong to many projects. (Due to the fact that once a sub-point changes, each project needs to reflect those changes).

Also there's 2 columns in the Pivot table - Visible and Statuss. (Not all projects will need all subpoints and there needs to be a check if those subpoints are completed).

I already created the Pivot model for the tables.

So the problem is that when using HasMany, it doesn't show the contents (the name, visible and statuss) in the repeater field, but using a belongsToMany does. Note that belongsToMany doesn't save it in the pivot table, so that doesn't work.

Here's my code:

Project Model

class Project extends Model
{
    use SoftDeletes, LogsActivity;

    protected $fillable = [
        'name',
        'address',
        '...'
    ];

    protected static function booted(): void {
        static::created(function ($project) {
            $subPoints = ProjectStageSubpoint::all();
            $project->stageSubpoints()->sync($subPoints);
        });
    }

    // Filament relationship (Many to Many)
    public function projectSubpoints(): HasMany {
        return $this->hasMany(ProjectSubpoint::class);
    }
}

ProjectStageSubpoint model

class ProjectStageSubpoint extends Model implements Sortable
{
    use SoftDeletes, SortableTrait, LogsActivity;

    protected $fillable = [
        'name',
        'description',
        'project_stage_id',
        'order_column',
    ];

    public function projectStage(): BelongsTo {
        return $this->belongsTo(ProjectStage::class);
    }

    // Filament relationship (Many to Many)
    public function subpointProjects(): HasMany {
        return $this->hasMany(ProjectSubpoint::class);
    }
}

ProjectSubpoint Pivot model

class ProjectSubpoint extends Pivot
{
    protected $table = 'project_project_stage_subpoint';

    protected $fillable = [
        'order_column',
        'project_id',
        'project_stage_subpoint_id',
        'visible',
        'status',
    ];

    public function project(): BelongsTo {
        return $this->belongsTo(Project::class);
    }

    public function projectStageSubpoint(): BelongsTo {
        return $this->belongsTo(ProjectStageSubpoint::class);
    }
}

And in my resource, I'm creating a Wizard of all the stages that has the Repeater field of the relationship:

$stages = ProjectStage::with('projectStageSubpoints')->get();

...

Wizard::make([
    ...$stages->map(function (ProjectStage $stage) {
        return Wizard\Step::make($stage->name)
            ->schema([
                Repeater::make('projectSubpoints')
                    ->relationship('projectSubpoints', fn(Builder $query) => $query->where('project_stage_subpoint_id', $stage->id))
                    ->schema([
                        TextInput::make('name')->disabled(),
                        Toggle::make('visible'),
                        Toggle::make('status'),
                    ])->columns(3)
            ]);
    })
])

All 3 of those don't show correctly. Name comes from the projectStageSubpoint.name. Visible and Status is from the Pivot table.

Is there something I'm missing? And what would be the best way to structure this?

P.S. I know the naming convention is horrible, I'll think of something later.

0 likes
1 reply
HenrijsS's avatar

So I got it working, but I now know the problem is in the Builder query that's in the relationship.

Here's my current code:

Section::make('Stadijas')
    ->schema([
        Wizard::make([
            ...$stages->map(function (ProjectStage $stage) {
                return Wizard\Step::make($stage->name)
                    ->schema([
                        Repeater::make("projectSubpoints")
                            ->relationship('projectSubpoints', fn(Builder $query) => $query->whereHas('projectStageSubpoint', function (?Builder $query) use ($stage) {
                                $query?->where('project_stage_id', $stage->id);
                            }))
                            ->schema([
                                Placeholder::make('Nosaukums')
                                    ->content(fn(?ProjectSubpoint $record) => $record?->projectStageSubpoint()?->first()?->name)
                                    ->label($stage->name),
                                Placeholder::make('Test')
                                    ->content(fn() => $stage->id),
                                Toggle::make('visible'),
                                Toggle::make('status'),
                            ])->columns(3)
                            ->addable(false)
                            ->deletable(false)
                            ->reorderableWithDragAndDrop(false)
                            ->orderColumn('order_column')
                    ]);
            })
        ])
    ])
    ->visible(fn(?Project $record) => $record?->has_bis_lieta)

The thing is that when I put a static number like 1,2,3,4 in $stage->id, it works. But if it's using the variable I'm looping through, it just shows the last one (7 in my case).

Viewing the $stage->id inside the form changes it when hopping through the steps. Is this some kind of Livewire mismatch issue or what?

Please or to participate in this conversation.