graphql_core_schema-1.0.x-dev/src/Plugin/GraphQL/EntityQueryBase.php

src/Plugin/GraphQL/EntityQueryBase.php
<?php

namespace Drupal\graphql_core_schema\Plugin\GraphQL;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Query\QueryInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\graphql\GraphQL\Execution\FieldContext;
use Drupal\graphql\Plugin\GraphQL\DataProducer\DataProducerPluginBase;
use GraphQL\Error\UserError;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * The base class for entity queries.
 */
class EntityQueryBase extends DataProducerPluginBase implements ContainerFactoryPluginInterface {

  const MAX_LIMIT = 1000;

  const OPERATOR_MAPPING = [
    'BETWEEN' => 'BETWEEN',
    'CONTAINS' => 'CONTAINS',
    'ENDS_WITH' => 'ENDS_WITH',
    'EQUAL' => '=',
    'GREATER_THAN' => '>',
    'GREATER_THAN_OR_EQUAL' => '>=',
    'IN' => 'IN',
    'IS_NOT_NULL' => 'IS NOT NULL',
    'IS_NULL' => 'IS NULL',
    'LIKE' => 'LIKE',
    'NOT_BETWEEN' => 'NOT BETWEEN',
    'NOT_EQUAL' => '!=',
    'NOT_IN' => 'NOT IN',
    'NOT_LIKE' => 'NOT LIKE',
    'REGEXP' => 'REGEXP',
    'SMALLER_THAN' => '<',
    'SMALLER_THAN_OR_EQUAL' => '<=',
    'STARTS_WITH' => 'STARTS_WITH',
  ];

  public function __construct(
    array $configuration,
    $pluginId,
    $pluginDefinition,
    protected EntityTypeManagerInterface $entityTypeManager,
    protected LanguageManagerInterface $languageManager,
  ) {
    parent::__construct($configuration, $pluginId, $pluginDefinition);
  }

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

  /**
   * Build the base query.
   *
   * @param string $entityTypeKey
   *   The entity type key.
   * @param int $limit
   *   The limit.
   * @param int $offset
   *   The offset.
   * @param mixed $revisions
   *   Revision mode.
   * @param mixed $sort
   *   The sort.
   * @param mixed $filter
   *   The filters.
   * @param \Drupal\Core\Cache\RefinableCacheableDependencyInterface $metadata
   *   The metadata.
   *
   * @return \Drupal\Core\Entity\Query\QueryInterface
   *   The query.
   */
  protected function getBaseQuery($entityTypeKey, $limit, $offset, $revisions, $sort, $filter, FieldContext $metadata) {
    if ($limit > static::MAX_LIMIT) {
      throw new UserError(sprintf('Exceeded maximum query limit: %s.', static::MAX_LIMIT));
    }

    // If limit is set but offset is not, the limit argument would be ignored.
    if (!empty($limit) && empty($offset)) {
      $offset = 0;
    }

    $storage = $this->entityTypeManager->getStorage(strtolower($entityTypeKey));
    $entityType = $storage->getEntityType();
    $query = $storage->getQuery()->accessCheck(TRUE);

    $query->range($offset, $limit);

    if ($sort) {
      $this->applySort($query, $sort);
    }

    if ($revisions) {
      $this->applyRevisionsMode($query, $revisions);
    }

    if ($filter) {
      $this->applyFilter($query, $filter);
    }

    // When querying for users, we have to explicity exclude the user with ID 0 (anonymous), because it can't be loaded.
    if ($entityType->id() === 'user') {
      $query->condition('uid', '0', '<>');
    }

    $metadata->addCacheTags($entityType->getListCacheTags());
    $metadata->addCacheContexts($entityType->getListCacheContexts());

    return $query;
  }

  /**
   * 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');
    }
    elseif ($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;

      // Map GraphQL operator to proper ConditionInterface operator.
      $operator = static::OPERATOR_MAPPING[$operator] ?? NULL;

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

        // Pick the first item from the values.
        $value = reset($value);
      }
      // Range operators need exactly two values.
      elseif (!empty($operator) && $this->isRangeOperator($operator)) {
        if (empty($value) || count($value) !== 2) {
          throw new UserError(sprintf("Range operators must require exactly two values (field '%s').", $field));
        }
      }
      // Null operators can't have a value set.
      elseif (!empty($operator) && $this->isNullOperator($operator)) {
        if (!empty($value)) {
          throw new UserError(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", "CONTAINS", "STARTS_WITH", "ENDS_WITH"];
    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);
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc