You are here

class EntityQuery in GraphQL 8.3

@GraphQLField( id = "entity_query", secure = false, type = "EntityQueryResult", arguments = { "filter" = "EntityQueryFilterInput", "sort" = "[EntityQuerySortInput]", "offset" = { "type" = "Int", "default" = 0 }, "limit" = { "type" = "Int", "default" = 10 }, "revisions" = { "type" = "EntityQueryRevisionMode", "default" = "default" } }, deriver = "Drupal\graphql_core\Plugin\Deriver\Fields\EntityQueryDeriver" )

This field is marked as not secure because it does not enforce entity field access over a chain of filters. For example node.uid.pass could be used as filter input which would disclose information about Drupal's password hashes.

Hierarchy

Expanded class hierarchy of EntityQuery

4 files declare their use of EntityQuery
EntityQueryExclusive.php in modules/graphql_core/src/Plugin/GraphQL/Fields/Entity/EntityQueryExclusive.php
EntityReferenceQuery.php in modules/graphql_core/src/Plugin/GraphQL/Fields/EntityReference/EntityReferenceQuery.php
EntityReferenceReverse.php in modules/graphql_core/src/Plugin/GraphQL/Fields/EntityReference/EntityReferenceReverse.php
EntityRevisions.php in modules/graphql_core/src/Plugin/GraphQL/Fields/Entity/EntityRevisions.php

File

modules/graphql_core/src/Plugin/GraphQL/Fields/EntityQuery/EntityQuery.php, line 45

Namespace

