Newer
Older
<?php
/**
* @file
catch
committed
* Contains \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema.
*/
catch
committed
namespace Drupal\Core\Entity\Sql;
Alex Pott
committed
use Drupal\Component\Utility\String;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityManagerInterface;
Alex Pott
committed
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
Angie Byron
committed
use Drupal\Core\Entity\Schema\DynamicallyFieldableEntityStorageSchemaInterface;
use Drupal\Core\Field\FieldException;
Alex Pott
committed
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\field\FieldStorageConfigInterface;
/**
* Defines a schema handler that supports revisionable, translatable entities.
*
* Entity types may extend this class and optimize the generated schema for all
* entity base tables by overriding getEntitySchema() for cross-field
* optimizations and getSharedTableFieldSchema() for optimizations applying to
* a single field.
*/
Angie Byron
committed
class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorageSchemaInterface {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The entity type this schema builder is responsible for.
*
* @var \Drupal\Core\Entity\ContentEntityTypeInterface
*/
protected $entityType;
/**
* The storage field definitions for this entity type.
*
Alex Pott
committed
* @var \Drupal\Core\Field\FieldStorageDefinitionInterface[]
*/
protected $fieldStorageDefinitions;
/**
* The original storage field definitions for this entity type. Used during
* field schema updates.
*
* @var \Drupal\Core\Field\FieldDefinitionInterface[]
*/
protected $originalDefinitions;
/**
* The storage object for the given entity type.
*
catch
committed
* @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage
*/
protected $storage;
/**
* A static cache of the generated schema array.
*
* @var array
*/
protected $schema;
/**
* The database connection to be used.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* The key-value collection for tracking installed storage schema.
*
* @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
*/
protected $installedStorageSchema;
/**
* Constructs a SqlContentEntityStorageSchema.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
* The entity type.
catch
committed
* @param \Drupal\Core\Entity\Sql\SqlContentEntityStorage $storage
* The storage of the entity type. This must be an SQL-based storage.
* @param \Drupal\Core\Database\Connection $database
* The database connection to be used.
*/
catch
committed
public function __construct(EntityManagerInterface $entity_manager, ContentEntityTypeInterface $entity_type, SqlContentEntityStorage $storage, Connection $database) {
$this->entityManager = $entity_manager;
$this->entityType = $entity_type;
$this->fieldStorageDefinitions = $entity_manager->getFieldStorageDefinitions($entity_type->id());
$this->storage = $storage;
$this->database = $database;
}
/**
* Returns the keyvalue collection for tracking the installed schema.
*
* @return \Drupal\Core\KeyValueStore\KeyValueStoreInterface
*
* @todo Inject this dependency in the constructor once this class can be
* instantiated as a regular entity handler:
* https://www.drupal.org/node/2332857.
*/
protected function installedStorageSchema() {
if (!isset($this->installedStorageSchema)) {
$this->installedStorageSchema = \Drupal::keyValue('entity.storage_schema.sql');
}
return $this->installedStorageSchema;
}
Alex Pott
committed
/**
* {@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() ||
// Detect changes in key or index definitions.
$this->getEntitySchemaData($entity_type, $this->getEntitySchema($entity_type, TRUE)) != $this->loadEntitySchemaData($original);
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
public function requiresFieldStorageSchemaChanges(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
$table_mapping = $this->storage->getTableMapping();
if (
Alex Pott
committed
$storage_definition->hasCustomStorage() != $original->hasCustomStorage() ||
$storage_definition->getSchema() != $original->getSchema() ||
$storage_definition->isRevisionable() != $original->isRevisionable() ||
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
$storage_definition->isTranslatable() != $original->isTranslatable() ||
$table_mapping->allowsSharedTableStorage($storage_definition) != $table_mapping->allowsSharedTableStorage($original) ||
$table_mapping->requiresDedicatedTableStorage($storage_definition) != $table_mapping->requiresDedicatedTableStorage($original)
) {
return TRUE;
}
if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
return $this->getDedicatedTableSchema($storage_definition) != $this->loadFieldSchemaData($original);
}
elseif ($table_mapping->allowsSharedTableStorage($storage_definition)) {
$field_name = $storage_definition->getName();
$schema = array();
foreach (array_diff($table_mapping->getTableNames(), $table_mapping->getDedicatedTableNames()) as $table_name) {
if (in_array($field_name, $table_mapping->getFieldNames($table_name))) {
$column_names = $table_mapping->getColumnNames($storage_definition->getName());
$schema[$table_name] = $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names);
}
}
return $schema != $this->loadFieldSchemaData($original);
}
else {
// The field has custom storage, so we don't know if a schema change is
// needed or not, but since per the initial checks earlier in this
// function, nothing about the definition changed that we manage, we
// return FALSE.
return FALSE;
}
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
public function requiresEntityDataMigration(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
// If the original storage has existing entities, or it is impossible to
// determine if that is the case, require entity data to be migrated.
Alex Pott
committed
$original_storage_class = $original->getStorageClass();
if (!class_exists($original_storage_class)) {
return TRUE;
Alex Pott
committed
}
// Use the original entity type since the storage has not been updated.
$original_storage = $this->entityManager->createHandlerInstance($original_storage_class, $original);
return $original_storage->hasData();
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
public function requiresFieldDataMigration(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
return !$this->storage->countFieldData($original, TRUE);
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
public function onEntityTypeCreate(EntityTypeInterface $entity_type) {
Alex Pott
committed
$this->checkEntityType($entity_type);
$schema_handler = $this->database->schema();
// Create entity tables.
$schema = $this->getEntitySchema($entity_type, TRUE);
foreach ($schema as $table_name => $table_schema) {
if (!$schema_handler->tableExists($table_name)) {
$schema_handler->createTable($table_name, $table_schema);
}
}
// Create dedicated field tables.
$field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type->id());
$table_mapping = $this->storage->getTableMapping($field_storage_definitions);
foreach ($field_storage_definitions as $field_storage_definition) {
if ($table_mapping->requiresDedicatedTableStorage($field_storage_definition)) {
$this->createDedicatedTableSchema($field_storage_definition);
}
elseif ($table_mapping->allowsSharedTableStorage($field_storage_definition)) {
// The shared tables are already fully created, but we need to save the
// per-field schema definitions for later use.
$this->createSharedTableSchema($field_storage_definition, TRUE);
}
}
// Save data about entity indexes and keys.
$this->saveEntitySchemaData($entity_type, $schema);
}
/**
* {@inheritdoc}
*/
public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
Alex Pott
committed
$this->checkEntityType($entity_type);
$this->checkEntityType($original);
// If no schema changes are needed, we don't need to do anything.
if (!$this->requiresEntityStorageSchemaChanges($entity_type, $original)) {
return;
}
// If a migration is required, we can't proceed.
if ($this->requiresEntityDataMigration($entity_type, $original)) {
throw new EntityStorageException(String::format('The SQL storage cannot change the schema for an existing entity type with data.'));
}
Alex Pott
committed
// If we have no data just recreate the entity schema from scratch.
if ($this->isTableEmpty($this->storage->getBaseTable())) {
Alex Pott
committed
if ($this->database->supportsTransactionalDDL()) {
// If the database supports transactional DDL, we can go ahead and rely
// on it. If not, we will have to rollback manually if something fails.
$transaction = $this->database->startTransaction();
}
try {
$this->onEntityTypeDelete($original);
$this->onEntityTypeCreate($entity_type);
}
catch (\Exception $e) {
if ($this->database->supportsTransactionalDDL()) {
$transaction->rollback();
}
else {
// Recreate original schema.
$this->onEntityTypeCreate($original);
}
throw $e;
}
}
else {
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
$schema_handler = $this->database->schema();
// Drop original indexes and unique keys.
foreach ($this->loadEntitySchemaData($entity_type) as $table_name => $schema) {
if (!empty($schema['indexes'])) {
foreach ($schema['indexes'] as $name => $specifier) {
$schema_handler->dropIndex($table_name, $name);
}
}
if (!empty($schema['unique keys'])) {
foreach ($schema['unique keys'] as $name => $specifier) {
$schema_handler->dropUniqueKey($table_name, $name);
}
}
}
// Create new indexes and unique keys.
$entity_schema = $this->getEntitySchema($entity_type, TRUE);
foreach ($this->getEntitySchemaData($entity_type, $entity_schema) as $table_name => $schema) {
if (!empty($schema['indexes'])) {
foreach ($schema['indexes'] as $name => $specifier) {
$schema_handler->addIndex($table_name, $name, $specifier);
}
}
if (!empty($schema['unique keys'])) {
foreach ($schema['unique keys'] as $name => $specifier) {
$schema_handler->addUniqueKey($table_name, $name, $specifier);
}
}
}
// Store the updated entity schema.
$this->saveEntitySchemaData($entity_type, $entity_schema);
}
/**
* {@inheritdoc}
*/
public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
Alex Pott
committed
$this->checkEntityType($entity_type);
$schema_handler = $this->database->schema();
$actual_definition = $this->entityManager->getDefinition($entity_type->id());
// @todo Instead of switching the wrapped entity type, we should be able to
// instantiate a new table mapping for each entity type definition. See
// https://www.drupal.org/node/2274017.
$this->storage->setEntityType($entity_type);
// Delete entity tables.
Alex Pott
committed
foreach ($this->getEntitySchemaTables() as $table_name) {
if ($schema_handler->tableExists($table_name)) {
$schema_handler->dropTable($table_name);
}
}
// Delete dedicated field tables.
$field_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type->id());
$this->originalDefinitions = $field_storage_definitions;
$table_mapping = $this->storage->getTableMapping($field_storage_definitions);
foreach ($field_storage_definitions as $field_storage_definition) {
if ($table_mapping->requiresDedicatedTableStorage($field_storage_definition)) {
$this->deleteDedicatedTableSchema($field_storage_definition);
}
}
$this->originalDefinitions = NULL;
$this->storage->setEntityType($actual_definition);
// Delete the entity schema.
$this->deleteEntitySchemaData($entity_type);
}
Alex Pott
committed
/**
* {@inheritdoc}
*/
public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition) {
$this->performFieldSchemaOperation('create', $storage_definition);
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
// Store original definitions so that switching between shared and dedicated
// field table layout works.
$this->originalDefinitions = $this->fieldStorageDefinitions;
$this->originalDefinitions[$original->getName()] = $original;
$this->performFieldSchemaOperation('update', $storage_definition, $original);
$this->originalDefinitions = NULL;
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $storage_definition) {
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
// Only configurable fields currently support purging, so prevent deletion
// of ones we can't purge if they have existing data.
// @todo Add purging to all fields: https://www.drupal.org/node/2282119.
if (!($storage_definition instanceof FieldStorageConfigInterface) && $this->storage->countFieldData($storage_definition, TRUE)) {
throw new FieldStorageDefinitionUpdateForbiddenException('Unable to delete a field with data that can\'t be purged.');
}
// Retrieve a table mapping which contains the deleted field still.
$table_mapping = $this->storage->getTableMapping(
$this->entityManager->getLastInstalledFieldStorageDefinitions($this->entityType->id())
);
if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
// Move the table to a unique name while the table contents are being
// deleted.
$table = $table_mapping->getDedicatedDataTableName($storage_definition);
$new_table = $table_mapping->getDedicatedDataTableName($storage_definition, TRUE);
$this->database->schema()->renameTable($table, $new_table);
if ($this->entityType->isRevisionable()) {
$revision_table = $table_mapping->getDedicatedRevisionTableName($storage_definition);
$revision_new_table = $table_mapping->getDedicatedRevisionTableName($storage_definition, TRUE);
$this->database->schema()->renameTable($revision_table, $revision_new_table);
}
}
// @todo Remove when finalizePurge() is invoked from the outside for all
// fields: https://www.drupal.org/node/2282119.
if (!($storage_definition instanceof FieldStorageConfigInterface)) {
$this->performFieldSchemaOperation('delete', $storage_definition);
}
}
/**
* {@inheritdoc}
*/
public function finalizePurge(FieldStorageDefinitionInterface $storage_definition) {
$this->performFieldSchemaOperation('delete', $storage_definition);
Alex Pott
committed
}
/**
* Checks that we are dealing with the correct entity type.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type to be checked.
*
* @return bool
* TRUE if the entity type matches the current one.
*
* @throws \Drupal\Core\Entity\EntityStorageException
*/
protected function checkEntityType(EntityTypeInterface $entity_type) {
if ($entity_type->id() != $this->entityType->id()) {
throw new EntityStorageException(String::format('Unsupported entity type @id', array('@id' => $entity_type->id())));
}
return TRUE;
}
/**
* Returns the entity schema for the specified entity type.
*
* Entity types may override this method in order to optimize the generated
* schema of the entity tables. However, only cross-field optimizations should
* be added here; e.g., an index spanning multiple fields. Optimizations that
* apply to a single field have to be added via
* SqlContentEntityStorageSchema::getSharedTableFieldSchema() instead.
*
* @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
* The entity type definition.
* @param bool $reset
* (optional) If set to TRUE static cache will be ignored and a new schema
* array generation will be performed. Defaults to FALSE.
*
* @return array
* A Schema API array describing the entity schema, excluding dedicated
* field tables.
*
* @throws \Drupal\Core\Field\FieldException
*/
protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
$this->checkEntityType($entity_type);
$entity_type_id = $entity_type->id();
if (!isset($this->schema[$entity_type_id]) || $reset) {
// Back up the storage definition and replace it with the passed one.
// @todo Instead of switching the wrapped entity type, we should be able
// to instantiate a new table mapping for each entity type definition.
// See https://www.drupal.org/node/2274017.
$actual_definition = $this->entityManager->getDefinition($entity_type_id);
$this->storage->setEntityType($entity_type);
// Prepare basic information about the entity type.
Alex Pott
committed
$tables = $this->getEntitySchemaTables();
// Initialize the table schema.
$schema[$tables['base_table']] = $this->initializeBaseTable($entity_type);
if (isset($tables['revision_table'])) {
$schema[$tables['revision_table']] = $this->initializeRevisionTable($entity_type);
}
if (isset($tables['data_table'])) {
$schema[$tables['data_table']] = $this->initializeDataTable($entity_type);
}
if (isset($tables['revision_data_table'])) {
$schema[$tables['revision_data_table']] = $this->initializeRevisionDataTable($entity_type);
}
// We need to act only on shared entity schema tables.
$table_mapping = $this->storage->getTableMapping();
$table_names = array_diff($table_mapping->getTableNames(), $table_mapping->getDedicatedTableNames());
$storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
foreach ($table_names as $table_name) {
if (!isset($schema[$table_name])) {
$schema[$table_name] = array();
}
foreach ($table_mapping->getFieldNames($table_name) as $field_name) {
if (!isset($storage_definitions[$field_name])) {
throw new FieldException(String::format('Field storage definition for "@field_name" could not be found.', array('@field_name' => $field_name)));
}
// Add the schema for base field definitions.
elseif ($table_mapping->allowsSharedTableStorage($storage_definitions[$field_name])) {
$column_names = $table_mapping->getColumnNames($field_name);
$storage_definition = $storage_definitions[$field_name];
$schema[$table_name] = array_merge_recursive($schema[$table_name], $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names));
}
}
// Add the schema for extra fields.
foreach ($table_mapping->getExtraColumns($table_name) as $column_name) {
if ($column_name == 'default_langcode') {
$this->addDefaultLangcodeSchema($schema[$table_name]);
}
}
}
// Process tables after having gathered field information.
$this->processBaseTable($entity_type, $schema[$tables['base_table']]);
if (isset($tables['revision_table'])) {
$this->processRevisionTable($entity_type, $schema[$tables['revision_table']]);
}
if (isset($tables['data_table'])) {
$this->processDataTable($entity_type, $schema[$tables['data_table']]);
}
if (isset($tables['revision_data_table'])) {
$this->processRevisionDataTable($entity_type, $schema[$tables['revision_data_table']]);
}
$this->schema[$entity_type_id] = $schema;
// Restore the actual definition.
$this->storage->setEntityType($actual_definition);
}
return $this->schema[$entity_type_id];
}
/**
* Gets a list of entity type tables.
*
* @return array
* A list of entity type tables, keyed by table key.
*/
Alex Pott
committed
protected function getEntitySchemaTables() {
return array_filter(array(
'base_table' => $this->storage->getBaseTable(),
'revision_table' => $this->storage->getRevisionTable(),
'data_table' => $this->storage->getDataTable(),
'revision_data_table' => $this->storage->getRevisionDataTable(),
));
}
/**
* Returns entity schema definitions for index and key definitions.
*
* @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
* The entity type definition.
* @param array $schema
* The entity schema array.
*
* @return array
* A stripped down version of the $schema Schema API array containing, for
* each table, only the key and index definitions not derived from field
* storage definitions.
*/
protected function getEntitySchemaData(ContentEntityTypeInterface $entity_type, array $schema) {
$schema_data = array();
$entity_type_id = $entity_type->id();
$keys = array('indexes', 'unique keys');
$unused_keys = array_flip(array('description', 'fields', 'foreign keys'));
foreach ($schema as $table_name => $table_schema) {
$table_schema = array_diff_key($table_schema, $unused_keys);
foreach ($keys as $key) {
// Exclude data generated from field storage definitions, we will check
// that separately.
if (!empty($table_schema[$key])) {
$data_keys = array_keys($table_schema[$key]);
$entity_keys = array_filter($data_keys, function ($key) use ($entity_type_id) {
return strpos($key, $entity_type_id . '_field__') !== 0;
});
$table_schema[$key] = array_intersect_key($table_schema[$key], array_flip($entity_keys));
}
}
$schema_data[$table_name] = array_filter($table_schema);
}
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
}
/**
* Returns an index schema array for a given field.
*
* @param string $field_name
* The name of the field.
* @param array $field_schema
* The schema of the field.
* @param string[] $column_mapping
* A mapping of field column names to database column names.
*
* @return array
* The schema definition for the indexes.
*/
protected function getFieldIndexes($field_name, array $field_schema, array $column_mapping) {
return $this->getFieldSchemaData($field_name, $field_schema, $column_mapping, 'indexes');
}
/**
* Returns a unique key schema array for a given field.
*
* @param string $field_name
* The name of the field.
* @param array $field_schema
* The schema of the field.
* @param string[] $column_mapping
* A mapping of field column names to database column names.
*
* @return array
* The schema definition for the unique keys.
*/
protected function getFieldUniqueKeys($field_name, array $field_schema, array $column_mapping) {
return $this->getFieldSchemaData($field_name, $field_schema, $column_mapping, 'unique keys');
}
/**
* Returns field schema data for the given key.
*
* @param string $field_name
* The name of the field.
* @param array $field_schema
* The schema of the field.
* @param string[] $column_mapping
* A mapping of field column names to database column names.
* @param string $schema_key
* The type of schema data. Either 'indexes' or 'unique keys'.
*
* @return array
* The schema definition for the specified key.
*/
protected function getFieldSchemaData($field_name, array $field_schema, array $column_mapping, $schema_key) {
$data = array();
foreach ($field_schema[$schema_key] as $key => $columns) {
// To avoid clashes with entity-level indexes or unique keys we use
// "{$entity_type_id}_field__" as a prefix instead of just
// "{$entity_type_id}__". We additionally namespace the specifier by the
// field name to avoid clashes when multiple fields of the same type are
// added to an entity type.
$entity_type_id = $this->entityType->id();
$real_key = $this->getFieldSchemaIdentifierName($entity_type_id, $field_name, $key);
foreach ($columns as $column) {
// Allow for indexes and unique keys to specified as an array of column
// name and length.
if (is_array($column)) {
list($column_name, $length) = $column;
$data[$real_key][] = array($column_mapping[$column_name], $length);
}
else {
$data[$real_key][] = $column_mapping[$column];
}
}
}
return $data;
}
/**
* Generates a safe schema identifier (name of an index, column name etc.).
*
* @param string $entity_type_id
* The ID of the entity type.
* @param string $field_name
* The name of the field.
* @param string|null $key
* (optional) A further key to append to the name.
*
* @return string
* The field identifier name.
*/
protected function getFieldSchemaIdentifierName($entity_type_id, $field_name, $key = NULL) {
$real_key = isset($key) ? "{$entity_type_id}_field__{$field_name}__{$key}" : "{$entity_type_id}_field__{$field_name}";
// Limit the string to 48 characters, keeping a 16 characters margin for db
// prefixes.
if (strlen($real_key) > 48) {
// Use a shorter separator, a truncated entity_type, and a hash of the
// field name.
// Truncate to the same length for the current and revision tables.
$entity_type = substr($entity_type_id, 0, 36);
$field_hash = substr(hash('sha256', $real_key), 0, 10);
$real_key = $entity_type . '__' . $field_hash;
}
return $real_key;
}
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
/**
* Returns field foreign keys.
*
* @param string $field_name
* The name of the field.
* @param array $field_schema
* The schema of the field.
* @param string[] $column_mapping
* A mapping of field column names to database column names.
*
* @return array
* The schema definition for the foreign keys.
*/
protected function getFieldForeignKeys($field_name, array $field_schema, array $column_mapping) {
$foreign_keys = array();
foreach ($field_schema['foreign keys'] as $specifier => $specification) {
// To avoid clashes with entity-level foreign keys we use
// "{$entity_type_id}_field__" as a prefix instead of just
// "{$entity_type_id}__". We additionally namespace the specifier by the
// field name to avoid clashes when multiple fields of the same type are
// added to an entity type.
$entity_type_id = $this->entityType->id();
$real_specifier = "{$entity_type_id}_field__{$field_name}__{$specifier}";
$foreign_keys[$real_specifier]['table'] = $specification['table'];
foreach ($specification['columns'] as $column => $referenced) {
$foreign_keys[$real_specifier]['columns'][$column_mapping[$column]] = $referenced;
}
}
return $foreign_keys;
}
/**
* Returns the schema for the 'default_langcode' metadata field.
*
* @param array $schema
* The table schema to add the field schema to, passed by reference.
*
* @return array
* A schema field array for the 'default_langcode' metadata field.
*/
protected function addDefaultLangcodeSchema(&$schema) {
$schema['fields']['default_langcode'] = array(
'description' => 'Boolean indicating whether field values are in the default entity language.',
'type' => 'int',
'size' => 'tiny',
'not null' => TRUE,
'default' => 1,
);
}
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
/**
* Loads stored schema data for the given entity type definition.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
*
* @return array
* The entity schema data array.
*/
protected function loadEntitySchemaData(EntityTypeInterface $entity_type) {
return $this->installedStorageSchema()->get($entity_type->id() . '.entity_schema_data', array());
}
/**
* Stores schema data for the given entity type definition.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param array $schema
* The entity schema data array.
*/
protected function saveEntitySchemaData(EntityTypeInterface $entity_type, $schema) {
$data = $this->getEntitySchemaData($entity_type, $schema);
$this->installedStorageSchema()->set($entity_type->id() . '.entity_schema_data', $data);
}
/**
* Deletes schema data for the given entity type definition.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
*/
protected function deleteEntitySchemaData(EntityTypeInterface $entity_type) {
$this->installedStorageSchema()->delete($entity_type->id() . '.entity_schema_data');
}
/**
* Loads stored schema data for the given field storage definition.
*
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
* The field storage definition.
*
* @return array
* The field schema data array.
*/
protected function loadFieldSchemaData(FieldStorageDefinitionInterface $storage_definition) {
return $this->installedStorageSchema()->get($storage_definition->getTargetEntityTypeId() . '.field_schema_data.' . $storage_definition->getName(), array());
}
/**
* Stores schema data for the given field storage definition.
*
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
* The field storage definition.
* @param array $schema
* The field schema data array.
*/
protected function saveFieldSchemaData(FieldStorageDefinitionInterface $storage_definition, $schema) {
$this->installedStorageSchema()->set($storage_definition->getTargetEntityTypeId() . '.field_schema_data.' . $storage_definition->getName(), $schema);
}
/**
* Deletes schema data for the given field storage definition.
*
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
* The field storage definition.
*/
protected function deleteFieldSchemaData(FieldStorageDefinitionInterface $storage_definition) {
$this->installedStorageSchema()->delete($storage_definition->getTargetEntityTypeId() . '.field_schema_data.' . $storage_definition->getName());
}
/**
* Initializes common information for a base table.
*
* @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
* The entity type.
*
* @return array
* A partial schema array for the base table.
*/
protected function initializeBaseTable(ContentEntityTypeInterface $entity_type) {
$entity_type_id = $entity_type->id();
$schema = array(
'description' => "The base table for $entity_type_id entities.",
'primary key' => array($entity_type->getKey('id')),
'indexes' => array(),
'foreign keys' => array(),
);
if ($entity_type->hasKey('revision')) {
$revision_key = $entity_type->getKey('revision');
$key_name = $this->getEntityIndexName($entity_type, $revision_key);
$schema['unique keys'][$key_name] = array($revision_key);
$schema['foreign keys'][$entity_type_id . '__revision'] = array(
'table' => $this->storage->getRevisionTable(),
'columns' => array($revision_key => $revision_key),
);
}
Alex Pott
committed
$this->addTableDefaults($schema);
return $schema;
}
/**
* Initializes common information for a revision table.
*
* @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
* The entity type.
*
* @return array
* A partial schema array for the revision table.
*/
protected function initializeRevisionTable(ContentEntityTypeInterface $entity_type) {
$entity_type_id = $entity_type->id();
$id_key = $entity_type->getKey('id');
$revision_key = $entity_type->getKey('revision');
$schema = array(
'description' => "The revision table for $entity_type_id entities.",
'primary key' => array($revision_key),
'indexes' => array(),
'foreign keys' => array(
$entity_type_id . '__revisioned' => array(
'table' => $this->storage->getBaseTable(),
'columns' => array($id_key => $id_key),
),
),
);
$schema['indexes'][$this->getEntityIndexName($entity_type, $id_key)] = array($id_key);
Alex Pott
committed
$this->addTableDefaults($schema);
return $schema;
}
/**
* Initializes common information for a data table.
*
* @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
* The entity type.
*
* @return array
* A partial schema array for the data table.
*/
protected function initializeDataTable(ContentEntityTypeInterface $entity_type) {
$entity_type_id = $entity_type->id();
$id_key = $entity_type->getKey('id');
$schema = array(
'description' => "The data table for $entity_type_id entities.",
// @todo Use the language entity key when https://drupal.org/node/2143729
// is in.
'primary key' => array($id_key, 'langcode'),
'indexes' => array(),
'foreign keys' => array(
$entity_type_id => array(
'table' => $this->storage->getBaseTable(),
'columns' => array($id_key => $id_key),
),
),
);
if ($entity_type->hasKey('revision')) {
$key = $entity_type->getKey('revision');
$schema['indexes'][$this->getEntityIndexName($entity_type, $key)] = array($key);
}
Alex Pott
committed
$this->addTableDefaults($schema);
return $schema;
}
/**
* Initializes common information for a revision data table.
*
* @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
* The entity type.
*
* @return array
* A partial schema array for the revision data table.
*/
protected function initializeRevisionDataTable(ContentEntityTypeInterface $entity_type) {
$entity_type_id = $entity_type->id();
$id_key = $entity_type->getKey('id');
$revision_key = $entity_type->getKey('revision');
$schema = array(
'description' => "The revision data table for $entity_type_id entities.",
// @todo Use the language entity key when https://drupal.org/node/2143729
// is in.
'primary key' => array($revision_key, 'langcode'),
'indexes' => array(),
'foreign keys' => array(
$entity_type_id => array(
'table' => $this->storage->getBaseTable(),
'columns' => array($id_key => $id_key),
),
$entity_type_id . '__revision' => array(
'table' => $this->storage->getRevisionTable(),
'columns' => array($revision_key => $revision_key),
)
),
);
Alex Pott
committed
$this->addTableDefaults($schema);
return $schema;
}
Alex Pott
committed
/**
* Adds defaults to a table schema definition.
*
* @param $schema
* The schema definition array for a single table, passed by reference.
*/
protected function addTableDefaults(&$schema) {
$schema += array(
'fields' => array(),
'unique keys' => array(),
'indexes' => array(),
'foreign keys' => array(),
);
}
/**
* Processes the gathered schema for a base table.
*
* @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
* The entity type.
* @param array $schema
* The table schema, passed by reference.
*
* @return array
* A partial schema array for the base table.
*/
protected function processBaseTable(ContentEntityTypeInterface $entity_type, array &$schema) {
$this->processIdentifierSchema($schema, $entity_type->getKey('id'));
}
/**
* Processes the gathered schema for a base table.
*
* @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
* The entity type.
* @param array $schema
* The table schema, passed by reference.
*
* @return array
* A partial schema array for the base table.
*/
protected function processRevisionTable(ContentEntityTypeInterface $entity_type, array &$schema) {
$this->processIdentifierSchema($schema, $entity_type->getKey('revision'));
}
/**
* Processes the gathered schema for a base table.
*
* @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
* The entity type.
* @param array $schema
* The table schema, passed by reference.
*
* @return array
* A partial schema array for the base table.
*/
protected function processDataTable(ContentEntityTypeInterface $entity_type, array &$schema) {