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

nunomira's avatar

Constraining in Nested Eager Loading

Constraining in Nested Eager Loading

I'm having a hard time trying to use a Contrain in Nested Eager Loading. The idea of what I have: Pages wich can be in different languages. For this I have 4 tables: "langs", "pages", "pagedetails" and "pagedetail_lang". Something like:

langs

    id, language, lang

    1, english, en
    2, french, fr

pages

    id, description

    1, home page
    2, about page
    3, contacts page

pagedetails

    id, page_id, slug, title

    1, 1, home-en, Home in English
    2, 1, home-fr, Home in French
    3, 2, about, About in English
    4, 3, contacts, Contacts page in English and French

pagedetail_lang

    id, pagedetails_id, lang_id

    1, 1, 1
    2, 1, 2
    3, 3, 1
    4, 4, 1
    4, 4, 2

And I have these Models:

class Page extends Model
{
    public function pagedetails()
    {
        return $this->hasMany('App\Pagedetail');
    }
}
class Pagedetail extends Model
{
    public function langs()
    {
        return $this->belongsToMany('App\Lang', 'pagedetail_lang');
    }
}

I want to get the pages, with their pagedetails in English only, so I tried the following code.

$pages = Page::with(['pagedetails' => function($query){
            $query->with(['langs' => function($query){
                    $query->where('lang', 'en');
                }]);
         }])->get();

But no matter what I do, the where clause doesn't work. I get the pagedetails in all languages. I have even tried using limit, but I still receive more than one result:

$query->where('lang', 'en')->limit(1);
0 likes
10 replies
pmall's avatar

You check lang = en whereas in your structure above lang contains english and code contains en.

$pages = Page::with(['pagedetails.langs' => function($query){
    $query->where('code', '=', 'en');
}])->get();

Should work

1 like
nunomira's avatar

Hi, thanks!

Unfortunately "code" vs "lang" was just a mistake in my example code. So basically, I do have "code", so the problem remains and lies elsewhere. (I'll update/correct my question)

Are my relationships correct?

pmall's avatar

The code I showed above should work

nunomira's avatar

I guess you're right and it's my understanding of this that is wrong. I may be going the wrong way.

This lead me to think that it was not working:

foreach($pages as $page)
{
    $pagedetails = $page->pagedetails;
    foreach($pagedetails as $pagedetail)
    {
        $langs = $pagedetail->langs;
        var_dump($pagedetail->title); // all titles appear
        foreach($langs as $lang)
        {
            var_dump($lang->lang); // en
        }
    }
}

But then this, made me realize that it's working:

foreach($pages as $page)
{
    $pagedetails = $page->pagedetails;
    foreach($pagedetails as $pagedetail)
    {
        $langs = $pagedetail->langs;
        foreach($langs as $lang)
        {
            var_dump($pagedetail->title); // only english titles appear
            var_dump($lang->lang); // en
        }
    }
}

I thought I'd have access to the pages and their titles (and slugs) in english automatically.

But it seems I have to filter them. Is this so?

If this is the case, maybe I'd be better off with my own query, joining the 3 tables?

I believe that there are 3 queries here, and a lot of unecessary data being loaded. And that I could do it with only one query and only the desired data would be loaded.

The same goes for the loops. There are 3 here, to grab the desired data, and I'd do it with only one.

Once again, thank you for your help!

janokary's avatar

@pmall, What if we want to make a constrain in your query

$pages = Page::with(['pagedetails.langs' => function($query){
  $query->where('code', '=', 'en');
}])->get();

But in pagedetails not langs for instance:

orderBy('slug', 'desc');

or

where('slug', 'like', 'foo%')

I am stack in a situation like that

janokary's avatar

@pmall, in your query the constrain is in the langs table.

$pages = Page::with(['pagedetails.langs' => function($query){
  $query->where('code', '=', 'en');
}])->get();

So you get every 'page' with 'pagedetails' and and the corresponding langs where code='en'

Now, what I want is, for each Page I to fetch all pagedetails ordered by slug (and then the corresponding pagedetails)

orderBy('slug', 'desc');

or another query would be for each Page I to fetch all pagedetails that have slug like foo% (and then the corresponding pagedetails)

where('slug', 'like', 'foo%')

So in those two situations the constrain is not in the first table 'pages' nor in the last 'langs' it is in the middle table 'pagedetails'

pmall's avatar

Your critera are on the pagedetails so you should select pagedetails.

$pagedetails = Pagedetail::with()->with([
    'page',
    'langs' => function ($query) {
        $query->where('code', '=', 'en');
    },
])->where('slug', 'like', 'foo%')->orderBy('slug', 'desc');

foreach ($pagedetails as $pagedetail) {

    $pagedetail->page;
    $pagedetail->langs;

}

But it is strange to have a pagedetails table. I dont understand the structure.

1 like
janokary's avatar

Thank you @pmall this is what I needed. Strange structures can always occur.

Please or to participate in this conversation.