I'm trying to do a search input from 2 datatables of my database, one containing vegetables and the other one containing recipes made of vegetables, i first made my search input with only the elements from the vegetable table. But now im trying to include the repice table but it wont work: I'm getting the error "Attempt to read property "recipes" on array" but I do't understand where the problem comes from.
This is my code:
Search.php:
<?php
namespace App\Http\Livewire;
use Livewire\Component;
use App\Models\Vegetable;
class Search extends Component
{
public $query = '';
public $vegetables;
public $recipes;
public function updatedQuery()
{
$this->vegetables = Vegetable::where('name', 'like', '%'.$this->query.'%')->get()->toArray();
}
public function render()
{
return view('livewire.search');
}
}
search.blade.php :
<div >
<h1>Recherche</h1>
<input wire:model="query" type="text" placeholder="Rechercher...">
@if(!empty($query))
<ul>
@if(!empty($vegetables))
<div >
@foreach($vegetables as $vegetable)
<li><span >lunch_dining</span>{{ $vegetable['name'] }}</li>
@endforeach
</div>
@else
<li>Pas de résultat</li>
@endif
<div >
@foreach($vegetables as $vegetable)
@foreach($vegetable->recipes as $recipe)
<li><span >menu_book</span>{{ $recipe['name'] }}<span >Ingrédient: {{ $vegetable['name'] }}</span></li>
@endforeach
@endforeach
</div>
</ul>
@endif
</div>
My model Vegetable :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Vegetable extends Model
{
use HasFactory;
public $timestamps = false;
protected $fillable = ['name'];
public function recipes(){
return $this->belongsToMany(Recipe::class, 'vegetables_recipes', 'vegetable_id', 'recipe_id');
}
public function getName($id) {
return $this->name;
}
}
My model Recipe :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Recipe extends Model
{
use HasFactory;
public $timestamps = false;
protected $fillable = ['name'];
public function vegetables(){
return $this->hasOne(Vegetable::class, 'vegetables_recipes', 'recipe_id', 'vegetable_id');
}
public function getName($id) {
return $this->name;
}
}
The model from my pivot table VegetablesRecipe :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class VegetablesRecipe extends Model
{
use HasFactory;
public $timestamps = false;
protected $fillable = ['vegetable_id', 'recipe_id'];
}
Thank you in advance
CodePudding user response:
So I found the issue with your code; I didn't notice it yesterday as it was hidden off-screen in your Search.php
's updatedQuery()
function:
$this->vegetables = Vegetable::where('name', 'like', '%'.$this->query.'%')
->get()
->toArray();
When you call ->toArray()
, you convert the Collection of Vegetable
Model instances into an Array of Arrays. This means you cannot use Object Access to get Properties:
$vegetable->recipes; // Fails with error "Attempt to read property "recipes" on array"
Additionally, you cannot access any Model functions, as an Array is not an instance of a Model:
$vegetables['recipes']; // Undefined array key "recipes"
Since public function recipes()
is a method of the Vegetable.php
Model class, that won't work. It can work, if you load it first before casting to an Array:
$this->vegetables = Vegetable::where('name', 'like', '%'.$this->query.'%')
->with('recipes')
->get()
->toArray();
Now, $vegetable['recipes']
will work, but it's better to just drop the ->toArray()
completely:
$this->vegetables = Vegetable::where('name', 'like', '%'.$this->query.'%')
->with('recipes')
->get();
Additionally, for performance reasons, you want to include ->with('recipes')
to prevent N 1 queries being called while looping.
With those changes made, you can write your livewire/search.blade.php
code as follows:
<div >
<h1>Recherche</h1>
<input wire:model="query" type="text" placeholder="Rechercher...">
@if(!empty($query))
<ul>
@if(!empty($vegetables))
<div >
@foreach($vegetables as $vegetable)
<li><span >lunch_dining</span>{{ $vegetable->name }}</li>
@endforeach
</div>
@else
<li>Pas de résultat</li>
@endif
<div >
@foreach($vegetables as $vegetable)
@foreach($vegetable->recipes as $recipe)
<li><span >menu_book</span>{{ $recipe->name }}<span >Ingrédient: {{ $vegetable->name }}</span></li>
@endforeach
@endforeach
</div>
</ul>
@endif
</div>
Lastly, some cleanup:
Vegetable.php
and Recipe.php
The methods getName($id)
have no purpose; you're not doing anything with $id
, and name
is not a private property; you can simply do $vegetable->name
or $recipe->name
and it will do the same as what these methods are doing.
Recipe.php
As stated in the comments, belongsToMany()
is the inverse of belongsToMany()
, not hasOne()
:
public function vegetables(){
return $this->belongsToMany(Vegetable::class, 'vegetables_recipes');
}
Additionally, if the primary and foreign key names match the model names, you don't need them. The proper name for the pivot table would be recipes_vegetables
(plural, alphabetical), so specifying that is required. You can do the same for Vegetable.php
:
public function recipes(){
return $this->belongsToMany(Recipe::class, 'vegetables_recipes');
}
Lastly, your model VegetablesRecipe.php
is not needed, and is currently not being used. You typically don't define a Model for a Pivot table, so you can either remove it, or keep it around should you ever need to directly modify the Pivot.