function SqlContentEntityStorage::loadFromDedicatedTables

Same name and namespace in other branches
  1. 11.x core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php \Drupal\Core\Entity\Sql\SqlContentEntityStorage::loadFromDedicatedTables()
  2. 10 core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php \Drupal\Core\Entity\Sql\SqlContentEntityStorage::loadFromDedicatedTables()
  3. 9 core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php \Drupal\Core\Entity\Sql\SqlContentEntityStorage::loadFromDedicatedTables()
  4. 8.9.x core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php \Drupal\Core\Entity\Sql\SqlContentEntityStorage::loadFromDedicatedTables()

Loads values of fields stored in dedicated tables for a group of entities.

Parameters

array &$values: An array of values keyed by entity ID.

bool $load_from_revision: Flag to indicate whether revisions should be loaded or not.

array &$translations: List of translations, keyed on the entity ID.

1 call to SqlContentEntityStorage::loadFromDedicatedTables()
SqlContentEntityStorage::mapFromStorageRecords in core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
Maps from storage records to entity objects, and attaches fields.

File

core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php, line 1231

Class

SqlContentEntityStorage
A content entity database storage implementation.

Namespace

Drupal\Core\Entity\Sql

Code

protected function loadFromDedicatedTables(array &$values, $load_from_revision, &$translations) {
  if (empty($values)) {
    return;
  }
  // Collect entities ids, bundles and languages.
  $bundles = [];
  $ids = [];
  $default_langcodes = [];
  foreach ($values as $key => $entity_values) {
    $bundles[$this->bundleKey ? $entity_values[$this->bundleKey][LanguageInterface::LANGCODE_DEFAULT] : $this->entityTypeId] = TRUE;
    $ids[] = !$load_from_revision ? $key : $entity_values[$this->revisionKey][LanguageInterface::LANGCODE_DEFAULT];
    if ($this->langcodeKey && isset($entity_values[$this->langcodeKey][LanguageInterface::LANGCODE_DEFAULT])) {
      $default_langcodes[$key] = $entity_values[$this->langcodeKey][LanguageInterface::LANGCODE_DEFAULT];
    }
  }
  // Collect impacted fields.
  $storage_definitions = [];
  $definitions = [];
  $table_mapping = $this->getTableMapping();
  foreach ($bundles as $bundle => $v) {
    $definitions[$bundle] = $this->entityFieldManager
      ->getFieldDefinitions($this->entityTypeId, $bundle);
    foreach ($definitions[$bundle] as $field_name => $field_definition) {
      $storage_definition = $field_definition->getFieldStorageDefinition();
      if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
        $storage_definitions[$field_name] = $storage_definition;
      }
    }
  }
  $single_cardinality_fields = [];
  $multiple_cardinality_fields = [];
  $field_definition_columns = [];
  $field_columns = [];
  foreach ($storage_definitions as $field_name => $storage_definition) {
    $field_columns[$field_name] = $this->tableMapping
      ->getColumnNames($field_name);
    $field_definition_columns[$field_name] = $storage_definition->getColumns();
    if ($storage_definition->getCardinality() === 1) {
      $single_cardinality_fields[$field_name] = $storage_definition;
    }
    else {
      $multiple_cardinality_fields[$field_name] = $storage_definition;
    }
  }
  $id_key = !$load_from_revision ? 'entity_id' : 'revision_id';
  // Because any field could potentially have no data, we need to begin
  // the query from a table that will reliably exist, which means the base,
  // data, or revision table.
  $base_table = $load_from_revision ? $this->revisionDataTable ?? $this->revisionTable : $this->dataTable ?? $this->baseTable;
  $base_id_key = !$load_from_revision ? $this->idKey : $this->revisionKey;
  $base_query = $this->database
    ->select($base_table, $base_table)
    ->fields($base_table)
    ->condition("[{$base_table}].[{$base_id_key}]", $ids, 'IN');
  // Alias the base langcode field so that it does not get overwritten by
  // results from field tables.
  $base_langcode_alias = '';
  if ($this->langcodeKey) {
    $base_langcode_alias = $base_table . '__' . $this->langcodeKey;
    $base_query->addField($base_table, $this->langcodeKey, $base_langcode_alias);
  }
  $query = clone $base_query;
  if ($this->revisionDataTable) {
    // Find revisioned fields that are not entity keys. Exclude the langcode
    // key as the base table holds only the default language.
    $base_fields = array_diff($table_mapping->getFieldNames($this->baseTable), [
      $this->langcodeKey,
    ]);
    $revisioned_fields = array_diff($table_mapping->getFieldNames($this->revisionDataTable), $base_fields);
    // Find fields that are not revisioned or entity keys. Data fields have
    // the same value regardless of entity revision.
    $data_fields = array_diff($table_mapping->getFieldNames($this->dataTable), $revisioned_fields, $base_fields);
    // If there are no data fields then only revisioned fields are needed
    // else both data fields and revisioned fields are needed to map the
    // entity values.
    $shared_fields = $revisioned_fields;
    if ($data_fields) {
      $shared_fields = array_merge($revisioned_fields, $data_fields);
      if ($load_from_revision) {
        $query->leftJoin($this->dataTable, 'data', "([data].[{$this->idKey}] = [{$base_table}].[{$this->idKey}] AND [{$base_table}].[{$this->langcodeKey}] = [data].[{$this->langcodeKey}])");
        $column_names = [];
        // Some fields can have more then one columns in the data table so
        // column names are needed.
        foreach ($data_fields as $data_field) {
          // \Drupal\Core\Entity\Sql\TableMappingInterface::getColumnNames()
          // returns an array keyed by property names so remove the keys
          // before array_merge() to avoid losing data with fields having the
          // same columns i.e. value.
          $column_names[] = array_values($table_mapping->getColumnNames($data_field));
        }
        $column_names = array_merge(...$column_names);
        $query->fields('data', $column_names);
      }
    }
  }
  else {
    $shared_fields = $table_mapping->getFieldNames($base_table);
  }
  foreach ($shared_fields as $field_name) {
    $storage_definition = $this->fieldStorageDefinitions[$field_name];
    $field_definition_columns[$field_name] = $storage_definition->getColumns();
    $field_columns[$field_name] = $table_mapping->getColumnNames($field_name);
  }
  // Add a left join for each single cardinality field.
  foreach ($single_cardinality_fields as $field_name => $storage_definition) {
    $table = !$load_from_revision ? $table_mapping->getDedicatedDataTableName($storage_definition) : $table_mapping->getDedicatedRevisionTableName($storage_definition);
    // If the entity is translatable, add the langcode to the join and
    // a condition on valid langcodes.
    if ($this->langcodeKey) {
      $query->leftJoin($table, $table, "[{$table}].[{$id_key}] = [{$base_table}].[{$base_id_key}] AND [{$table}].[langcode] = [{$base_table}].[{$this->langcodeKey}] AND [{$table}].[deleted] = 0");
    }
    else {
      $query->leftJoin($table, $table, "[{$table}].[{$id_key}] = [{$base_table}].[{$base_id_key}] AND [{$table}].[deleted] = 0");
    }
    $query->fields($table, $this->tableMapping
      ->getColumnNames($field_name));
  }
  $results = $query->execute();
  $is_not_null = fn($value) => !is_null($value);
  foreach ($results as $row) {
    $row = (array) $row;
    $value_key = $row[$base_id_key];
    $bundle = $this->bundleKey ? $values[$value_key][$this->bundleKey][LanguageInterface::LANGCODE_DEFAULT] : $this->entityTypeId;
    // Field values in default language are stored with
    // LanguageInterface::LANGCODE_DEFAULT as key.
    $langcode = $this->langcodeKey && empty($row[$this->defaultLangcodeKey]) ? $row[$base_langcode_alias] : LanguageInterface::LANGCODE_DEFAULT;
    $translations[$value_key][$langcode] = TRUE;
    foreach ($shared_fields as $field_name) {
      $definition_columns = $field_definition_columns[$field_name];
      $columns = $field_columns[$field_name];
      // Do not key single-column fields by property name.
      if (count($columns) == 1) {
        $column_name = reset($columns);
        // Special case for the langcode key which is aliased in the query
        // results.
        if ($column_name === $this->langcodeKey) {
          $column_name = $base_langcode_alias;
        }
        $column_attributes = $definition_columns[key($columns)];
        $values[$value_key][$field_name][$langcode] = !empty($column_attributes['serialize']) ? $this->handleNullableFieldUnserialize($row[$column_name]) : $row[$column_name];
      }
      else {
        foreach ($columns as $property_name => $column_name) {
          $column_attributes = $definition_columns[$property_name];
          $values[$value_key][$field_name][$langcode][$property_name] = !empty($column_attributes['serialize']) ? $this->handleNullableFieldUnserialize($row[$column_name]) : $row[$column_name];
        }
      }
    }
    $langcode = $this->langcodeKey && empty($row[$this->defaultLangcodeKey]) && isset($default_langcodes[$value_key]) && $row[$base_langcode_alias] != $default_langcodes[$value_key] ? $row[$base_langcode_alias] : LanguageInterface::LANGCODE_DEFAULT;
    foreach ($single_cardinality_fields as $field_name => $storage_definition) {
      $field_values = array_intersect_key($row, array_flip($field_columns[$field_name]));
      // If all the field values are null, then there was no result for this
      // field.
      // @todo use array_any() once Drupal core requires PHP 8.4.
      if (empty(array_filter($field_values, $is_not_null))) {
        continue;
      }
      if (!isset($values[$value_key][$field_name][$langcode])) {
        $values[$value_key][$field_name][$langcode] = [];
      }
      // Ensure that records for non-translatable fields having invalid
      // languages are skipped.
      if ($langcode == LanguageInterface::LANGCODE_DEFAULT || $definitions[$bundle][$field_name]->isTranslatable()) {
        if (empty($values[$value_key][$field_name][$langcode])) {
          $item = [];
          // For each column declared by the field, populate the item from
          // the prefixed database column.
          foreach ($field_definition_columns[$field_name] as $column => $attributes) {
            $column_name = $table_mapping->getFieldColumnName($storage_definition, $column);
            // Unserialize the value if specified in the column schema.
            $item[$column] = !empty($attributes['serialize']) ? $this->handleNullableFieldUnserialize($row[$column_name]) : $row[$column_name];
          }
          // Add the item to the field values for the entity.
          $values[$value_key][$field_name][$langcode][] = $item;
        }
      }
    }
  }
  if ($multiple_cardinality_fields) {
    $query = clone $base_query;
    $delta_keys = [];
    foreach ($multiple_cardinality_fields as $field_name => $storage_definition) {
      $table = !$load_from_revision ? $table_mapping->getDedicatedDataTableName($storage_definition) : $table_mapping->getDedicatedRevisionTableName($storage_definition);
      // If the entity is translatable, add the langcode to the join and
      // a condition on valid langcodes.
      if ($this->langcodeKey) {
        $query->leftJoin($table, $table, "[{$table}].[{$id_key}] = [{$base_table}].[{$base_id_key}] AND [{$table}].[langcode] = [{$base_table}].[{$this->langcodeKey}] AND [{$table}].[deleted] = 0");
      }
      else {
        $query->leftJoin($table, $table, "[{$table}].[{$id_key}] = [{$base_table}].[{$base_id_key}] AND [{$table}].[deleted] = 0");
      }
      $query->fields($table, $field_columns[$field_name]);
      $delta_keys[$field_name] = $query->addField($table, 'delta', $field_name . '_delta');
    }
    $results = $query->execute();
    foreach ($results as $row) {
      $row = (array) $row;
      $value_key = $row[$base_id_key];
      // Field values in default language are stored with
      // LanguageInterface::LANGCODE_DEFAULT as key.
      $langcode = LanguageInterface::LANGCODE_DEFAULT;
      if ($this->langcodeKey && isset($default_langcodes[$value_key]) && $row[$base_langcode_alias] != $default_langcodes[$value_key]) {
        $langcode = $row[$base_langcode_alias];
      }
      foreach ($multiple_cardinality_fields as $field_name => $storage_definition) {
        $delta_key = $delta_keys[$field_name];
        $bundle = $this->bundleKey ? $values[$value_key][$this->bundleKey][LanguageInterface::LANGCODE_DEFAULT] : $this->entityTypeId;
        // If the delta is null then there are no values at this delta for
        // this field.
        if (!isset($row[$delta_key])) {
          continue;
        }
        if (!isset($values[$value_key][$field_name][$langcode])) {
          $values[$value_key][$field_name][$langcode] = [];
        }
        // Ensure that records for non-translatable fields having invalid
        // languages are skipped.
        if ($langcode == LanguageInterface::LANGCODE_DEFAULT || $definitions[$bundle][$field_name]->isTranslatable()) {
          if ($storage_definition->getCardinality() === FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED || $row[$delta_key] < $storage_definition->getCardinality()) {
            $item = [];
            // For each column declared by the field, populate the item from
            // the prefixed database column.
            foreach ($field_definition_columns[$field_name] as $column => $attributes) {
              $column_name = $table_mapping->getFieldColumnName($storage_definition, $column);
              // Unserialize the value if specified in the column schema.
              $item[$column] = !empty($attributes['serialize']) ? $this->handleNullableFieldUnserialize($row[$column_name]) : $row[$column_name];
            }
            // Add the item to the field values for the entity.
            $values[$value_key][$field_name][$langcode][(int) $row[$delta_key]] = $item;
          }
        }
      }
    }
    // Ensure that all of the deltas from all of the multiple cardinality
    // fields are returned in the correct order.
    foreach ($values as &$fields) {
      foreach ($fields as $field_name => &$field_data) {
        if (isset($multiple_cardinality_fields[$field_name])) {
          foreach ($field_data as &$language_data) {
            ksort($language_data);
          }
        }
      }
    }
  }
}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.