Updated Drupal to 8.6. This goes with the following updates because it's possible...
[yaffs-website] / web / core / lib / Drupal / Core / Entity / Sql / DefaultTableMapping.php
1 <?php
2
3 namespace Drupal\Core\Entity\Sql;
4
5 use Drupal\Core\Entity\ContentEntityTypeInterface;
6 use Drupal\Core\Field\FieldStorageDefinitionInterface;
7
8 /**
9  * Defines a default table mapping class.
10  */
11 class DefaultTableMapping implements TableMappingInterface {
12
13   /**
14    * The entity type definition.
15    *
16    * @var \Drupal\Core\Entity\ContentEntityTypeInterface
17    */
18   protected $entityType;
19
20   /**
21    * The field storage definitions of this mapping.
22    *
23    * @var \Drupal\Core\Field\FieldStorageDefinitionInterface[]
24    */
25   protected $fieldStorageDefinitions = [];
26
27   /**
28    * The base table of the entity.
29    *
30    * @var string
31    */
32   protected $baseTable;
33
34   /**
35    * The table that stores revisions, if the entity supports revisions.
36    *
37    * @var string
38    */
39   protected $revisionTable;
40
41   /**
42    * The table that stores field data, if the entity has multilingual support.
43    *
44    * @var string
45    */
46   protected $dataTable;
47
48   /**
49    * The table that stores revision field data if the entity supports revisions
50    * and has multilingual support.
51    *
52    * @var string
53    */
54   protected $revisionDataTable;
55
56   /**
57    * A list of field names per table.
58    *
59    * This corresponds to the return value of
60    * TableMappingInterface::getFieldNames() except that this variable is
61    * additionally keyed by table name.
62    *
63    * @var array[]
64    */
65   protected $fieldNames = [];
66
67   /**
68    * A list of database columns which store denormalized data per table.
69    *
70    * This corresponds to the return value of
71    * TableMappingInterface::getExtraColumns() except that this variable is
72    * additionally keyed by table name.
73    *
74    * @var array[]
75    */
76   protected $extraColumns = [];
77
78   /**
79    * A mapping of column names per field name.
80    *
81    * This corresponds to the return value of
82    * TableMappingInterface::getColumnNames() except that this variable is
83    * additionally keyed by field name.
84    *
85    * This data is derived from static::$storageDefinitions, but is stored
86    * separately to avoid repeated processing.
87    *
88    * @var array[]
89    */
90   protected $columnMapping = [];
91
92   /**
93    * A list of all database columns per table.
94    *
95    * This corresponds to the return value of
96    * TableMappingInterface::getAllColumns() except that this variable is
97    * additionally keyed by table name.
98    *
99    * This data is derived from static::$storageDefinitions, static::$fieldNames,
100    * and static::$extraColumns, but is stored separately to avoid repeated
101    * processing.
102    *
103    * @var array[]
104    */
105   protected $allColumns = [];
106
107   /**
108    * Constructs a DefaultTableMapping.
109    *
110    * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
111    *   The entity type definition.
112    * @param \Drupal\Core\Field\FieldStorageDefinitionInterface[] $storage_definitions
113    *   A list of field storage definitions that should be available for the
114    *   field columns of this table mapping.
115    */
116   public function __construct(ContentEntityTypeInterface $entity_type, array $storage_definitions) {
117     $this->entityType = $entity_type;
118     $this->fieldStorageDefinitions = $storage_definitions;
119
120     // @todo Remove table names from the entity type definition in
121     //   https://www.drupal.org/node/2232465.
122     $this->baseTable = $entity_type->getBaseTable() ?: $entity_type->id();
123     if ($entity_type->isRevisionable()) {
124       $this->revisionTable = $entity_type->getRevisionTable() ?: $entity_type->id() . '_revision';
125     }
126     if ($entity_type->isTranslatable()) {
127       $this->dataTable = $entity_type->getDataTable() ?: $entity_type->id() . '_field_data';
128     }
129     if ($entity_type->isRevisionable() && $entity_type->isTranslatable()) {
130       $this->revisionDataTable = $entity_type->getRevisionDataTable() ?: $entity_type->id() . '_field_revision';
131     }
132   }
133
134   /**
135    * Initializes the table mapping.
136    *
137    * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
138    *   The entity type definition.
139    * @param \Drupal\Core\Field\FieldStorageDefinitionInterface[] $storage_definitions
140    *   A list of field storage definitions that should be available for the
141    *   field columns of this table mapping.
142    *
143    * @return static
144    *
145    * @internal
146    */
147   public static function create(ContentEntityTypeInterface $entity_type, array $storage_definitions) {
148     $table_mapping = new static($entity_type, $storage_definitions);
149
150     $revisionable = $entity_type->isRevisionable();
151     $translatable = $entity_type->isTranslatable();
152
153     $id_key = $entity_type->getKey('id');
154     $revision_key = $entity_type->getKey('revision');
155     $bundle_key = $entity_type->getKey('bundle');
156     $uuid_key = $entity_type->getKey('uuid');
157     $langcode_key = $entity_type->getKey('langcode');
158
159     $shared_table_definitions = array_filter($storage_definitions, function (FieldStorageDefinitionInterface $definition) use ($table_mapping) {
160       return $table_mapping->allowsSharedTableStorage($definition);
161     });
162
163     $key_fields = array_values(array_filter([$id_key, $revision_key, $bundle_key, $uuid_key, $langcode_key]));
164     $all_fields = array_keys($shared_table_definitions);
165     $revisionable_fields = array_keys(array_filter($shared_table_definitions, function (FieldStorageDefinitionInterface $definition) {
166       return $definition->isRevisionable();
167     }));
168     // Make sure the key fields come first in the list of fields.
169     $all_fields = array_merge($key_fields, array_diff($all_fields, $key_fields));
170
171     $revision_metadata_fields = $revisionable ? array_values($entity_type->getRevisionMetadataKeys()) : [];
172
173     if (!$revisionable && !$translatable) {
174       // The base layout stores all the base field values in the base table.
175       $table_mapping->setFieldNames($table_mapping->baseTable, $all_fields);
176     }
177     elseif ($revisionable && !$translatable) {
178       // The revisionable layout stores all the base field values in the base
179       // table, except for revision metadata fields. Revisionable fields
180       // denormalized in the base table but also stored in the revision table
181       // together with the entity ID and the revision ID as identifiers.
182       $table_mapping->setFieldNames($table_mapping->baseTable, array_diff($all_fields, $revision_metadata_fields));
183       $revision_key_fields = [$id_key, $revision_key];
184       $table_mapping->setFieldNames($table_mapping->revisionTable, array_merge($revision_key_fields, $revisionable_fields));
185     }
186     elseif (!$revisionable && $translatable) {
187       // Multilingual layouts store key field values in the base table. The
188       // other base field values are stored in the data table, no matter
189       // whether they are translatable or not. The data table holds also a
190       // denormalized copy of the bundle field value to allow for more
191       // performant queries. This means that only the UUID is not stored on
192       // the data table.
193       $table_mapping
194         ->setFieldNames($table_mapping->baseTable, $key_fields)
195         ->setFieldNames($table_mapping->dataTable, array_values(array_diff($all_fields, [$uuid_key])));
196     }
197     elseif ($revisionable && $translatable) {
198       // The revisionable multilingual layout stores key field values in the
199       // base table and the revision table holds the entity ID, revision ID and
200       // langcode ID along with revision metadata. The revision data table holds
201       // data field values for all the revisionable fields and the data table
202       // holds the data field values for all non-revisionable fields. The data
203       // field values of revisionable fields are denormalized in the data
204       // table, as well.
205       $table_mapping->setFieldNames($table_mapping->baseTable, $key_fields);
206
207       // Like in the multilingual, non-revisionable case the UUID is not
208       // in the data table. Additionally, do not store revision metadata
209       // fields in the data table.
210       $data_fields = array_values(array_diff($all_fields, [$uuid_key], $revision_metadata_fields));
211       $table_mapping->setFieldNames($table_mapping->dataTable, $data_fields);
212
213       $revision_base_fields = array_merge([$id_key, $revision_key, $langcode_key], $revision_metadata_fields);
214       $table_mapping->setFieldNames($table_mapping->revisionTable, $revision_base_fields);
215
216       $revision_data_key_fields = [$id_key, $revision_key, $langcode_key];
217       $revision_data_fields = array_diff($revisionable_fields, $revision_metadata_fields, [$langcode_key]);
218       $table_mapping->setFieldNames($table_mapping->revisionDataTable, array_merge($revision_data_key_fields, $revision_data_fields));
219     }
220
221     // Add dedicated tables.
222     $dedicated_table_definitions = array_filter($table_mapping->fieldStorageDefinitions, function (FieldStorageDefinitionInterface $definition) use ($table_mapping) {
223       return $table_mapping->requiresDedicatedTableStorage($definition);
224     });
225     $extra_columns = [
226       'bundle',
227       'deleted',
228       'entity_id',
229       'revision_id',
230       'langcode',
231       'delta',
232     ];
233     foreach ($dedicated_table_definitions as $field_name => $definition) {
234       $tables = [$table_mapping->getDedicatedDataTableName($definition)];
235       if ($revisionable && $definition->isRevisionable()) {
236         $tables[] = $table_mapping->getDedicatedRevisionTableName($definition);
237       }
238       foreach ($tables as $table_name) {
239         $table_mapping->setFieldNames($table_name, [$field_name]);
240         $table_mapping->setExtraColumns($table_name, $extra_columns);
241       }
242     }
243
244     return $table_mapping;
245   }
246
247   /**
248    * Gets the base table name.
249    *
250    * @return string
251    *   The base table name.
252    *
253    * @internal
254    */
255   public function getBaseTable() {
256     return $this->baseTable;
257   }
258
259   /**
260    * Gets the revision table name.
261    *
262    * @return string|null
263    *   The revision table name.
264    *
265    * @internal
266    */
267   public function getRevisionTable() {
268     return $this->revisionTable;
269   }
270
271   /**
272    * Gets the data table name.
273    *
274    * @return string|null
275    *   The data table name.
276    *
277    * @internal
278    */
279   public function getDataTable() {
280     return $this->dataTable;
281   }
282
283   /**
284    * Gets the revision data table name.
285    *
286    * @return string|null
287    *   The revision data table name.
288    *
289    * @internal
290    */
291   public function getRevisionDataTable() {
292     return $this->revisionDataTable;
293   }
294
295   /**
296    * {@inheritdoc}
297    */
298   public function getTableNames() {
299     return array_unique(array_merge(array_keys($this->fieldNames), array_keys($this->extraColumns)));
300   }
301
302   /**
303    * {@inheritdoc}
304    */
305   public function getAllColumns($table_name) {
306     if (!isset($this->allColumns[$table_name])) {
307       $this->allColumns[$table_name] = [];
308
309       foreach ($this->getFieldNames($table_name) as $field_name) {
310         $this->allColumns[$table_name] = array_merge($this->allColumns[$table_name], array_values($this->getColumnNames($field_name)));
311       }
312
313       // There is just one field for each dedicated storage table, thus
314       // $field_name can only refer to it.
315       if (isset($field_name) && $this->requiresDedicatedTableStorage($this->fieldStorageDefinitions[$field_name])) {
316         // Unlike in shared storage tables, in dedicated ones field columns are
317         // positioned last.
318         $this->allColumns[$table_name] = array_merge($this->getExtraColumns($table_name), $this->allColumns[$table_name]);
319       }
320       else {
321         $this->allColumns[$table_name] = array_merge($this->allColumns[$table_name], $this->getExtraColumns($table_name));
322       }
323     }
324     return $this->allColumns[$table_name];
325   }
326
327   /**
328    * {@inheritdoc}
329    */
330   public function getFieldNames($table_name) {
331     if (isset($this->fieldNames[$table_name])) {
332       return $this->fieldNames[$table_name];
333     }
334     return [];
335   }
336
337   /**
338    * {@inheritdoc}
339    */
340   public function getFieldTableName($field_name) {
341     $result = NULL;
342
343     if (isset($this->fieldStorageDefinitions[$field_name])) {
344       // Since a field may be stored in more than one table, we inspect tables
345       // in order of relevance: the data table if present is the main place
346       // where field data is stored, otherwise the base table is responsible for
347       // storing field data. Revision metadata is an exception as it's stored
348       // only in the revision table.
349       $storage_definition = $this->fieldStorageDefinitions[$field_name];
350       $table_names = array_filter([
351         $this->dataTable,
352         $this->baseTable,
353         $this->revisionTable,
354         $this->getDedicatedDataTableName($storage_definition),
355       ]);
356
357       // Collect field columns.
358       $field_columns = [];
359       foreach (array_keys($storage_definition->getColumns()) as $property_name) {
360         $field_columns[] = $this->getFieldColumnName($storage_definition, $property_name);
361       }
362
363       foreach ($table_names as $table_name) {
364         $columns = $this->getAllColumns($table_name);
365         // We assume finding one field column belonging to the mapping is enough
366         // to identify the field table.
367         if (array_intersect($columns, $field_columns)) {
368           $result = $table_name;
369           break;
370         }
371       }
372     }
373
374     if (!isset($result)) {
375       throw new SqlContentEntityStorageException("Table information not available for the '$field_name' field.");
376     }
377
378     return $result;
379   }
380
381   /**
382    * {@inheritdoc}
383    */
384   public function getColumnNames($field_name) {
385     if (!isset($this->columnMapping[$field_name])) {
386       $this->columnMapping[$field_name] = [];
387       if (isset($this->fieldStorageDefinitions[$field_name]) && !$this->fieldStorageDefinitions[$field_name]->hasCustomStorage()) {
388         foreach (array_keys($this->fieldStorageDefinitions[$field_name]->getColumns()) as $property_name) {
389           $this->columnMapping[$field_name][$property_name] = $this->getFieldColumnName($this->fieldStorageDefinitions[$field_name], $property_name);
390         }
391       }
392     }
393     return $this->columnMapping[$field_name];
394   }
395
396   /**
397    * {@inheritdoc}
398    */
399   public function getFieldColumnName(FieldStorageDefinitionInterface $storage_definition, $property_name) {
400     $field_name = $storage_definition->getName();
401
402     if ($this->allowsSharedTableStorage($storage_definition)) {
403       $column_name = count($storage_definition->getColumns()) == 1 ? $field_name : $field_name . '__' . $property_name;
404     }
405     elseif ($this->requiresDedicatedTableStorage($storage_definition)) {
406       if ($property_name == TableMappingInterface::DELTA) {
407         $column_name = 'delta';
408       }
409       else {
410         $column_name = !in_array($property_name, $this->getReservedColumns()) ? $field_name . '_' . $property_name : $property_name;
411       }
412     }
413     else {
414       throw new SqlContentEntityStorageException("Column information not available for the '$field_name' field.");
415     }
416
417     return $column_name;
418   }
419
420   /**
421    * Adds field columns for a table to the table mapping.
422    *
423    * @param string $table_name
424    *   The name of the table to add the field column for.
425    * @param string[] $field_names
426    *   A list of field names to add the columns for.
427    *
428    * @return $this
429    *
430    * @deprecated in Drupal 8.6.0 and will be changed to a protected method
431    *   before Drupal 9.0.0. There will be no replacement for it because the
432    *   default table mapping is now able to be initialized on its own.
433    */
434   public function setFieldNames($table_name, array $field_names) {
435     $this->fieldNames[$table_name] = $field_names;
436     // Force the re-computation of the column list.
437     unset($this->allColumns[$table_name]);
438     return $this;
439   }
440
441   /**
442    * {@inheritdoc}
443    */
444   public function getExtraColumns($table_name) {
445     if (isset($this->extraColumns[$table_name])) {
446       return $this->extraColumns[$table_name];
447     }
448     return [];
449   }
450
451   /**
452    * Adds a extra columns for a table to the table mapping.
453    *
454    * @param string $table_name
455    *   The name of table to add the extra columns for.
456    * @param string[] $column_names
457    *   The list of column names.
458    *
459    * @return $this
460    *
461    * @deprecated in Drupal 8.6.0 and will be changed to a protected method
462    *   before Drupal 9.0.0. There will be no replacement for it because the
463    *   default table mapping is now able to be initialized on its own.
464    */
465   public function setExtraColumns($table_name, array $column_names) {
466     $this->extraColumns[$table_name] = $column_names;
467     // Force the re-computation of the column list.
468     unset($this->allColumns[$table_name]);
469     return $this;
470   }
471
472   /**
473    * Checks whether the given field can be stored in a shared table.
474    *
475    * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
476    *   The field storage definition.
477    *
478    * @return bool
479    *   TRUE if the field can be stored in a shared table, FALSE otherwise.
480    */
481   public function allowsSharedTableStorage(FieldStorageDefinitionInterface $storage_definition) {
482     return !$storage_definition->hasCustomStorage() && $storage_definition->isBaseField() && !$storage_definition->isMultiple() && !$storage_definition->isDeleted();
483   }
484
485   /**
486    * Checks whether the given field has to be stored in a dedicated table.
487    *
488    * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
489    *   The field storage definition.
490    *
491    * @return bool
492    *   TRUE if the field has to be stored in a dedicated table, FALSE otherwise.
493    */
494   public function requiresDedicatedTableStorage(FieldStorageDefinitionInterface $storage_definition) {
495     return !$storage_definition->hasCustomStorage() && !$this->allowsSharedTableStorage($storage_definition);
496   }
497
498   /**
499    * Gets a list of dedicated table names for this mapping.
500    *
501    * @return string[]
502    *   An array of table names.
503    */
504   public function getDedicatedTableNames() {
505     $table_mapping = $this;
506     $definitions = array_filter($this->fieldStorageDefinitions, function ($definition) use ($table_mapping) {
507       return $table_mapping->requiresDedicatedTableStorage($definition);
508     });
509     $data_tables = array_map(function ($definition) use ($table_mapping) {
510       return $table_mapping->getDedicatedDataTableName($definition);
511     }, $definitions);
512     $revision_tables = array_map(function ($definition) use ($table_mapping) {
513       return $table_mapping->getDedicatedRevisionTableName($definition);
514     }, $definitions);
515     $dedicated_tables = array_merge(array_values($data_tables), array_values($revision_tables));
516     return $dedicated_tables;
517   }
518
519   /**
520    * {@inheritdoc}
521    */
522   public function getReservedColumns() {
523     return ['deleted'];
524   }
525
526   /**
527    * Generates a table name for a field data table.
528    *
529    * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
530    *   The field storage definition.
531    * @param bool $is_deleted
532    *   (optional) Whether the table name holding the values of a deleted field
533    *   should be returned.
534    *
535    * @return string
536    *   A string containing the generated name for the database table.
537    */
538   public function getDedicatedDataTableName(FieldStorageDefinitionInterface $storage_definition, $is_deleted = FALSE) {
539     if ($is_deleted) {
540       // When a field is a deleted, the table is renamed to
541       // {field_deleted_data_UNIQUE_STORAGE_ID}. To make sure we don't end up
542       // with table names longer than 64 characters, we hash the unique storage
543       // identifier and return the first 10 characters so we end up with a short
544       // unique ID.
545       return "field_deleted_data_" . substr(hash('sha256', $storage_definition->getUniqueStorageIdentifier()), 0, 10);
546     }
547     else {
548       return $this->generateFieldTableName($storage_definition, FALSE);
549     }
550   }
551
552   /**
553    * Generates a table name for a field revision archive table.
554    *
555    * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
556    *   The field storage definition.
557    * @param bool $is_deleted
558    *   (optional) Whether the table name holding the values of a deleted field
559    *   should be returned.
560    *
561    * @return string
562    *   A string containing the generated name for the database table.
563    */
564   public function getDedicatedRevisionTableName(FieldStorageDefinitionInterface $storage_definition, $is_deleted = FALSE) {
565     if ($is_deleted) {
566       // When a field is a deleted, the table is renamed to
567       // {field_deleted_revision_UNIQUE_STORAGE_ID}. To make sure we don't end
568       // up with table names longer than 64 characters, we hash the unique
569       // storage identifier and return the first 10 characters so we end up with
570       // a short unique ID.
571       return "field_deleted_revision_" . substr(hash('sha256', $storage_definition->getUniqueStorageIdentifier()), 0, 10);
572     }
573     else {
574       return $this->generateFieldTableName($storage_definition, TRUE);
575     }
576   }
577
578   /**
579    * Generates a safe and unambiguous field table name.
580    *
581    * The method accounts for a maximum table name length of 64 characters, and
582    * takes care of disambiguation.
583    *
584    * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
585    *   The field storage definition.
586    * @param bool $revision
587    *   TRUE for revision table, FALSE otherwise.
588    *
589    * @return string
590    *   The final table name.
591    */
592   protected function generateFieldTableName(FieldStorageDefinitionInterface $storage_definition, $revision) {
593     $separator = $revision ? '_revision__' : '__';
594     $table_name = $storage_definition->getTargetEntityTypeId() . $separator . $storage_definition->getName();
595     // Limit the string to 48 characters, keeping a 16 characters margin for db
596     // prefixes.
597     if (strlen($table_name) > 48) {
598       // Use a shorter separator, a truncated entity_type, and a hash of the
599       // field storage unique identifier.
600       $separator = $revision ? '_r__' : '__';
601       // Truncate to the same length for the current and revision tables.
602       $entity_type = substr($storage_definition->getTargetEntityTypeId(), 0, 34);
603       $field_hash = substr(hash('sha256', $storage_definition->getUniqueStorageIdentifier()), 0, 10);
604       $table_name = $entity_type . $separator . $field_hash;
605     }
606     return $table_name;
607   }
608
609 }