tixmanu's avatar

Question about relationship

Hi, I have a question about relationship. I used a lot of BelongsToMany/BelongsToMany relationship and it's work well this way in views template:

@foreach($object->getMany()->get() as $many) .... @endforeach

But on belongsTo/HaveMany relationship, i have trouble get the belongsTo function. This doesn't work:

$object->getBelongsTo();

But this work:

$object->getBelongsTo;

I don't understand why in BelongsToMany/BelongsToMany the function is working well but not in belongsTo/HaveMany? And if it's better to use attribut everywhere instead of the function?

Thanks for help!

0 likes
8 replies
Sinnbeck's avatar

I honestly dont quite understand what you are talking about. There isnt a getMany() method in eloquent?

The way eloquent works is that you preload the relationship using ->with() and then access it as a property. for instance

$post = Post::with('comments')->with('author')->find(1);

//you can now do this in blade if this is a hasMany relationship
@foreach ($post->comments as $comment)
  {{$comment->title}}
@endforeach
//or this if you have a belongsTo relationship
{{$this->author->name}}

Those relationships look like this

public function comments()
{
    return $this->hasMany(Comment::class);
}
public function author()
{
    return $this->belongsTo(Author::class);
}
tixmanu's avatar

Thanks for your anwser and sorry i was not very clear. I don't use with() for now, i'll try this and see if it works. And there is one thing i don't understand: In your examples why are function comments and author declared as public if we never use them directly?

Sinnbeck's avatar

@tixmanu Because eloquent uses them under the hood. It calls them to know how to load it.

So ->comments calls ->comments()->get() automatically for instance (and adds it to the posts)

tixmanu's avatar

@Sinnbeck Ok i see. So i changed all my call to function()->get() by the attribut and it's works well :) But i didn't put any with() instructions, only using:

$project = Project::find($id)

And then i'm able inside the view to use $project->getParts and it's working.

My model look like:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Project extends Model
{
    protected $table = 'projects';

    protected $primaryKey = 'id';

    protected $fillable = [
        'name',
        'description',
        'user_id',
        'order'
    ];

    public function getParts(): HasMany
    {
       return $this->hasMany(Part::class)->orderBy('name');       
    }

Sinnbeck's avatar

@tixmanu Be sure you load it when you load the project

$project = Project::with('getParts')->find($id)

Otherwise you are querying the database every time you do $project->getParts

Finally I would recommend naming it `parts'

    public function getParts(): HasMany
    {
       return $this->hasMany(Part::class)->orderBy('name');       
    }

and then use $project->parts

Snapey's avatar

Also, avoid database queries in the view. This is a code smell.

You should prepare all the data for your view in the controller

Also read up about n+1 issues. This is where you execute a database query inside a loop.

A BAD example;

// controller

$projects = Project::all();

return view('projectsview', compact('projects')

// blade

@foreach($projects as $project)

  {{ $project->name }}

  @foreach($project->getParts()->get() as $part)

      {{ $part->name }}

In the above, a database query runs in the view for every project which is slow and inefficient

A GOOD example

// controller

$projects = Project::with('parts')->get();

return view('projectsview', compact('projects')

// blade

@foreach($projects as $project)

  {{ $project->name }}

  @foreach($project->parts as $part)

      {{ $part->name }}

In this version, the projects and their child relations are all loaded in 2 queries inside the controller, irrespective of how many projects there are.

No database queries are performed in the view.

tixmanu's avatar

I still have one more question. A project contain many parts. A part contain many chapters. A chapter contain many scenes.

So to load all Parts, Chapters and Scenes inside a project i did this:

$project = Project::with(['parts' => function($q) {
            $q->with('chapters');
        }, 'parts.chapters' => function($q) {
            $q->with('scenes');
        }])->find($id);​

Is it the correct way to load everything? (it's works but i don't know how to verify if data are really loaded in the controller and not when i call the attribute inside the view?)

Please or to participate in this conversation.