2

The TL;DR summary: if I have a related Entries field that I access, can I then access an Entries field within that entry?

I created a learning project running on MAMP to help me learn Craft, and one of the things I'm trying to wrap my head around is some of the ways I can use Relationships. Originally I had a Matrix field but I deleted that because it was adding too much complexity.

I picked the NFL draft as a data set because it's easily available and has many different cross relationships.

The basic concept is this, you have 32 NFL teams, which have their own channel nflTeams (not singles, regular channel), a channel for the players that are available (draftClass2021), a channel for the schools the players attended (colleges), and finally a channel for that years Draft (nflDraft2021).

None of my sections are singles, everything is a regular channel.

Within that Sections structure I would have 2 Entries fields in the nflDraft2021 section:

draftPick (the player selected, from section draftClass2021) teamDraftedby (the team that selected him, from section nflTeams)

Each entry in the draftClass2021 would also have an Entries field playersSchool, which links to the colleges section.

What I am trying to figure out is, using reverse relationships, can I have a page for each NFL team that lists all the players they selected in the draft, AND the school each attended?

I can get the first part, the players selected, and I can even pull the data from other fields in that related entry, but not the Entries field that links to the school.

I created a graphic to help visualize the structure: enter image description here

Here is the code:

{% set myEntryQuery = craft.entries()
    .section('nflTeams')
    .all() %}

{# title for team page #}   
<h1>{{ entry.title }}</h1>

{# get the related entries from the nflDraft2021 channel #}
{% set players = craft.entries.section('nflDraft2021').relatedTo(entry).all() %}

{% for player in players %}


  {% set relatedEntries = player.draftPick.all() %}


  {% if relatedEntries|length %}
      <ul>
          {% for rel in relatedEntries %}
              <li>{{ player.draftSlot }} <a href="{{ rel.url }}">{{ rel.title }}</a> <br>{{rel.combineOrProDay }} 
             {{ rel.playerPosition }} <br> 
             
             {# This is the Entries field that is related to the player #}
             {{ rel.playersSchool }}
              </li>
          {% endfor %}
      </ul>
  {% endif %}

{% endfor %}

I've read the documentation on Relationships and Entries fields but I couldn't figure out from that if what I was trying to do was even possible. Any thoughts greatly appreciated.

2

Problems

I think you're just mixing up a couple of variables in your template. Just simplifying your template and better naming for variables will help you spot the errors. In particular:

{% set myEntryQuery = craft.entries().section('nflTeams').all() %}

This isn't used at all, what is it doing there? Get rid of unused code.

{% set players = craft.entries.section('nflDraft2021').relatedTo(entry).all() %}

You're calling the variable players, but your query returns nflDraft2021 entries, this is causing confusion down the line.

{% for player in players %}

Same as above, your player variable is a nflDraft2021 entry, not a draftClass2021 entry. By the way, the draftClass2021 is a really confusing name – the entries in the section represent players, not classes, right? Why not just name it players or players2021?

{% set relatedEntries = player.draftPick.all() %}

Now this is where you're actually getting the selected player, so this variable should be called player. Also, if I understand your setup correctly each nflDraft2021 entry only has one associated player, correct? In this case, you should make that clear in the code by using .one() to execute the query instead of .all(). This will give you the player entry (or null if none are selected) instead of an array, so you can get rid of the inner loop.

Solutions

Not sure if you can get everything in one query – it should be possible using eager-loading. See eager-loading nested elements. However, you don't need to make it so complicated. I see two possible approaches:

  1. Just use one query to get all nflDraft2021 entries related to the current team, then iterate through those. Inside the loop, use another query to get the selected player for each draft and use eager-loading to load the school as well in one go.
  2. Same as one, but after querying all nflDraft2021 entries, use just one query to get ALL players related to those drafts (you can pass an array to relatedTo. Again, use eager-loading to include the schools as well. This alleviates the N+1 problem.

Approach #1 sample code

{# Get all draft entries for this team. #}
{% set draftEntries = craft.entries().section('nflDraft2021')
    .relatedTo(entry)
    .all()
%}

{# Iterate through the drafts for this team. #}
{% for draft in draftEntries %}
    {# Get the drafted player, preloading their school. #}
    {% set selectedPlayer = craft.entries().section('draftClass2021')
        .relatedTo(draft)
        .with(['playersSchool'])
        .one() %}
    {% set playerSchool = selectedPlayer.playersSchool[0] ?? null %}

    Slot: {{ draft.draftSlot }} <br>
    {% if selectedPlayer %}
        Player: {{ selectedPlayer.title }} <br>
        {% if playerSchool %}
            Player schook: {{ playerSchool.title }}
        {% endif %}
    {% endif %}
{% endfor %}

Approach #2 sample code

{# Get all draft entries for this team. #}
{% set draftEntries = craft.entries().section('nflDraft2021')
    .relatedTo(entry)
    .all()
%}

{# Get all players selected by this team. #}
{% set selectedPlayers = craft.entries().section('draftClass2021')
    .relatedTo(draftEntries)
    .with(['playersSchool'])
    .all()
%}

{# Iterate through the players directly #}
{% for player in selectedPlayers %}
    {# Note the array access syntax to get the eager-loaded element. #}
    {% set playerSchool = player.playersSchool[0] ?? null %}

    Player: {{ player.title }} <br>
    {% if playerSchool %}
        Player school: {{ playerSchool.title }}
    {% endif %}
{% endfor %}
5
  • 1
    This is fantastic, thank you. I didn't know about eager-loading, and walking through where I had errors or inefficiencies is extremely helpful, so greatly appreciated. The first solution works perfectly for me, the second only returns the player names though. Jul 9 at 17:18
  • 1
    @doctorgloom Glad I could help! I had some typos in the second example, should be correct now.
    – MoritzLost
    Jul 9 at 19:45
  • Another question, lets pretend the School Entries field allowed multiple selections (choosing multiple schools). How would you loop through that, listing all schools, with eager loading? When I try a for loop it just spits out the first item in the array multiple times. I can make it a new post if that's not clear. Jul 10 at 21:38
  • 1
    @doctorgloom As far as I know, eager-loaded elements always end up in an array. In this case you'd loop over player.playersSchool instead of just accessing the first item. So instead of this: player.playersSchool[0], you use a loop: {% for school in player.playersSchool %} {{ school.title }} {% endfor %}). If that's not working maybe post a new question, would have to see the code to find out what's going on.
    – MoritzLost
    Jul 12 at 7:40
  • Thanks, that fixed it. I thought the array was needed for the eager-loading. Jul 12 at 22:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.