sparql_entity_storage-8.x-1.0-alpha8/src/Entity/Query/Sparql/Query.php

src/Entity/Query/Sparql/Query.php
<?php

declare(strict_types=1);

namespace Drupal\sparql_entity_storage\Entity\Query\Sparql;

use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Query\QueryBase;
use Drupal\Core\Entity\Query\Sql\ConditionAggregate;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\sparql_entity_storage\Driver\Database\sparql\ConnectionInterface;
use Drupal\sparql_entity_storage\SparqlEntityStorageFieldHandlerInterface;
use Drupal\sparql_entity_storage\SparqlEntityStorageGraphHandlerInterface;
use Drupal\sparql_entity_storage\SparqlEntityStorageInterface;
use EasyRdf\Sparql\Result;

/**
 * The base entity query class for SPARQL stored entities.
 */
class Query extends QueryBase implements SparqlQueryInterface {

  /**
   * The connection object.
   */
  protected ConnectionInterface $connection;

  /**
   * The string query.
   */
  public string $query = '';

  /**
   * Indicates if preExecute() has already been called.
   */
  protected bool $prepared = FALSE;

  /**
   * The graph IDs from where the query is going load entities from.
   *
   * If the value is NULL, the query will load entities from all graphs.
   *
   * @var string[]|null
   */
  protected ?array $graphIds = NULL;

  /**
   * An array that is meant to hold the results.
   */
  protected Result $result;

  /**
   * The SPARQL entity storage.
   */
  protected SparqlEntityStorageInterface $entityStorage;

  /**
   * The entity type manager service.
   */
  protected EntityTypeManagerInterface $entityTypeManager;

  /**
   * The SPARQL graph handler service object.
   */
  protected SparqlEntityStorageGraphHandlerInterface $graphHandler;

  /**
   * The SPARQL field mapping handler service.
   */
  protected SparqlEntityStorageFieldHandlerInterface $fieldHandler;

  /**
   * The module handler service.
   */
  protected ModuleHandlerInterface $moduleHandler;

  /**
   * The language manager service.
   */
  protected LanguageManagerInterface $languageManager;

  /**
   * The entity type id key.
   */
  protected string $idKey;

  /**
   * The entity type bundle key, if any.
   *
   * @var string|false
   */
  protected $bundleKey;

  /**
   * Constructs a query object.
   *
   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
   *   The entity type definition.
   * @param string $conjunction
   *   - AND: all of the conditions on the query need to match.
   *   - OR: at least one of the conditions on the query need to match.
   * @param \Drupal\sparql_entity_storage\Driver\Database\sparql\ConnectionInterface $connection
   *   The database connection to run the query against.
   * @param array $namespaces
   *   List of potential namespaces of the classes belonging to this query.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager service object.
   * @param \Drupal\sparql_entity_storage\SparqlEntityStorageGraphHandlerInterface $sparql_graph_handler
   *   The SPARQL graph handler service.
   * @param \Drupal\sparql_entity_storage\SparqlEntityStorageFieldHandlerInterface $sparql_field_handler
   *   The SPARQL field mapping handler service.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler service.
   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
   *   The language manager service.
   */
  public function __construct(EntityTypeInterface $entity_type, $conjunction, ConnectionInterface $connection, array $namespaces, EntityTypeManagerInterface $entity_type_manager, SparqlEntityStorageGraphHandlerInterface $sparql_graph_handler, SparqlEntityStorageFieldHandlerInterface $sparql_field_handler, ModuleHandlerInterface $module_handler, LanguageManagerInterface $language_manager) {
    // Assign the handlers before calling the parent so that they can be passed
    // to the condition class properly.
    $this->graphHandler = $sparql_graph_handler;
    $this->fieldHandler = $sparql_field_handler;
    $this->entityTypeManager = $entity_type_manager;
    $this->connection = $connection;
    $this->moduleHandler = $module_handler;
    $this->languageManager = $language_manager;

    parent::__construct($entity_type, $conjunction, $namespaces);

    $this->bundleKey = $entity_type->getKey('bundle');
    $this->idKey = $entity_type->getKey('id');

    // Set a unique tag for the SPARQL queries.
    $this->addTag('sparql');
    $this->addMetaData('entity_type', $this->entityType);
  }

  /**
   * {@inheritdoc}
   */
  public function getEntityType(): EntityTypeInterface {
    return $this->entityType;
  }

  /**
   * {@inheritdoc}
   */
  public function getEntityStorage(): SparqlEntityStorageInterface {
    if (!isset($this->entityStorage)) {
      $this->entityStorage = $this->entityTypeManager->getStorage($this->getEntityTypeId());
    }
    return $this->entityStorage;
  }

