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

Baadier's avatar

Can't get Eloquent eager loading working

I'm struggling to get eager loading working across 3 relationships.

When I try the code below I get the data I need but it makes a ton of queries as per debugbar

  public function getIndexPage(){
        $brands = Brand::with(['brandAccounts','brandAccountLogs'])->orderBy('total_audience', 'DESC')->get();
        return view('social-media-tracker.index')->with('brands', $brands);
    }

Most examples seem to do it like this:

  public function getIndexPage(){
        $brands = Brand::with(['brandAccounts.brandAccountLogs'])->orderBy('total_audience', 'DESC')->get();
        return view('social-media-tracker.index')->with('brands', $brands);
    }

But I dont get the data from the 3rd relationship like this.

The database tables look like the following:

Stores the base brand infomation

Schema::create('brands', function(Blueprint $table) {
            $table->increments('id');
            $table->string('name')->unique();
            $table->string('slug')->nullable();
            $table->string('url')->unique();
            $table->integer('total_audience')->unsigned();
            $table->timestamps();
            $table->softDeletes();
        });

Stores information about specific instances of accounts on social networks

Schema::create('brandAccounts', function(Blueprint $table) {
            $table->increments('id');
            $table->integer('brand_id')->unsigned();
            $table->foreign('brand_id')->references('id')->on('brands');
            $table->enum('network', array('facebook', 'twitter', 'instagram','youtube'));
            $table->string('network_id');
            $table->timestamps();
            $table->softDeletes();
        });

Stores individual data points for specific social media accounts.

        Schema::create('brandAccountLogs', function(Blueprint $table) {
            $table->increments('id');
            $table->integer('brand_account_id')->unsigned();
            $table->foreign('brand_account_id')->references('id')->on('brandAccounts');
            $table->integer('log')->unsigned();
            $table->timestamps();
            $table->softDeletes();
        });
0 likes
11 replies
ChristopherSFSD's avatar

What do you models look like? Do you have the relationships properly defined?

If I fire up tinker, all of the following produce the same result for me. I get both relationships ...

Signoff::where('id', 1)->with('User',  'User.Account')->get();
Signoff::where('id', 1)->with('User.Account')->get();
Signoff::where('id', 1)->with('user.account')->get();
Signoff::where('id', 1)->with(['user.account'])->get();
Signoff::with('user.account')->orderBy('name')->get();

Signoff belongs to User. User has many signoffs. User has one Account.

Baadier's avatar

Thanks @ChristopherRaymond

My Model definitions are as follows:

Brand

    public function brandAccounts()
    {
        return $this->hasMany('App\BrandAccount');
    }

    public function brandAccountLogs()
    {
        return $this->hasManyThrough('App\BrandAccountLog', 'App\BrandAccount','brand_id','brand_account_id');
    }

BrandAccount

    public function brands()
    {
        return $this->hasOne('App\Brand');
    }

    public function brandAccountLogs()
    {
        return $this->hasMany('App\BrandAccountLog');
    }

BrandAccountLog

    public function brandAccounts()
    {
        return $this->hasOne('App\BrandAccount');
    }
thomaskim's avatar

@Baadier You should be using belongsTo, not hasOne.

BrandAccount

    public function brands()
    {
        return $this->belongsTo('App\Brand');
    }

BrandAccountLog

    public function brandAccounts()
    {
        return $this->belongsTo('App\BrandAccount');
    }
1 like
ChristopherSFSD's avatar

Since Brand has a brandAccountLogs relationship, your first example where you had ->with('brandAccounts', 'brandAccountLogs') is going to call the brandAccountLogs relationship on the Brand model. In your second example where you ->with ('brandAccounts.brandAccountLogs'), it will call the brandAccountLogs relationship on the BrandAccount model.

I'm not too sure about the hasManyThrough that you have there. Try commenting that out and then use the . syntax and see what happens.

I'm not sure if you use tinker or not but you might want to fire that up and see what happens with the queries. For example you could do the following ...

# php artisan tinker

> DB::enableQueryLog();
> Brand::where('id', 1)->with('brandAccounts.brandAccountLogs')->get();
> DB::getQueryLog();

