/** * Prepares an entity that defines a field definition. * * @param bool $custom_invoke_all * (optional) Whether the test will set up its own * ModuleHandlerInterface::invokeAll() implementation. Defaults to FALSE. * @param string $field_definition_id * (optional) The ID to use for the field definition. Defaults to 'id'. * @param array $entity_keys * (optional) An array of entity keys for the mocked entity type. Defaults * to an empty array. * * @return \Drupal\Core\Field\BaseFieldDefinition|\Prophecy\Prophecy\ProphecyInterface * A field definition object. */ protected function setUpEntityWithFieldDefinition($custom_invoke_all = FALSE, $field_definition_id = 'id', $entity_keys = array()) { $field_type_manager = $this->prophesize(FieldTypePluginManagerInterface::class); $field_type_manager->getDefaultStorageSettings('boolean')->willReturn([]); $field_type_manager->getDefaultFieldSettings('boolean')->willReturn([]); $this->container->get('plugin.manager.field.field_type')->willReturn($field_type_manager->reveal()); $string_translation = $this->prophesize(TranslationInterface::class); $this->container->get('string_translation')->willReturn($string_translation->reveal()); $entity_class = EntityManagerTestEntity::class; $field_definition = $this->prophesize()->willImplement(FieldDefinitionInterface::class)->willImplement(FieldStorageDefinitionInterface::class); $entity_class::$baseFieldDefinitions = array($field_definition_id => $field_definition->reveal()); $entity_class::$bundleFieldDefinitions = array(); if (!$custom_invoke_all) { $this->moduleHandler->getImplementations(Argument::cetera())->willReturn([]); } // Mock the base field definition override. $override_entity_type = $this->prophesize(EntityTypeInterface::class); $this->entityType = $this->prophesize(EntityTypeInterface::class); $this->setUpEntityManager(array('test_entity_type' => $this->entityType, 'base_field_override' => $override_entity_type)); $override_entity_type->getClass()->willReturn($entity_class); $override_entity_type->getHandlerClass('storage')->willReturn(TestConfigEntityStorage::class); $this->entityType->getClass()->willReturn($entity_class); $this->entityType->getKeys()->willReturn($entity_keys + ['default_langcode' => 'default_langcode']); $this->entityType->isSubclassOf(FieldableEntityInterface::class)->willReturn(TRUE); $this->entityType->isTranslatable()->willReturn(FALSE); $this->entityType->getProvider()->willReturn('the_provider'); $this->entityType->id()->willReturn('the_entity_id'); return $field_definition->reveal(); }
/** * Detects whether there is a change in the shared table structure. * * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type * The new entity type. * @param \Drupal\Core\Entity\EntityTypeInterface $original * The origin entity type. * * @return bool * Returns TRUE if either the revisionable or translatable flag changes or * a table has been renamed. */ protected function hasSharedTableStructureChange(EntityTypeInterface $entity_type, EntityTypeInterface $original) { return $entity_type->isRevisionable() != $original->isRevisionable() || $entity_type->isTranslatable() != $original->isTranslatable() || $this->hasSharedTableNameChanges($entity_type, $original); }
/** * {@inheritdoc} */ public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) { $changes = []; // We implement a specific logic for table updates, which is bound to the // default sql content entity storage. if (!$this->entityManager->getStorage($entity_type->id()) instanceof SqlContentEntityStorage) { return; } if ($entity_type->getBaseTable() != $original->getBaseTable()) { $changes[] = static::BASE_TABLE_RENAME; } $revision_add = $entity_type->isRevisionable() && !$original->isRevisionable(); $revision_remove = !$entity_type->isRevisionable() && $original->isRevisionable(); $translation_add = $entity_type->isTranslatable() && !$original->isTranslatable(); $translation_remove = !$entity_type->isTranslatable() && $original->isTranslatable(); if ($revision_add) { $changes[] = static::REVISION_TABLE_ADDITION; } elseif ($revision_remove) { $changes[] = static::REVISION_TABLE_REMOVAL; } elseif ($entity_type->isRevisionable() && $entity_type->getRevisionTable() != $original->getRevisionTable()) { $changes[] = static::REVISION_TABLE_RENAME; } if ($translation_add) { $changes[] = static::DATA_TABLE_ADDITION; } elseif ($translation_remove) { $changes[] = static::DATA_TABLE_REMOVAL; } elseif ($entity_type->isTranslatable() && $entity_type->getDataTable() != $original->getDataTable()) { $changes[] = static::DATA_TABLE_RENAME; } if ($entity_type->isRevisionable() && $entity_type->isTranslatable()) { if ($revision_add || $translation_add) { $changes[] = static::REVISION_DATA_TABLE_ADDITION; } elseif ($entity_type->getRevisionDataTable() != $original->getRevisionDataTable()) { $changes[] = static::REVISION_DATA_TABLE_RENAME; } } elseif ($original->isRevisionable() && $original->isTranslatable() && ($revision_remove || $translation_remove)) { $changes[] = static::REVISION_DATA_TABLE_REMOVAL; } /** @var \Drupal\views\Entity\View[] $all_views */ $all_views = $this->entityManager->getStorage('view')->loadMultiple(NULL); foreach ($changes as $change) { switch ($change) { case static::BASE_TABLE_RENAME: $this->baseTableRename($all_views, $entity_type->id(), $original->getBaseTable(), $entity_type->getBaseTable()); break; case static::DATA_TABLE_RENAME: $this->dataTableRename($all_views, $entity_type->id(), $original->getDataTable(), $entity_type->getDataTable()); break; case static::DATA_TABLE_ADDITION: $this->dataTableAddition($all_views, $entity_type, $entity_type->getDataTable(), $entity_type->getBaseTable()); break; case static::DATA_TABLE_REMOVAL: $this->dataTableRemoval($all_views, $entity_type->id(), $original->getDataTable(), $entity_type->getBaseTable()); break; case static::REVISION_TABLE_RENAME: $this->baseTableRename($all_views, $entity_type->id(), $original->getRevisionTable(), $entity_type->getRevisionTable()); break; case static::REVISION_TABLE_ADDITION: // If we add revision support we don't have to do anything. break; case static::REVISION_TABLE_REMOVAL: $this->revisionRemoval($all_views, $original); break; case static::REVISION_DATA_TABLE_RENAME: $this->dataTableRename($all_views, $entity_type->id(), $original->getRevisionDataTable(), $entity_type->getRevisionDataTable()); break; case static::REVISION_DATA_TABLE_ADDITION: $this->dataTableAddition($all_views, $entity_type, $entity_type->getRevisionDataTable(), $entity_type->getRevisionTable()); break; case static::REVISION_DATA_TABLE_REMOVAL: $this->dataTableRemoval($all_views, $entity_type->id(), $original->getRevisionDataTable(), $entity_type->getRevisionTable()); break; } } foreach ($all_views as $view) { // All changes done to the views here can be trusted and this might be // called during updates, when it is not safe to rely on configuration // containing valid schema. Trust the data and disable schema validation // and casting. $view->trustData()->save(); } }
/** * {@inheritdoc} */ public function getViewsData() { $data = []; $base_table = $this->entityType->getBaseTable() ?: $this->entityType->id(); $views_revision_base_table = NULL; $revisionable = $this->entityType->isRevisionable(); $base_field = $this->entityType->getKey('id'); $revision_table = ''; if ($revisionable) { $revision_table = $this->entityType->getRevisionTable() ?: $this->entityType->id() . '_revision'; } $translatable = $this->entityType->isTranslatable(); $data_table = ''; if ($translatable) { $data_table = $this->entityType->getDataTable() ?: $this->entityType->id() . '_field_data'; } // Some entity types do not have a revision data table defined, but still // have a revision table name set in // \Drupal\Core\Entity\Sql\SqlContentEntityStorage::initTableLayout() so we // apply the same kind of logic. $revision_data_table = ''; if ($revisionable && $translatable) { $revision_data_table = $this->entityType->getRevisionDataTable() ?: $this->entityType->id() . '_field_revision'; } $revision_field = $this->entityType->getKey('revision'); // Setup base information of the views data. $data[$base_table]['table']['group'] = $this->entityType->getLabel(); $data[$base_table]['table']['provider'] = $this->entityType->getProvider(); $views_base_table = $base_table; if ($data_table) { $views_base_table = $data_table; } $data[$views_base_table]['table']['base'] = ['field' => $base_field, 'title' => $this->entityType->getLabel(), 'cache_contexts' => $this->entityType->getListCacheContexts()]; $data[$base_table]['table']['entity revision'] = FALSE; if ($label_key = $this->entityType->getKey('label')) { if ($data_table) { $data[$views_base_table]['table']['base']['defaults'] = array('field' => $label_key, 'table' => $data_table); } else { $data[$views_base_table]['table']['base']['defaults'] = array('field' => $label_key); } } // Entity types must implement a list_builder in order to use Views' // entity operations field. if ($this->entityType->hasListBuilderClass()) { $data[$base_table]['operations'] = array('field' => array('title' => $this->t('Operations links'), 'help' => $this->t('Provides links to perform entity operations.'), 'id' => 'entity_operations')); } if ($this->entityType->hasViewBuilderClass()) { $data[$base_table]['rendered_entity'] = ['field' => ['title' => $this->t('Rendered entity'), 'help' => $this->t('Renders an entity in a view mode.'), 'id' => 'rendered_entity']]; } // Setup relations to the revisions/property data. if ($data_table) { $data[$base_table]['table']['join'][$data_table] = ['left_field' => $base_field, 'field' => $base_field, 'type' => 'INNER']; $data[$data_table]['table']['group'] = $this->entityType->getLabel(); $data[$data_table]['table']['provider'] = $this->entityType->getProvider(); $data[$data_table]['table']['entity revision'] = FALSE; } if ($revision_table) { $data[$revision_table]['table']['group'] = $this->t('@entity_type revision', ['@entity_type' => $this->entityType->getLabel()]); $data[$revision_table]['table']['provider'] = $this->entityType->getProvider(); $views_revision_base_table = $revision_table; if ($revision_data_table) { $views_revision_base_table = $revision_data_table; } $data[$views_revision_base_table]['table']['entity revision'] = TRUE; $data[$views_revision_base_table]['table']['base'] = array('field' => $revision_field, 'title' => $this->t('@entity_type revisions', array('@entity_type' => $this->entityType->getLabel()))); // Join the revision table to the base table. $data[$views_revision_base_table]['table']['join'][$views_base_table] = array('left_field' => $revision_field, 'field' => $revision_field, 'type' => 'INNER'); if ($revision_data_table) { $data[$revision_data_table]['table']['group'] = $this->t('@entity_type revision', ['@entity_type' => $this->entityType->getLabel()]); $data[$revision_data_table]['table']['entity revision'] = TRUE; $data[$revision_table]['table']['join'][$revision_data_table] = array('left_field' => $revision_field, 'field' => $revision_field, 'type' => 'INNER'); } } $this->addEntityLinks($data[$base_table]); // Load all typed data definitions of all fields. This should cover each of // the entity base, revision, data tables. $field_definitions = $this->entityManager->getBaseFieldDefinitions($this->entityType->id()); /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */ if ($table_mapping = $this->storage->getTableMapping($field_definitions)) { // Fetch all fields that can appear in both the base table and the data // table. $entity_keys = $this->entityType->getKeys(); $duplicate_fields = array_intersect_key($entity_keys, array_flip(['id', 'revision', 'bundle'])); // Iterate over each table we have so far and collect field data for each. // Based on whether the field is in the field_definitions provided by the // entity manager. // @todo We should better just rely on information coming from the entity // storage. // @todo https://www.drupal.org/node/2337511 foreach ($table_mapping->getTableNames() as $table) { foreach ($table_mapping->getFieldNames($table) as $field_name) { // To avoid confusing duplication in the user interface, for fields // that are on both base and data tables, only add them on the data // table (same for revision vs. revision data). if ($data_table && ($table === $base_table || $table === $revision_table) && in_array($field_name, $duplicate_fields)) { continue; } $this->mapFieldDefinition($table, $field_name, $field_definitions[$field_name], $table_mapping, $data[$table]); } } foreach ($field_definitions as $field_definition) { if ($table_mapping->requiresDedicatedTableStorage($field_definition->getFieldStorageDefinition())) { $table = $table_mapping->getDedicatedDataTableName($field_definition->getFieldStorageDefinition()); $data[$table]['table']['group'] = $this->entityType->getLabel(); $data[$table]['table']['provider'] = $this->entityType->getProvider(); $data[$table]['table']['join'][$views_base_table] = ['left_field' => $base_field, 'field' => 'entity_id', 'extra' => [['field' => 'deleted', 'value' => 0, 'numeric' => TRUE]]]; if ($revisionable) { $revision_table = $table_mapping->getDedicatedRevisionTableName($field_definition->getFieldStorageDefinition()); $data[$revision_table]['table']['group'] = $this->t('@entity_type revision', ['@entity_type' => $this->entityType->getLabel()]); $data[$revision_table]['table']['provider'] = $this->entityType->getProvider(); $data[$revision_table]['table']['join'][$views_revision_base_table] = ['left_field' => $revision_field, 'field' => 'entity_id', 'extra' => [['field' => 'deleted', 'value' => 0, 'numeric' => TRUE]]]; } } } } // Add the entity type key to each table generated. $entity_type_id = $this->entityType->id(); array_walk($data, function (&$table_data) use($entity_type_id) { $table_data['table']['entity type'] = $entity_type_id; }); return $data; }
/** * {@inheritdoc} */ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { $fields = []; if ($entity_type->hasKey('id')) { $fields[$entity_type->getKey('id')] = BaseFieldDefinition::create('integer')->setLabel(new TranslatableMarkup('ID'))->setReadOnly(TRUE)->setSetting('unsigned', TRUE); } if ($entity_type->hasKey('uuid')) { $fields[$entity_type->getKey('uuid')] = BaseFieldDefinition::create('uuid')->setLabel(new TranslatableMarkup('UUID'))->setReadOnly(TRUE); } if ($entity_type->hasKey('revision')) { $fields[$entity_type->getKey('revision')] = BaseFieldDefinition::create('integer')->setLabel(new TranslatableMarkup('Revision ID'))->setReadOnly(TRUE)->setSetting('unsigned', TRUE); } if ($entity_type->hasKey('langcode')) { $fields[$entity_type->getKey('langcode')] = BaseFieldDefinition::create('language')->setLabel(new TranslatableMarkup('Language'))->setDisplayOptions('view', ['type' => 'hidden'])->setDisplayOptions('form', ['type' => 'language_select', 'weight' => 2]); if ($entity_type->isRevisionable()) { $fields[$entity_type->getKey('langcode')]->setRevisionable(TRUE); } if ($entity_type->isTranslatable()) { $fields[$entity_type->getKey('langcode')]->setTranslatable(TRUE); } } if ($entity_type->hasKey('bundle')) { if ($bundle_entity_type_id = $entity_type->getBundleEntityType()) { $fields[$entity_type->getKey('bundle')] = BaseFieldDefinition::create('entity_reference')->setLabel($entity_type->getBundleLabel())->setSetting('target_type', $bundle_entity_type_id)->setRequired(TRUE)->setReadOnly(TRUE); } else { $fields[$entity_type->getKey('bundle')] = BaseFieldDefinition::create('string')->setLabel($entity_type->getBundleLabel())->setRequired(TRUE)->setReadOnly(TRUE); } } return $fields; }
/** * {@inheritdoc} */ public function requiresEntityStorageSchemaChanges(EntityTypeInterface $entity_type, EntityTypeInterface $original) { return $entity_type->getStorageClass() != $original->getStorageClass() || $entity_type->isRevisionable() != $original->isRevisionable() || $entity_type->isTranslatable() != $original->isTranslatable() || $this->getEntitySchemaData($entity_type, $this->getEntitySchema($entity_type, TRUE)) != $this->loadEntitySchemaData($original); }