diff --git a/src/base/EagerLoadingFieldInterface.php b/src/base/EagerLoadingFieldInterface.php index 38d5fb2d06a..9005f3e6658 100644 --- a/src/base/EagerLoadingFieldInterface.php +++ b/src/base/EagerLoadingFieldInterface.php @@ -13,7 +13,7 @@ * @author Pixel & Tonic, Inc. * @since 3.0.0 */ -interface EagerLoadingFieldInterface +interface EagerLoadingFieldInterface extends FieldInterface { /** * Returns an array that maps source-to-target element IDs based on this custom field. diff --git a/src/base/GqlInlineFragmentFieldInterface.php b/src/base/GqlInlineFragmentFieldInterface.php index 6d3fc183475..ffdbf6bddb9 100644 --- a/src/base/GqlInlineFragmentFieldInterface.php +++ b/src/base/GqlInlineFragmentFieldInterface.php @@ -13,7 +13,7 @@ * @author Pixel & Tonic, Inc. * @since 3.3.0 */ -interface GqlInlineFragmentFieldInterface +interface GqlInlineFragmentFieldInterface extends FieldInterface { /** * Returns a GraphQL fragment by its GraphQL fragment name. diff --git a/src/elements/db/ElementQuery.php b/src/elements/db/ElementQuery.php index 5877d7c7227..32cd461fd55 100644 --- a/src/elements/db/ElementQuery.php +++ b/src/elements/db/ElementQuery.php @@ -132,6 +132,7 @@ class ElementQuery extends Query implements ElementQueryInterface /** * @var FieldInterface[]|null The fields that may be involved in this query. + * @todo make this private in v6 */ public ?array $customFields = null; @@ -1437,6 +1438,19 @@ public function anyStatus(): static // Query preparation/execution // ------------------------------------------------------------------------- + /** + * Returns the custom fields that may be involved in this query. + * + * @since 5.5.0 + */ + public function getCustomFields(): array + { + if (!isset($this->customFields)) { + $this->customFields = $this->customFields(); + } + return $this->customFields; + } + /** * @inheritdoc */ diff --git a/src/gql/ElementQueryConditionBuilder.php b/src/gql/ElementQueryConditionBuilder.php index 289e4bfccfd..509c702f42c 100644 --- a/src/gql/ElementQueryConditionBuilder.php +++ b/src/gql/ElementQueryConditionBuilder.php @@ -37,6 +37,7 @@ use GraphQL\Language\AST\VariableNode; use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\WrappingType; +use Illuminate\Support\Arr; use yii\base\InvalidArgumentException; /** @@ -83,8 +84,13 @@ class ElementQueryConditionBuilder extends Component */ private ArgumentManager $_argumentManager; + /** + * @var FieldInterface[] + * @see setCustomFields() + */ + private array $_fields = []; + private array $_fragments; - private array $_eagerLoadableFieldsByContext = []; private array $_transformableAssetProperties = ['url', 'width', 'height']; private array $_additionalEagerLoadableNodes; @@ -102,15 +108,6 @@ public function __construct($config = []) } parent::__construct($config); - - // Cache all eager-loadable fields by context - $allFields = Craft::$app->getFields()->getAllFields(false); - - foreach ($allFields as $field) { - if ($field instanceof EagerLoadingFieldInterface) { - $this->_eagerLoadableFieldsByContext[$field->context][$field->handle] = $field; - } - } } /** @@ -135,6 +132,17 @@ public function setArgumentManager(ArgumentManager $argumentManager): void $this->_argumentManager = $argumentManager; } + /** + * Sets the custom fields that may be involved in the query. + * + * @param FieldInterface[] $fields + * @since 5.5.0 + */ + public function setCustomFields(array $fields): void + { + $this->_fields = $fields; + } + /** * Extract the query conditions based on the resolve information passed in the constructor. * Returns an array of [methodName => parameters] to be called on the element query. @@ -410,7 +418,7 @@ private function _traversAndBuildPlans(Node $parentNode, EagerLoadPlan $parentPl // If that's a GraphQL field if ($subNode instanceof FieldNode) { /** @var FieldInterface|null $craftContentField */ - $craftContentField = $this->_eagerLoadableFieldsByContext[$context][$nodeName] ?? null; + $craftContentField = Arr::first($this->_fields, fn(FieldInterface $field) => $field->handle === $nodeName); $transformableAssetProperty = ($rootOfAssetQuery || $parentField instanceof AssetField) && in_array($nodeName, $this->_transformableAssetProperties, true); $isAssetField = $craftContentField instanceof AssetField; diff --git a/src/gql/base/ElementResolver.php b/src/gql/base/ElementResolver.php index f5455d2e4d1..fd1c64f030c 100644 --- a/src/gql/base/ElementResolver.php +++ b/src/gql/base/ElementResolver.php @@ -10,6 +10,7 @@ use Craft; use craft\base\EagerLoadingFieldInterface; use craft\base\ElementInterface; +use craft\base\FieldInterface; use craft\base\GqlInlineFragmentFieldInterface; use craft\elements\db\ElementQuery; use craft\elements\ElementCollection; @@ -17,6 +18,7 @@ use craft\gql\ElementQueryConditionBuilder; use craft\helpers\Gql as GqlHelper; use GraphQL\Type\Definition\ResolveInfo; +use Illuminate\Support\Arr; /** * Class ElementResolver @@ -92,15 +94,18 @@ protected static function prepareElementQuery(mixed $source, array $arguments, ? return $query; } + $fields = $query->getCustomFields(); $parentField = null; if ($source instanceof ElementInterface) { - $fieldContext = $source->getFieldContext(); - $field = Craft::$app->getFields()->getFieldByHandle($fieldName, $fieldContext); + $field = Arr::first($fields, fn(FieldInterface $field) => $field->handle === $fieldName); // This will happen if something is either dynamically added or is inside an block element that didn't support eager-loading // and broke the eager-loading chain. In this case Craft has to provide the relevant context so the condition knows where it's at. - if (($fieldContext !== 'global' && $field instanceof GqlInlineFragmentFieldInterface) || $field instanceof EagerLoadingFieldInterface) { + if ( + ($source->getFieldContext() !== 'global' && $field instanceof GqlInlineFragmentFieldInterface) || + $field instanceof EagerLoadingFieldInterface + ) { $parentField = $field; } } @@ -109,6 +114,7 @@ protected static function prepareElementQuery(mixed $source, array $arguments, ? $conditionBuilder = empty($context['conditionBuilder']) ? Craft::createObject(['class' => ElementQueryConditionBuilder::class]) : $context['conditionBuilder']; $conditionBuilder->setResolveInfo($resolveInfo); $conditionBuilder->setArgumentManager($argumentManager); + $conditionBuilder->setCustomFields($fields); $conditions = $conditionBuilder->extractQueryConditions($parentField);