Drupal\graphql_core\Plugin\GraphQL\Fields\EntityQuery
View source
class EntityQuery extends FieldPluginBase implements ContainerFactoryPluginInterface {
  use DependencySerializationTrait;

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $pluginId, $pluginDefinition) {
    return new static($configuration, $pluginId, $pluginDefinition, $container
      ->get('entity_type.manager'));
  }

  /**
   * EntityQuery constructor.
   *
   * @param array $configuration
   *   The plugin configuration array.
   * @param string $pluginId
   *   The plugin id.
   * @param mixed $pluginDefinition
   *   The plugin definition.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager service.
   */
  public function __construct(array $configuration, $pluginId, $pluginDefinition, EntityTypeManagerInterface $entityTypeManager) {
    parent::__construct($configuration, $pluginId, $pluginDefinition);
    $this->entityTypeManager = $entityTypeManager;
  }

  /**
   * {@inheritdoc}
   */
  protected function getCacheDependencies(array $result, $value, array $args, ResolveContext $context, ResolveInfo $info) {
    $entityType = $this
      ->getEntityType($value, $args, $context, $info);
    $type = $this->entityTypeManager
      ->getDefinition($entityType);
    $metadata = new CacheableMetadata();
    $metadata
      ->addCacheTags($type
      ->getListCacheTags());
    $metadata
      ->addCacheContexts($type
      ->getListCacheContexts());
    return [
      $metadata,
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function resolveValues($value, array $args, ResolveContext $context, ResolveInfo $info) {
    (yield $this
      ->getQuery($value, $args, $context, $info));
  }

  /**
   * Retrieve the target entity type of this plugin.
   *
   * @param mixed $value
   *   The parent value.
   * @param array $args
   *   The field arguments array.
   * @param \Drupal\graphql\GraphQL\Execution\ResolveContext $context
   *   The resolve context.
   * @param \GraphQL\Type\Definition\ResolveInfo $info
   *   The resolve info object.
   *
   * @return null|string
   *   The entity type object or NULL if none could be derived.
   */
  protected function getEntityType($value, array $args, ResolveContext $context, ResolveInfo $info) {
    $definition = $this
      ->getPluginDefinition();
    if (isset($definition['entity_type'])) {
      return $definition['entity_type'];
    }
    if ($value instanceof EntityInterface) {
      return $value
        ->getEntityType()
        ->id();
    }
    return NULL;
  }

  /**
   * Create the full entity query for the plugin's entity type.
   *
   * @param mixed $value
   *   The parent entity type.
   * @param array $args
   *   The field arguments array.
   * @param \Drupal\graphql\GraphQL\Execution\ResolveContext $context
   *   The resolve context.
   * @param \GraphQL\Type\Definition\ResolveInfo $info
   *   The resolve info object.
   *
   * @return \Drupal\Core\Entity\Query\QueryInterface|null
   *   The entity query object.
   */
  protected function getQuery($value, array $args, ResolveContext $context, ResolveInfo $info) {
    if (!($query = $this
      ->getBaseQuery($value, $args, $context, $info))) {
      return NULL;
    }
    $query
      ->range($args['offset'], $args['limit']);
    if (array_key_exists('revisions', $args)) {
      $query = $this
        ->applyRevisionsMode($query, $args['revisions']);
    }
    if (array_key_exists('filter', $args)) {
      $query = $this
        ->applyFilter($query, $args['filter']);
    }
    if (array_key_exists('sort', $args)) {
      $query = $this
        ->applySort($query, $args['sort']);
    }
    return $query;
  }

  /**
   * Create the basic entity query for the plugin's entity type.
   *
   * @param mixed $value
   *   The parent entity type.
   * @param array $args
   *   The field arguments array.
   * @param \Drupal\graphql\GraphQL\Execution\ResolveContext $context
   *   The resolve context.
   * @param \GraphQL\Type\Definition\ResolveInfo $info
   *   The resolve info object.
   *
   * @return \Drupal\Core\Entity\Query\QueryInterface|null
   *   The entity query object.
   */
  protected function getBaseQuery($value, array $args, ResolveContext $context, ResolveInfo $info) {
    $entityType = $this
      ->getEntityType($value, $args, $context, $info);
    $entityStorage = $this->entityTypeManager
      ->getStorage($entityType);
    $query = $entityStorage
      ->getQuery();
    $query
      ->accessCheck(TRUE);

    // The context object can e.g. transport the parent entity language.
    $query
      ->addMetaData('graphql_context', $this
      ->getQueryContext($value, $args, $context, $info));
    return $query;
  }

  /**
   * Retrieves an arbitrary value to write into the query metadata.
   *
   * @param mixed $value
   *   The parent value.
   * @param array $args
   *   The field arguments array.
   * @param \Drupal\graphql\GraphQL\Execution\ResolveContext $context
   *   The resolve context.
   * @param \GraphQL\Type\Definition\ResolveInfo $info
   *   The resolve info object.
   *
   * @return mixed
   *   The query context.
   */
  protected function getQueryContext($value, array $args, ResolveContext $context, ResolveInfo $info) {

    // Forward the whole set of arguments by default.
    return [
      'parent' => $value,
      'args' => $args,
      'info' => $info,
    ];
  }

  /**
   * Apply the specified revision filtering mode to the query.
   *
   * @param \Drupal\Core\Entity\Query\QueryInterface $query
   *   The entity query object.
   * @param mixed $mode
   *   The revision query mode.
   *
   * @return \Drupal\Core\Entity\Query\QueryInterface
   *   The entity query object.
   */
  protected function applyRevisionsMode(QueryInterface $query, $mode) {
    if ($mode === 'all') {

      // Mark the query as such and sort by the revision id too.
      $query
        ->allRevisions();
      $query
        ->addTag('revisions');
    }
    else {
      if ($mode === 'latest') {

        // Mark the query to only include latest revision and sort by revision id.
        $query
          ->latestRevision();
        $query
          ->addTag('revisions');
      }
    }
    return $query;
  }

  /**
   * Apply the specified sort directives to the query.
   *
   * @param \Drupal\Core\Entity\Query\QueryInterface $query
   *   The entity query object.
   * @param mixed $sort
   *   The sort definitions from the field arguments.
   *
   * @return \Drupal\Core\Entity\Query\QueryInterface
   *   The entity query object.
   */
  protected function applySort(QueryInterface $query, $sort) {
    if (!empty($sort) && is_array($sort)) {
      foreach ($sort as $item) {
        $direction = !empty($item['direction']) ? $item['direction'] : 'DESC';
        $language = !empty($item['language']) ? $item['language'] : null;
        $query
          ->sort($item['field'], $direction, $language);
      }
    }
    return $query;
  }

  /**
   * Apply the specified filter conditions to the query.
   *
   * Recursively picks up all filters and aggregates them into condition groups
   * according to the nested structure of the filter argument.
   *
   * @param \Drupal\Core\Entity\Query\QueryInterface $query
   *   The entity query object.
   * @param mixed $filter
   *   The filter definitions from the field arguments.
   *
   * @return \Drupal\Core\Entity\Query\QueryInterface
   *   The entity query object.
   */
  protected function applyFilter(QueryInterface $query, $filter) {
    if (!empty($filter) && is_array($filter)) {

      //Conditions can be disabled. Check we are not adding an empty condition group.
      $filterConditions = $this
        ->buildFilterConditions($query, $filter);
      if (count($filterConditions
        ->conditions())) {
        $query
          ->condition($filterConditions);
      }
    }
    return $query;
  }

  /**
   * Recursively builds the filter condition groups.
   *
   * @param \Drupal\Core\Entity\Query\QueryInterface $query
   *   The entity query object.
   * @param array $filter
   *   The filter definitions from the field arguments.
   *
   * @return \Drupal\Core\Entity\Query\ConditionInterface
   *   The generated condition group according to the given filter definitions.
   *
   * @throws \GraphQL\Error\Error
   *   If the given operator and value for a filter are invalid.
   */
  protected function buildFilterConditions(QueryInterface $query, array $filter) {
    $conjunction = !empty($filter['conjunction']) ? $filter['conjunction'] : 'AND';
    $group = $conjunction === 'AND' ? $query
      ->andConditionGroup() : $query
      ->orConditionGroup();

    // Apply filter conditions.
    $conditions = !empty($filter['conditions']) ? $filter['conditions'] : [];
    foreach ($conditions as $condition) {

      // Check if we need to disable this condition.
      if (isset($condition['enabled']) && empty($condition['enabled'])) {
        continue;
      }
      $field = $condition['field'];
      $value = !empty($condition['value']) ? $condition['value'] : NULL;
      $operator = !empty($condition['operator']) ? $condition['operator'] : NULL;
      $language = !empty($condition['language']) ? $condition['language'] : NULL;

      // We need at least a value or an operator.
      if (empty($operator) && empty($value)) {
        throw new Error(sprintf("Missing value and operator in filter for '%s'.", $field));
      }
      else {
        if (!empty($operator) && $this
          ->isUnaryOperator($operator)) {
          if (empty($value) || count($value) > 1) {
            throw new Error(sprintf("Unary operators must be associated with a single value (field '%s').", $field));
          }

          // Pick the first item from the values.
          $value = reset($value);
        }
        else {
          if (!empty($operator) && $this
            ->isRangeOperator($operator)) {
            if (empty($value) || count($value) !== 2) {
              throw new Error(sprintf("Range operators must require exactly two values (field '%s').", $field));
            }
          }
          else {
            if (!empty($operator) && $this
              ->isNullOperator($operator)) {
              if (!empty($value)) {
                throw new Error(sprintf("Null operators must not be associated with a filter value (field '%s').", $field));
              }
            }
          }
        }
      }

      // If no operator is set, however, we default to EQUALS or IN, depending
      // on whether the given value is an array with one or more than one items.
      if (empty($operator)) {
        $value = count($value) === 1 ? reset($value) : $value;
        $operator = is_array($value) ? 'IN' : '=';
      }

      // Add the condition for the current field.
      $group
        ->condition($field, $value, $operator, $language);
    }

    // Apply nested filter group conditions.
    $groups = !empty($filter['groups']) ? $filter['groups'] : [];
    foreach ($groups as $args) {

      // By default, we use AND condition groups.
      // Conditions can be disabled. Check we are not adding an empty condition group.
      $filterConditions = $this
        ->buildFilterConditions($query, $args);
      if (count($filterConditions
        ->conditions())) {
        $group
          ->condition($filterConditions);
      }
    }
    return $group;
  }

  /**
   * Checks if an operator is a unary operator.
   *
   * @param string $operator
   *   The query operator to check against.
   *
   * @return bool
   *   TRUE if the given operator is unary, FALSE otherwise.
   */
  protected function isUnaryOperator($operator) {
    $unary = [
      "=",
      "<>",
      "<",
      "<=",
      ">",
      ">=",
      "LIKE",
      "NOT LIKE",
    ];
    return in_array($operator, $unary);
  }

  /**
   * Checks if an operator is a null operator.
   *
   * @param string $operator
   *   The query operator to check against.
   *
   * @return bool
   *   TRUE if the given operator is a null operator, FALSE otherwise.
   */
  protected function isNullOperator($operator) {
    $null = [
      "IS NULL",
      "IS NOT NULL",
    ];
    return in_array($operator, $null);
  }

  /**
   * Checks if an operator is a range operator.
   *
   * @param string $operator
   *   The query operator to check against.
   *
   * @return bool
   *   TRUE if the given operator is a range operator, FALSE otherwise.
   */
  protected function isRangeOperator($operator) {
    $null = [
      "BETWEEN",
      "NOT BETWEEN",
    ];
    return in_array($operator, $null);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
ArgumentAwarePluginTrait::buildArgumentDefault protected function Builds an argument's default value.
ArgumentAwarePluginTrait::buildArgumentDescription protected function Builds an argument's description.
ArgumentAwarePluginTrait::buildArguments protected function Builds the list of arguments.
ArgumentAwarePluginTrait::buildArgumentType protected function Builds an argument's type.
CacheablePluginTrait::buildCacheContexts protected function
DependencySerializationTrait::$_entityStorages protected property An array of entity type IDs keyed by the property name of their storages.
DependencySerializationTrait::$_serviceIds protected property An array of service IDs keyed by property name used for serialization.
DependencySerializationTrait::__sleep public function 1
DependencySerializationTrait::__wakeup public function 2
DeprecatablePluginTrait::buildDeprecationReason protected function
DescribablePluginTrait::buildDescription protected function
EntityQuery::$entityTypeManager protected property The entity type manager.
EntityQuery::applyFilter protected function Apply the specified filter conditions to the query.
EntityQuery::applyRevisionsMode protected function Apply the specified revision filtering mode to the query.
EntityQuery::applySort protected function Apply the specified sort directives to the query.
EntityQuery::buildFilterConditions protected function Recursively builds the filter condition groups.
EntityQuery::create public static function Creates an instance of the plugin. Overrides ContainerFactoryPluginInterface::create
EntityQuery::getBaseQuery protected function Create the basic entity query for the plugin's entity type. 4
EntityQuery::getCacheDependencies protected function Retrieve the list of cache dependencies for a given value and arguments. Overrides FieldPluginBase::getCacheDependencies
EntityQuery::getEntityType protected function Retrieve the target entity type of this plugin.
EntityQuery::getQuery protected function Create the full entity query for the plugin's entity type.
EntityQuery::getQueryContext protected function Retrieves an arbitrary value to write into the query metadata. 1
EntityQuery::isNullOperator protected function Checks if an operator is a null operator.
EntityQuery::isRangeOperator protected function Checks if an operator is a range operator.
EntityQuery::isUnaryOperator protected function Checks if an operator is a unary operator.
EntityQuery::resolveValues public function Retrieve the list of field values. Overrides FieldPluginBase::resolveValues
EntityQuery::__construct public function EntityQuery constructor. Overrides PluginBase::__construct
FieldPluginBase::$isLanguageAware protected property Static cache for `isLanguageAwareField()`
FieldPluginBase::$languageContext protected property The language context service.
FieldPluginBase::$renderer protected property The renderer service. 1
FieldPluginBase::createInstance public static function Overrides FieldPluginInterface::createInstance
FieldPluginBase::getDefinition public function Returns the plugin's type or field definition for the schema. Overrides FieldPluginInterface::getDefinition
FieldPluginBase::getLanguageContext protected function Get the language context instance.
FieldPluginBase::getRenderer protected function Get the renderer service.
FieldPluginBase::isLanguageAwareField protected function Indicator if the field is language aware. 1
FieldPluginBase::resolve public function 1
FieldPluginBase::resolveDeferred protected function
FieldPluginBase::unwrapResult protected function Unwrap the resolved values.
PluginBase::$configuration protected property Configuration information passed into the plugin. 1
PluginBase::$pluginDefinition protected property The plugin implementation definition. 1
PluginBase::$pluginId protected property The plugin_id.
PluginBase::DERIVATIVE_SEPARATOR constant A string which is used to separate base plugin IDs from the derivative ID.
PluginBase::getBaseId public function Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface::getBaseId
PluginBase::getDerivativeId public function Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface::getDerivativeId
PluginBase::getPluginDefinition public function Gets the definition of the plugin implementation. Overrides PluginInspectionInterface::getPluginDefinition 3
PluginBase::getPluginId public function Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface::getPluginId
PluginBase::isConfigurable public function Determines if the plugin is configurable.
TypedPluginTrait::buildType protected function