  /**
   * Implements \Drupal\Core\Entity\Query\QueryInterface::execute().
   */
  public function execute() {
    return $this
      ->prepare()
      ->compile()
      ->addSort()
      ->addPager()
      ->run()
      ->result();
  }

  /**
   * {@inheritdoc}
   */
  public function graphs(?array $graph_ids = NULL): SparqlQueryInterface {
    $this->graphIds = $graph_ids;
    return $this;
  }

  /**
   * Initialize the query.
   *
   * @return $this
   */
  protected function prepare(): SparqlQueryInterface {
    // Running as count query?
    if ($this->count) {
      if (is_string($this->count)) {
        $this->query = 'SELECT count(' . $this->count . ') AS ?count ';
      }
      else {
        $this->query = 'SELECT count(?entity) AS ?count ';
      }
    }
    else {
      $this->query = 'SELECT DISTINCT(?entity) ';
    }
    $this->query .= "\n";

    if (!$this->graphIds) {
      // Allow all default graphs for this entity type.
      $this->graphIds = $this->graphHandler->getEntityTypeDefaultGraphIds($this->getEntityTypeId());
    }

    foreach ($this->getGraphUris() as $graph_uri) {
      $this->query .= "FROM <$graph_uri>\n";
    }

    foreach ($this->sort as $data) {
      if (in_array($data['field'], [$this->idKey, $this->bundleKey])) {
        continue;
      }
      // Add a requirement for each sorting criteria.
      $this->addFieldMappingRequirement($this->entityTypeId, $data['field']);
    }

    return $this;
  }

  /**
   * Add the registered conditions to the WHERE clause.
   *
   * @return $this
   */
  protected function compile(): SparqlQueryInterface {
    // Modules may alter all queries or only those having a particular tag.
    if (isset($this->alterTags)) {
      // Remap the entity reference default tag to the SPARQL entity reference
      // because the first one requires that the query is an instance of the
      // SelectInterface.
      // @todo Maybe overwrite the default selection class?
      if (isset($this->alterTags['entity_reference'])) {
        $this->alterTags['sparql_reference'] = $this->alterTags['entity_reference'];
        unset($this->alterTags['entity_reference']);
      }
      $hooks = ['query'];
      foreach ($this->alterTags as $tag => $value) {
        $hooks[] = 'query_' . $tag;
      }
      $this->moduleHandler->alter($hooks, $this);
    }

    $this->condition->compile($this);
    $this->query .= "WHERE {\n{$this->condition}\n}";

    return $this;
  }

  /**
   * Adds the sort to the build query.
   *
   * @return \Drupal\sparql_entity_storage\Entity\Query\Sparql\Query
   *   Returns the called object.
   */
  protected function addSort(): SparqlQueryInterface {
    if ($this->count) {
      $this->sort = [];
    }

    if (empty($this->sort)) {
      return $this;
    }

    $fragments = [];
    foreach ($this->sort as $data) {
      $field = $data['field'];
      if ($field === $this->idKey) {
        $field = SparqlCondition::ID_KEY;
      }
      elseif ($field === $this->bundleKey) {
        $field = SparqlArg::toVar($field);
      }
      else {
        // Build the property to sort by just like it is build in condition().
        // @see: \Drupal\sparql_entity_storage\Entity\Query\Sparql\SparqlCondition::condition.
        $field_name_parts = explode('.', $field);
        $field = $field_name_parts[0];
        $column = $field_name_parts[1] ?? $this->fieldHandler->getFieldMainProperty($this->getEntityTypeId(), $field);
        $field = SparqlArg::toVar("{$field}__{$column}");
      }
      $fragments[] = empty($data['direction']) ? $field : "{$data['direction']}({$field})";
    }

    $this->query .= "\nORDER BY " . implode(', ', $fragments);
    return $this;
  }

  /**
   * Add pager to query.
   */
  protected function addPager(): SparqlQueryInterface {
    $this->initializePager();
    if (!$this->count && $this->range) {
      $this->query .= 'LIMIT ' . $this->range['length'] . "\n";
      $this->query .= 'OFFSET ' . $this->range['start'] . "\n";
    }
    return $this;
  }

  /**
   * Commit the query to the backend.
   */
  protected function run(): SparqlQueryInterface {
    $this->result = $this->connection->query($this->query);
    return $this;
  }

  /**
   * Do the actual query building.
   */
  protected function result() {
    // Count query.
    if ($this->count) {
      foreach ($this->result as $result) {
        return (string) $result->count;
      }
    }
    $uris = [];

    // SELECT query.
    foreach ($this->result as $result) {
      // If the query does not return any results, EasyRdf\Sparql\Result still
      // contains an empty result object. If this is the case, skip it.
      if (!empty((array) $result)) {
        $uri = (string) $result->entity;
        $uris[$uri] = $uri;
      }
    }
    return $uris;
  }

