Wanna to leave here some useful links and things I got while researching:
Below I've added example of my own implementation
Relations
Recipe.php model
// Relation for the Filament Repeater
public function ingredientRecipe(): HasMany
{
return $this->hasMany(IngredientRecipe::class);
}
// Regular relation belongsToMany
public function ingredients(): BelongsToMany
{
return $this->belongsToMany(Ingredient::class)
->using(IngredientRecipe::class)
->withTimestamps()
->withPivot(['quantity', 'unit_id']);
}
IngredientRecipe.php pivot model
// Set relationships as in the docs (link above)
public function recipe(): BelongsTo
{
return $this->belongsTo(Recipe::class);
}
public function ingredient(): BelongsTo
{
return $this->belongsTo(Ingredient::class);
}
Ingredient.php
public function recipes(): BelongsToMany
{
return $this->belongsToMany(Recipe::class);
}
Filament Code
And then in the Filament Repeater:
return [
Repeater::make('ingredientRecipe')
->label('ingredients')
->relationship()
->schema([
Grid::make(3)->schema([
// Ingredient name
TextInput::make('ingredient_name')
->label('Ingredient')
->required()
->string()
->maxLength(255),
// Ingredient quantity
TextInput::make('quantity')
->label('Quantity')
->required()
->numeric()
->minValue(0.1)
->maxValue(999.99),
// Ingredient unit
Select::make('unit_id')
->label('Unit')
->options(Unit::pluck('name', 'id'))
->required()
->searchable(),
]),
])
// Mutate Ingredients Before Fill Repeater
// To see ingredient name in the edit form
->mutateRelationshipDataBeforeFillUsing(function (array $data): array {
if (isset($data['ingredient_id'])) {
$data['ingredient_name'] = Ingredient::find($data['ingredient_id'])->name;
}
return $data;
})
// Mutate Before Store in the DB
->mutateRelationshipDataBeforeCreateUsing(function (array $data): array {
// Prepare Final Ingredients to save into DB
return self::prepareIngredient($data);
})
->addActionLabel('Add ingredient')
->addActionAlignment(Alignment::End)
->reorderable(false)
->minItems(1)
->maxItems(99)
->defaultItems(1)
->columnSpan('full'),
];
// Prepare final Ingredient
public static function prepareIngredient($ingredientData): array
{
// Check if the ingredient exists (get the existed one or create and save to database)
$newOrExistedIngredient = Ingredient::firstOrCreate([
'name' => trim($ingredientData['ingredient_name']) // Match by name
]);
// Prepare data for pivot table
$finalIngredient = [
'ingredient_id' => $newOrExistedIngredient->id,
'quantity' => $ingredientData['quantity'],
'unit_id' => $ingredientData['unit_id'],
];
return $finalIngredient;
}
Have a good coding