entity_hierarchy-8.x-2.24/src/Storage/QueryBuilder.php
src/Storage/QueryBuilder.php
<?php declare(strict_types=1); namespace Drupal\entity_hierarchy\Storage; use Drupal\Core\Database\Connection; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Psr\Log\LoggerInterface; /** * The query builder for a specific hierarchical field instance. */ class QueryBuilder { use QueryBuilderTrait; /** * Constructor for query builder. * * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $fieldStorageDefinition * The field storage for this hierarchy field. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager * The entity type manage service. * @param \Drupal\Core\Database\Connection $database * The database connection service. * @param \Psr\Log\LoggerInterface $logger * The logger service. * * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException */ public function __construct( protected readonly FieldStorageDefinitionInterface $fieldStorageDefinition, protected readonly EntityTypeManagerInterface $entityTypeManager, protected readonly Connection $database, protected readonly LoggerInterface $logger, ) { $this->setupTablesAndColumns(); } /** * Find the ancestor records of an entity. * * @param \Drupal\Core\Entity\ContentEntityInterface $entity * The entity to find the ancestors of. * * @return \Drupal\entity_hierarchy\Storage\RecordCollection * The collection of records returned for the ancestor query. */ public function findAncestors(ContentEntityInterface $entity): RecordCollection { $sql = $this->getAncestorSql() . " SELECT * FROM ancestors ORDER BY depth"; $result = $this->database->query($sql, [ ':id' => $entity->id(), ':revisionId' => $entity->getRevisionId() ?: $entity->id(), ]); $type = $this->fieldStorageDefinition->getTargetEntityTypeId(); $records = $result->fetchAll(\PDO::FETCH_CLASS, Record::class, [$type]); if (!$this->fieldStorageDefinition->isBaseField()) { // Drupal doesn't store an empty row for a NULL parent. Add it back in. if ($first_record = reset($records)) { $record = Record::create($type, $first_record->getTargetId(), 0, $first_record->getDepth() - 1); } else { $record = Record::create($type, (int) $entity->id(), 0, 0); } array_unshift($records, $record); } return new RecordCollection($records); } /** * Find the root record of an entity. * * @param \Drupal\Core\Entity\ContentEntityInterface $entity * The entity to find the depth of. * * @return \Drupal\entity_hierarchy\Storage\Record * The root record of an ancestor query. */ public function findRoot(ContentEntityInterface $entity): ?Record { $ancestors = $this->findAncestors($entity); // Convert FALSE to NULL. return $ancestors->getIterator()->current() ?: NULL; } /** * Find the parent of an entity. * * This isn't really a query builder function but adding here for * completeness. * * @param \Drupal\Core\Entity\ContentEntityInterface $entity * The entity to find the depth of. * * @return \Drupal\Core\Entity\ContentEntityInterface * The parent entity of an entity. */ public function findParent(ContentEntityInterface $entity): ?ContentEntityInterface { $field_name = $this->fieldStorageDefinition->getName(); if (!$entity->hasField($field_name)) { return NULL; } return $entity->get($field_name)->entity; } /** * Find the depth of an entity from the root. * * @param \Drupal\Core\Entity\ContentEntityInterface $entity * The entity to find the depth of. * * @return int * The depth. */ public function findDepth(ContentEntityInterface $entity): int { $sql = $this->getAncestorSql() . " SELECT depth FROM ancestors ORDER BY depth LIMIT 1"; $result = $this->database->query($sql, [ ':id' => $entity->id(), ':revisionId' => $entity->getRevisionId() ?: $entity->id(), ]); if ($object = $result->fetchObject()) { $depth = $object->depth * -1; if (!$this->fieldStorageDefinition->isBaseField()) { // Non base fields don't have a record where parent is null. Compensate. $depth = $depth + 1; } return $depth; } return 0; } /** * Find the descendant records of an entity. * * @param \Drupal\Core\Entity\ContentEntityInterface $entity * The entity to find the descendants of. * @param int $depth * The depth of descendants to load. * @param int $start * The starting depth. * * @return \Drupal\entity_hierarchy\Storage\RecordCollection * A collection of records reflecting the descendants of the entity. */ public function findDescendants(ContentEntityInterface $entity, int $depth = 0, int $start = 1): RecordCollection { $sql = $this->getDescendantSql() . " SELECT id, revisionId, targetId, weight, depth FROM descendants WHERE depth >= :start"; $params = [ ':targetId' => $entity->id(), ':start' => $start, ]; if ($depth > 0) { $sql .= " AND depth < :depth"; $params[':depth'] = $start + $depth; } $sql .= " ORDER by weight, id"; $result = $this->database->query($sql, $params); $type = $this->fieldStorageDefinition->getTargetEntityTypeId(); $records = $result->fetchAll(\PDO::FETCH_CLASS, Record::class, [$type]); return new RecordCollection($records); } /** * Helper function to only find depth 1 descendants. * * @param \Drupal\Core\Entity\ContentEntityInterface $entity * The entity to check. * * @return \Drupal\entity_hierarchy\Storage\RecordCollection * A collection of records reflecting the children of the entity. */ public function findChildren(ContentEntityInterface $entity): RecordCollection { return $this->findDescendants($entity, 1); } }