Skip to content

Multi-element type eager loading maps #16972

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG-WIP.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
- Added `craft\web\View::registerIcon()`.
- Added `craft\web\assets\codemirror\CodeMirrorAsset`.
- `craft\base\Element::fieldLayoutFields()` now has an `editableOnly` argument.
- `craft\base\ElementInterface::eagerLoadingMap()` and `craft\base\EagerLoadingFieldInterface::eagerLoadingMap()` can now specify mappings for multiple target element types, or not specify the element types at all. ([#16972](https://github.com/craftcms/cms/pull/16972))
- `craft\cache\ElementQueryTagDependency` now merges cache tags provided by the element query with any tags already set on its `$tags` property.
- `craft\elements\NestedElementManager::getCardsHtml()` and `getIndexHtml()` now accept `canPaste` config options, which can be set to `true`, `false`, or a JavaScript function.
- `craft\services\Elements::duplicateElement()` now has a `checkAuthorization` argument.
Expand Down Expand Up @@ -142,3 +143,4 @@
- Updated GraphiQL to 3.8.3. ([#16836](https://github.com/craftcms/cms/pull/16836))
- Fixed a bug where indicator icons within field layout element chips didn’t have alternative text. ([#16297](https://github.com/craftcms/cms/discussions/16297))
- Fixed a bug where slide pickers within selected field layout elements didn’t have a label. ([#16696](https://github.com/craftcms/cms/pull/16696))
- Fixed a bug where nested elements’ `getOwner()` and `getPrimaryOwner()` methods weren’t working properly if they had been queried alongside other elements that didn’t share the same owner type. ([#16960](https://github.com/craftcms/cms/pull/16960))
23 changes: 19 additions & 4 deletions src/base/EagerLoadingFieldInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
/**
* EagerLoadingFieldInterface defines the common interface to be implemented by field classes that support eager-loading.
*
* @phpstan-import-type EagerLoadingMap from ElementInterface
* @author Pixel & Tonic, Inc. <[email protected]>
* @since 3.0.0
*/
Expand All @@ -20,13 +21,27 @@ interface EagerLoadingFieldInterface
*
* This method aids in the eager-loading of elements when performing an element query. The returned array should
* contain the following keys:
* - `elementType` – the fully qualified class name of the element type that should be eager-loaded
* - `map` – an array of element ID mappings, where each element is a sub-array with `source` and `target` keys.
* - `criteria` *(optional)* – Any criteria parameters that should be applied to the element query when fetching the eager-loaded elements.
* - `map` – an array defining source-target element mappings
* - `elementType` *(optional)* – the fully qualified class name of the element type that should be eager-loaded,
* if each target element is of the same element type
* - `criteria` *(optional)* – any criteria parameters that should be applied to the element query when fetching the
* eager-loaded elements
* - `createElement` *(optional)* - an element factory function, which will be passed the element query, the current
* query result data, and the first source element that the result was eager-loaded for
*
* Each mapping listed in `map` should be an array with the following keys:
* - `source` – the source element ID
* - `target` – the target element ID
* - `elementType` *(optional)* – the target element type (only checked for if the top-level array doesn’t specify
* an `elementType` key)
*
* Alternatively, the method can return an array of multiple sets of mappings, each with their own nested `map`,
* `elementType`, `criteria`, and `createElement` keys.
*
* @param ElementInterface[] $sourceElements An array of the source elements
* @return array|null|false The eager-loading element ID mappings, false if no mappings exist, or null if the result
* @return EagerLoadingMap|EagerLoadingMap[]|null|false The eager-loading element ID mappings, false if no mappings exist, or null if the result
* should be ignored.
* @see ElementInterface::eagerLoadingMap()
*/
public function getEagerLoadingMap(array $sourceElements): array|null|false;

Expand Down
19 changes: 15 additions & 4 deletions src/base/ElementInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
* @mixin CustomFieldBehavior
* @mixin Component
* @phpstan-require-extends Element
* @phpstan-type EagerLoadingMap array{elementType?:class-string<ElementInterface>,map:array{elementType?:class-string<ElementInterface>,source:int,target:int}[],criteria?:array,createElement?:callable}
* @author Pixel & Tonic, Inc. <[email protected]>
* @since 3.0.0
*/
Expand Down Expand Up @@ -574,13 +575,20 @@ public static function attributePreviewHtml(array $attribute): mixed;
*
* This method aids in the eager-loading of elements when performing an element query. The returned array should
* contain the following keys:
* - `elementType` – the fully qualified class name of the element type that should be eager-loaded
* - `map` – an array of element ID mappings, where each element is a sub-array with `source` and `target` keys
* - `map` – an array defining source-target element mappings
* - `elementType` *(optional)* – the fully qualified class name of the element type that should be eager-loaded,
* if each target element is of the same element type
* - `criteria` *(optional)* – any criteria parameters that should be applied to the element query when fetching the
* eager-loaded elements
* - `createElement` *(optional)* - an element factory function, which will be passed the element query, the current
* query result data, and the first source element that the result was eager-loaded for
*
* Each mapping listed in `map` should be an array with the following keys:
* - `source` – the source element ID
* - `target` – the target element ID
* - `elementType` *(optional)* – the target element type (only checked for if the top-level array doesn’t specify
* an `elementType` key)
*
* ```php
* use craft\base\ElementInterface;
* use craft\db\Query;
Expand Down Expand Up @@ -616,10 +624,13 @@ public static function attributePreviewHtml(array $attribute): mixed;
* }
* ```
*
* Alternatively, the method can return an array of multiple sets of mappings, each with their own nested `map`,
* `elementType`, `criteria`, and `createElement` keys.
*
* @param self[] $sourceElements An array of the source elements
* @param string $handle The property handle used to identify which target elements should be included in the map
* @return array|null|false The eager-loading element ID mappings, false if no mappings exist, or null if the result
* should be ignored
* @return EagerLoadingMap|EagerLoadingMap[]|null|false The eager-loading element ID mappings, false if no mappings
* exist, or null if the result should be ignored
*/
public static function eagerLoadingMap(array $sourceElements, string $handle): array|null|false;

Expand Down
9 changes: 2 additions & 7 deletions src/base/NestedElementTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,9 @@ public static function eagerLoadingMap(array $sourceElements, string $handle): a
switch ($handle) {
case 'owner':
case 'primaryOwner':
/** @var array<NestedElementInterface&self> $sourceElements */
$ownerType = $sourceElements[0]->ownerType();
if (!$ownerType) {
return false;
}

/** @phpstan-ignore-next-line */
return [
'elementType' => $ownerType,
/** @phpstan-ignore-next-line */
'map' => array_filter(array_map(function(NestedElementInterface $element) use ($handle) {
$ownerId = match ($handle) {
'owner' => $element->getOwnerId(),
Expand Down
1 change: 1 addition & 0 deletions src/elements/Entry.php
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,7 @@ public static function eagerLoadingMap(array $sourceElements, string $handle): a
}
}

/** @phpstan-ignore-next-line */
return [
'elementType' => User::class,
'map' => $map,
Expand Down
1 change: 1 addition & 0 deletions src/fields/BaseRelationField.php
Original file line number Diff line number Diff line change
Expand Up @@ -1046,6 +1046,7 @@ public function getEagerLoadingMap(array $sourceElements): array|null|false
$criteria['orderBy'] = ['structureelements.lft' => SORT_ASC];
}

/** @phpstan-ignore-next-line */
return [
'elementType' => static::elementType(),
'map' => $map,
Expand Down
Loading