  /**
   * {@inheritdoc}
   */
  public function sort($field, $direction = 'ASC', $langcode = NULL) {
    $direction = strtoupper($direction);
    if (!in_array($direction, ['ASC', 'DESC'])) {
      throw new \RuntimeException('Only "ASC" and "DESC" are allowed as sort order.');
    }

    // Unlike the normal SQL queries where a column not defined can be used for
    // sorting if exists in the table, in SPARQL, the sort argument must be
    // defined in the WHERE clause. Any sort property, therefore, must will be
    // included with an EXISTS condition.
    // Also, the $idKey and $bundleKey properties cannot be added as triples as
    // they cannot be the object of the triple.
    if (!in_array($field, [$this->idKey, $this->bundleKey])) {
      $this->exists($field);
    }
    return parent::sort($field, $direction, $langcode);
  }

  /**
   * Returns the array of conditions.
   *
   * @return array
   *   The array of conditions.
   */
  public function &conditions(): array {
    return $this->condition->conditions();
  }

  /**
   * {@inheritdoc}
   */
  public function existsAggregate($field, $function, $langcode = NULL) {
    return $this->conditionAggregate->exists($field, $function, $langcode);
  }

  /**
   * {@inheritdoc}
   */
  public function notExistsAggregate($field, $function, $langcode = NULL) {
    return $this->conditionAggregate->notExists($field, $function, $langcode);
  }

  /**
   * {@inheritdoc}
   */
  public function conditionAggregateGroupFactory($conjunction = 'AND') {
    return new ConditionAggregate($conjunction, $this);
  }

  /**
   * {@inheritdoc}
   */
  protected function conditionGroupFactory($conjunction = 'AND') {
    $class = static::getClass($this->namespaces, 'SparqlCondition');
    return new $class($conjunction, $this, $this->namespaces, $this->fieldHandler, $this->languageManager);
  }

  /**
   * Wrapper method to set a mapping requirement in the conditions group.
   *
   * @param string $entity_type_id
   *   The entity type id.
   * @param string $field
   *   The field name.
   */
  protected function addFieldMappingRequirement(string $entity_type_id, string $field): void {
    $this->condition->addFieldMappingRequirement($entity_type_id, $field);
  }

  /**
   * Returns a list of graph URIs that should be queried.
   *
   * @return string[]
   *   List of graph URIs.
   */
  protected function getGraphUris(): array {
    if ($bundle_conditions = $this->getBundleConditions()) {
      // When the query has at least a bundle condition, we optimize the list of
      // graphs to be searched.
      $bundle_uris = ['IN' => [], 'NOT IN' => []];
      $entity_type_graph_uris = $this->graphHandler->getEntityTypeGraphUris($this->getEntityTypeId());
      foreach ($bundle_conditions as $type => $bundle_ids) {
        foreach ($bundle_ids as $bundle_id) {
          if (!isset($entity_type_graph_uris[$bundle_id])) {
            $entity_type_graph_uris[$bundle_id][] = '';
            continue;
          }
          foreach (array_values(array_intersect_key($entity_type_graph_uris[$bundle_id], array_flip($this->graphIds))) as $uri) {
            $bundle_uris[$type][] = $uri;
          }
        }
      }
      $bundle_uris['IN'] = $bundle_uris['IN'] ?: $this->graphHandler->getEntityTypeGraphUrisFlatList($this->getEntityTypeId(), $this->graphIds);
      return array_diff($bundle_uris['IN'], $bundle_uris['NOT IN']);
    }
    return $this->graphHandler->getEntityTypeGraphUrisFlatList($this->getEntityTypeId(), $this->graphIds);
  }

  /**
   * Returns the bundle conditions, if any.
   *
   * @return array
   *   The bundle conditions with two keys, 'IN' and/or 'NOT IN'. If the entity
   *   type supports no bundle or there are no bundle conditions, an empty array
   *   is returned.
   */
  protected function getBundleConditions(): array {
    $conditions = [];
    if ($this->bundleKey) {
      foreach ($this->conditions() as $condition) {
        if ($condition['field'] === $this->bundleKey) {
          foreach ($condition['value'] as $bundle_id) {
            if (!isset($conditions[$condition['operator']])) {
              $conditions[$condition['operator']] = [];
            }
            if (array_search($bundle_id, $conditions[$condition['operator']]) === FALSE) {
              $conditions[$condition['operator']][] = $bundle_id;
            }
          }
        }
      }
    }
    return $conditions;
  }

  /**
   * Return the query string for debugging help.
   *
   * @return string
   *   Query.
   */
  public function __toString() {
    return $this->query;
  }

}

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

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