Note that you can also DB::flushQueryLog() if you want to clear it and start again.

Baadier's avatar

Thanks @thomaskim

I made the change, I always end up confused when I do the inverse of the relationships, and I do get the data but I still get a helluva lot of queries.

This is the actual blade view:

                    <table class="table table-striped table-hover">
                        <thead>
                        <tr>
                            <th>Ranking</th>
                            <th>Brand</th>
                            <th>Facebook</th>
                            <th>Twitter</th>
                            <th>Instagram</th>
                            <th>YouTube</th>
                            <th>Total Audience</th>
                        </tr>
                        </thead>
                        <tbody>
                        <?php $i = 1; ?>
                        @foreach($brands as $brand)
                            <?php  ?>
                            <tr>
                                <td>
                                    #{{ $i }}
                                </td>
                                <td>
                                    {{ $brand->name }}
                                </td>
                                <td>
                                    {{ $brand->brandAccounts()->where('network','=','facebook')->first()->brandAccountLogs()->whereDate('created_at', '=', date('Y-m-d'))->first()->log }}
                                </td>
                                <td>
                                    {{ $brand->brandAccounts()->where('network','=','twitter')->first()->brandAccountLogs()->whereDate('created_at', '=', date('Y-m-d'))->first()->log }}
                                </td>
                                <td>
                                    {{ $brand->brandAccounts()->where('network','=','instagram')->first()->brandAccountLogs()->whereDate('created_at', '=', date('Y-m-d'))->first()->log }}
                                </td>
                                <td>
                                    {{ $brand->brandAccounts()->where('network','=','youtube')->first()->brandAccountLogs()->whereDate('created_at', '=', date('Y-m-d'))->first()->log }}
                                </td>
                                <td>
                                    {{ $brand->total_audience }}
                                </td>
                            </tr>
                            <?php $i++; ?>
                        @endforeach
                        </tbody>
                    </table>

My understanding is that once the data is passed to the view their are no further queries unless initiated by AJAX so the problem is still in the controller?

ChristopherSFSD's avatar

But you are issuing queries in your view. When you $brand->brandAccounts()->where ... you're querying the database.

Baadier's avatar

@ChristopherRaymond How would I access the same data from the collection that I've received instead of hitting the database again. I re-looked at its a bit silly to think I was accessing the collection in that view in the manner I was doing it:

{{ $brand->brandAccounts()->where('network','=','instagram')->first()->brandAccountLogs()->whereDate('created_at', '=', date('Y-m-d'))->first()->log }}
thomaskim's avatar

You should drop the parenthesis to get the collection. Example:

$brand->brandAccounts

The collections where method also doesn't accept an operator argument. It always looks for an equal value so:

->where('network', 'facebook')

Finally, there is no whereDate method on the collection. I also think you're putting too much login in your HTML. It's getting too complicated. Couldn't you just do this in the query so that you don't have to put the logic in your HTML?

ChristopherSFSD's avatar

This is how I understand all this. I maybe wrong, but here goes ...

Basically what happens under the hood when you call a relationship WITHOUT the (), a DB query is executed and Laravel sets a magical property on the object with the results from the DB. That way if you call it again, it just returns what's stored in that property instead of querying the database again.

So let's say I have a user that has many posts. When I $user->posts, Laravel looks to see if the posts property has been set on the user model. If so, it returns that. If not it executes the posts relationship (queries the DB) and then sets the posts property on the model so that subsequent calls to $user->posts does not require another DB query.

However when I $user->posts() (notice the parenthesis) ... Laravel returns the relationship -- not the data. So I could use that relationship as a basis for a more complex query (which is what you're doing in your views). $user->posts()->whereDate('created_at', '2016-01-01')->get(). That will execute the posts relationship and query the DB with the new / additional criteria.

Eager loading is not going to load all the various permutations of queries unless you define those permutations as relationships and eager load them. Either way eager loading is not really going to reduce the total queries, it's just going to make sure you load all the necessary data up-front.

One other thing I noticed is your use of enum. You might want to think about converting that column to a string. If you ever use SQLite for running tests (especially in-memory tests), SQLite doesn't understand enums.

Please or to participate in this conversation.