Problem/Motivation
Since Drupal 11.3 core renders placeholders and lazy builders inside fibers, and entity storage suspends the current fiber in EntityStorageBase::loadMultiple() so that several placeholders can batch their loads. PHP has a bug where a fiber that suspends inside a magic method trips the engine's recursion guard for that property, so the next read of the same property on the same object skips __get() and reads a real property that does not exist (https://github.com/php/php-src/issues/14983). Core worked around it in #3565937: Workaround PHP bug with fibers and __get() for EntityReferenceFieldItemList and EntityReferenceItemBase.
The {type}_profiles fields that profile adds to the user entity use their own list class, ProfileEntityFieldItemList, which is computed. Reading $user->customer_profiles->entity goes through FieldItemList::__get(), which calls first(), which computes the list by loading the profiles, which suspends the fiber while still inside __get(). Core's workaround sits one level down on the item and is never reached. The result is this warning and a NULL where the profile should be:
Warning: Undefined property: Drupal\profile\Plugin\Field\ProfileEntityFieldItemList::$entityYou see it whenever two lazy builders or placeholders on the same page both read a profile off the same user. Core runs each of them in a fiber and pauses the first one at the profile load, so the second read on that user object hits the recursion guard and gets NULL. A single read on a page never triggers it, which makes it look intermittent.
Steps to reproduce
- Drupal 11.3 or later, profile 8.x-1.14, a profile type, a user with one profile of that type.
- From a lazy builder or any code running inside a fiber, read
$user->{type}_profiles->entity, then read it again on the same user object before the fiber resumes. - The second read logs the warning above and returns NULL.
The kernel test on the MR does this directly with a \Fiber, the same way core's EntityReferenceFieldTest::testEntityReferenceListFiberSuspension() does.
Proposed resolution
Add the same __get() workaround core uses in EntityReferenceItemBase to ProfileEntityFieldItemList: when a fiber is current and the property is entity, run the parent getter inside a private fiber and drive it to completion before returning. Nothing changes outside a fiber.
Issue fork profile-3621620
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
loze commentedComment #5
loze commented!44 is up against 8.x-1.x. The new
ProfileFiberTestpasses in the pipeline, and phpcs, cspell, eslint and stylelint are green.Two jobs are red and neither is from this MR. In phpunit,
Profile2MigrateTesterrors twice on a missingDrupal\filter\FilterFormatRepositoryInterfaceservice when installing profile's config, andProfileDefaultTest::testProfileFieldOnUserDisplayConfigfails to find the field label on the user display page.I ran that second one locally on 11.4 with and without this change and it fails the same way both times, so it is the current core the pipeline pulls and nothing this branch touches.
The seven phpstan findings are all in files this MR does not change: the 11.3
CacheableMetadataparameter on the two list builders, the deprecatedDrupalSqlBaseparent in the Profile2 source plugin,user_load_by_name()inProfileRegisterFormTest, the private property inProfileLocalAction, and the stored storage property in two kernel tests. The last pipeline on 8.x-1.x itself was green in December 2025 against the core of that time, so those are core drift since then and belong in their own issues.Some of this work was done with help from an AI coding agent. I have gone through it and verified it.