[6.x]: Lazy eager loading returns nothing for a multi-instance relation field once its value was read

Closed Beginner friendly
#20,235 1 comment 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
68/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
mysql, php
Domain
backend, databases

Research direction

Start in BaseRelationField::getEagerLoadingMap() and compare how it reads the query conditions with normalizeValueForInput(), which uses getWhereForColumn('elements.id') and the values key. Check how the normalized value's whereIn() condition is represented, then run or add coverage for the multi-instance Assets field scenario. Done means lazy eager loading returns the asset for an already-read field on entries in a multi-entry result set.

Written by the indexing model from the issue text.

Description

repo:cms
What happened?

An entry type has one Assets field placed twice in its layout (image, and logoOverHero as a second instance with its own handle). In a site template, {{ element.image.one() }} returns null when element came from a result set of two or more entries (e.g. the nested entries of a Matrix field), although the entry has an image (element.image.ids() returns it, and so does element.image.eagerly(false).one()).

BaseRelationField::getEagerLoadingMap() reads the target IDs from a normalized value's elements.id where with $where['value'], but normalizeValue() adds that constraint with whereIn(), which Laravel stores under values (getQuery()->wheres holds ['type' => 'In', 'column' => 'elements.id', 'values' => [...]]). So the IDs of every source element whose field was already normalized are lost. For single-instance fields fetchRelationsFromDbTable() covers this by querying the relations table, which hides the bug; for multi-instance fields it returns false by design, so the map comes back empty and the element gets an empty eager-loaded result. The element that triggers lazy eager loading always has a normalized value (reading the field is what runs the query), so it always loses its own relations.

The same file reads the same where correctly in normalizeValueForInput() ($value->getWhereForColumn('elements.id') and $where['values']).

Steps to reproduce
  1. Create an Assets field and add it twice to an entry type's field layout (second instance with a different handle).
  2. Add two or more entries of that type to a Matrix field on a page; give the first one an image in the first instance.
  3. In the page's site template: {% for block in entry.myMatrix.all() %}{{ block.image ? block.image.one().id ?? 'null' }}{% endfor %}.
Expected behavior

The asset ID, as with block.image.eagerly(false).one() or with a single-instance field.

Actual behavior

null (block.image.all() is empty). Changing $where['value'] to $where['values'] in BaseRelationField::getEagerLoadingMap() fixes it.

Craft CMS version

6.x-dev (603f0e0, 2026-10-31)

PHP version

8.5

Operating system and version

macOS (Laravel Herd)

Database type and version

MySQL 8

Image driver and version

—

Installed plugins and versions

None needed to reproduce.

Dominant language
PHP
Stars
3.6k
Forks
712
Avg merge
13h 5m
Merged PRs (30d)
328

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from craftcms/cms

All issues in craftcms/cms

Similar issues

More PHP issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.