4b1e313f8c52ee650987e47f542f04a98a2dd8f5
[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    * A list of field names per table.
29    *
30    * This corresponds to the return value of
31    * TableMappingInterface::getFieldNames() except that this variable is
32    * additionally keyed by table name.
33    *
34    * @var array[]
35    */
36   protected $fieldNames = [];
37
38   /**
39    * A list of database columns which store denormalized data per table.
40    *
41    * This corresponds to the return value of
42    * TableMappingInterface::getExtraColumns() except that this variable is
43    * additionally keyed by table name.
44    *
45    * @var array[]
46    */
47   protected $extraColumns = [];
48
49   /**
50    * A mapping of column names per field name.
51    *
52    * This corresponds to the return value of
53    * TableMappingInterface::getColumnNames() except that this variable is
54    * additionally keyed by field name.
55    *
56    * This data is derived from static::$storageDefinitions, but is stored
57    * separately to avoid repeated processing.
58    *
59    * @var array[]
60    */
61   protected $columnMapping = [];
62
63   /**
64    * A list of all database columns per table.
65    *
66    * This corresponds to the return value of
67    * TableMappingInterface::getAllColumns() except that this variable is
68    * additionally keyed by table name.
69    *
70    * This data is derived from static::$storageDefinitions, static::$fieldNames,
71    * and static::$extraColumns, but is stored separately to avoid repeated
72    * processing.
73    *
74    * @var array[]
75    */
76   protected $allColumns = [];
77
78   /**
79    * Constructs a DefaultTableMapping.
80    *
81    * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
82    *   The entity type definition.
83    * @param \Drupal\Core\Field\FieldStorageDefinitionInterface[] $storage_definitions
84    *   A list of field storage definitions that should be available for the
85    *   field columns of this table mapping.
86    */
87   public function __construct(ContentEntityTypeInterface $entity_type, array $storage_definitions) {
88     $this->entityType = $entity_type;
89     $this->fieldStorageDefinitions = $storage_definitions;
90   }
91
92   /**
93    * {@inheritdoc}
94    */
95   public function getTableNames() {
96     return array_unique(array_merge(array_keys($this->fieldNames), array_keys($this->extraColumns)));
97   }
98
99   /**
100    * {@inheritdoc}
101    */
102   public function getAllColumns($table_name) {
103     if (!isset($this->allColumns[$table_name])) {
104       $this->allColumns[$table_name] = [];
105
106       foreach ($this->getFieldNames($table_name) as $field_name) {
107         $this->allColumns[$table_name] = array_merge($this->allColumns[$table_name], array_values($this->getColumnNames($field_name)));
108       }
109
110       // There is just one field for each dedicated storage table, thus
111       // $field_name can only refer to it.
112       if (isset($field_name) && $this->requiresDedicatedTableStorage($this->fieldStorageDefinitions[$field_name])) {
113         // Unlike in shared storage tables, in dedicated ones field columns are
114         // positioned last.
115         $this->allColumns[$table_name] = array_merge($this->getExtraColumns($table_name), $this->allColumns[$table_name]);
116       }
117       else {
118         $this->allColumns[$table_name] = array_merge($this->allColumns[$table_name], $this->getExtraColumns($table_name));
119       }
120     }
121     return $this->allColumns[$table_name];
122   }
123
124   /**
125    * {@inheritdoc}
126    */
127   public function getFieldNames($table_name) {
128     if (isset($this->fieldNames[$table_name])) {
129       return $this->fieldNames[$table_name];
130     }
131     return [];
132   }
133
134   /**
135    * {@inheritdoc}
136    */
137   public function getFieldTableName($field_name) {
138     $result = NULL;
139
140     if (isset($this->fieldStorageDefinitions[$field_name])) {
141       // Since a field may be stored in more than one table, we inspect tables
142       // in order of relevance: the data table if present is the main place
143       // where field data is stored, otherwise the base table is responsible for
144       // storing field data. Revision metadata is an exception as it's stored
145       // only in the revision table.
146       // @todo The table mapping itself should know about entity tables. See
147       //   https://www.drupal.org/node/2274017.
148       /** @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage $storage */
149       $storage = \Drupal::entityManager()->getStorage($this->entityType->id());
150       $storage_definition = $this->fieldStorageDefinitions[$field_name];
151       $table_names = [
152         $storage->getDataTable(),
153         $storage->getBaseTable(),
154         $storage->getRevisionTable(),
155         $this->getDedicatedDataTableName($storage_definition),
156       ];
157
158       // Collect field columns.
159       $field_columns = [];
160       foreach (array_keys($storage_definition->getColumns()) as $property_name) {
161         $field_columns[] = $this->getFieldColumnName($storage_definition, $property_name);
162       }
163
164       foreach (array_filter($table_names) as $table_name) {
165         $columns = $this->getAllColumns($table_name);
166         // We assume finding one field column belonging to the mapping is enough
167         // to identify the field table.
168         if (array_intersect($columns, $field_columns)) {
169           $result = $table_name;
170           break;
171         }
172       }
173     }
174
175     if (!isset($result)) {
176       throw new SqlContentEntityStorageException("Table information not available for the '$field_name' field.");
177     }
178
179     return $result;
180   }
181
182   /**
183    * {@inheritdoc}
184    */
185   public function getColumnNames($field_name) {
186     if (!isset($this->columnMapping[$field_name])) {
187       $this->columnMapping[$field_name] = [];
188       if (isset($this->fieldStorageDefinitions[$field_name]) && !$this->fieldStorageDefinitions[$field_name]->hasCustomStorage()) {
189         foreach (array_keys($this->fieldStorageDefinitions[$field_name]->getColumns()) as $property_name) {
190           $this->columnMapping[$field_name][$property_name] = $this->getFieldColumnName($this->fieldStorageDefinitions[$field_name], $property_name);
191         }
192       }
193     }
194     return $this->columnMapping[$field_name];
195   }
196
197   /**
198    * {@inheritdoc}
199    */
200   public function getFieldColumnName(FieldStorageDefinitionInterface $storage_definition, $property_name) {
201     $field_name = $storage_definition->getName();
202
203     if ($this->allowsSharedTableStorage($storage_definition)) {
204       $column_name = count($storage_definition->getColumns()) == 1 ? $field_name : $field_name . '__' . $property_name;
205     }
206     elseif ($this->requiresDedicatedTableStorage($storage_definition)) {
207       if ($property_name == TableMappingInterface::DELTA) {
208         $column_name = 'delta';
209       }
210       else {
211         $column_name = !in_array($property_name, $this->getReservedColumns()) ? $field_name . '_' . $property_name : $property_name;
212       }
213     }
214     else {
215       throw new SqlContentEntityStorageException("Column information not available for the '$field_name' field.");
216     }
217
218     return $column_name;
219   }
220
221   /**
222    * Adds field columns for a table to the table mapping.
223    *
224    * @param string $table_name
225    *   The name of the table to add the field column for.
226    * @param string[] $field_names
227    *   A list of field names to add the columns for.
228    *
229    * @return $this
230    */
231   public function setFieldNames($table_name, array $field_names) {
232     $this->fieldNames[$table_name] = $field_names;
233     // Force the re-computation of the column list.
234     unset($this->allColumns[$table_name]);
235     return $this;
236   }
237
238   /**
239    * {@inheritdoc}
240    */
241   public function getExtraColumns($table_name) {
242     if (isset($this->extraColumns[$table_name])) {
243       return $this->extraColumns[$table_name];
244     }
245     return [];
246   }
247
248   /**
249    * Adds a extra columns for a table to the table mapping.
250    *
251    * @param string $table_name
252    *   The name of table to add the extra columns for.
253    * @param string[] $column_names
254    *   The list of column names.
255    *
256    * @return $this
257    */
258   public function setExtraColumns($table_name, array $column_names) {
259     $this->extraColumns[$table_name] = $column_names;
260     // Force the re-computation of the column list.
261     unset($this->allColumns[$table_name]);
262     return $this;
263   }
264
265   /**
266    * Checks whether the given field can be stored in a shared table.
267    *
268    * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
269    *   The field storage definition.
270    *
271    * @return bool
272    *   TRUE if the field can be stored in a dedicated table, FALSE otherwise.
273    */
274   public function allowsSharedTableStorage(FieldStorageDefinitionInterface $storage_definition) {
275     return !$storage_definition->hasCustomStorage() && $storage_definition->isBaseField() && !$storage_definition->isMultiple() && !$storage_definition->isDeleted();
276   }
277
278   /**
279    * Checks whether the given field has to be stored in a dedicated table.
280    *
281    * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
282    *   The field storage definition.
283    *
284    * @return bool
285    *   TRUE if the field can be stored in a dedicated table, FALSE otherwise.
286    */
287   public function requiresDedicatedTableStorage(FieldStorageDefinitionInterface $storage_definition) {
288     return !$storage_definition->hasCustomStorage() && !$this->allowsSharedTableStorage($storage_definition);
289   }
290
291   /**
292    * Gets a list of dedicated table names for this mapping.
293    *
294    * @return string[]
295    *   An array of table names.
296    */
297   public function getDedicatedTableNames() {
298     $table_mapping = $this;
299     $definitions = array_filter($this->fieldStorageDefinitions, function ($definition) use ($table_mapping) {
300       return $table_mapping->requiresDedicatedTableStorage($definition);
301     });
302     $data_tables = array_map(function ($definition) use ($table_mapping) {
303       return $table_mapping->getDedicatedDataTableName($definition);
304     }, $definitions);
305     $revision_tables = array_map(function ($definition) use ($table_mapping) {
306       return $table_mapping->getDedicatedRevisionTableName($definition);
307     }, $definitions);
308     $dedicated_tables = array_merge(array_values($data_tables), array_values($revision_tables));
309     return $dedicated_tables;
310   }
311
312   /**
313    * {@inheritdoc}
314    */
315   public function getReservedColumns() {
316     return ['deleted'];
317   }
318
319   /**
320    * Generates a table name for a field data table.
321    *
322    * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
323    *   The field storage definition.
324    * @param bool $is_deleted
325    *   (optional) Whether the table name holding the values of a deleted field
326    *   should be returned.
327    *
328    * @return string
329    *   A string containing the generated name for the database table.
330    */
331   public function getDedicatedDataTableName(FieldStorageDefinitionInterface $storage_definition, $is_deleted = FALSE) {
332     if ($is_deleted) {
333       // When a field is a deleted, the table is renamed to
334       // {field_deleted_data_UNIQUE_STORAGE_ID}. To make sure we don't end up
335       // with table names longer than 64 characters, we hash the unique storage
336       // identifier and return the first 10 characters so we end up with a short
337       // unique ID.
338       return "field_deleted_data_" . substr(hash('sha256', $storage_definition->getUniqueStorageIdentifier()), 0, 10);
339     }
340     else {
341       return $this->generateFieldTableName($storage_definition, FALSE);
342     }
343   }
344
345   /**
346    * Generates a table name for a field revision archive table.
347    *
348    * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
349    *   The field storage definition.
350    * @param bool $is_deleted
351    *   (optional) Whether the table name holding the values of a deleted field
352    *   should be returned.
353    *
354    * @return string
355    *   A string containing the generated name for the database table.
356    */
357   public function getDedicatedRevisionTableName(FieldStorageDefinitionInterface $storage_definition, $is_deleted = FALSE) {
358     if ($is_deleted) {
359       // When a field is a deleted, the table is renamed to
360       // {field_deleted_revision_UNIQUE_STORAGE_ID}. To make sure we don't end
361       // up with table names longer than 64 characters, we hash the unique
362       // storage identifier and return the first 10 characters so we end up with
363       // a short unique ID.
364       return "field_deleted_revision_" . substr(hash('sha256', $storage_definition->getUniqueStorageIdentifier()), 0, 10);
365     }
366     else {
367       return $this->generateFieldTableName($storage_definition, TRUE);
368     }
369   }
370
371   /**
372    * Generates a safe and unambiguous field table name.
373    *
374    * The method accounts for a maximum table name length of 64 characters, and
375    * takes care of disambiguation.
376    *
377    * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
378    *   The field storage definition.
379    * @param bool $revision
380    *   TRUE for revision table, FALSE otherwise.
381    *
382    * @return string
383    *   The final table name.
384    */
385   protected function generateFieldTableName(FieldStorageDefinitionInterface $storage_definition, $revision) {
386     $separator = $revision ? '_revision__' : '__';
387     $table_name = $storage_definition->getTargetEntityTypeId() . $separator . $storage_definition->getName();
388     // Limit the string to 48 characters, keeping a 16 characters margin for db
389     // prefixes.
390     if (strlen($table_name) > 48) {
391       // Use a shorter separator, a truncated entity_type, and a hash of the
392       // field storage unique identifier.
393       $separator = $revision ? '_r__' : '__';
394       // Truncate to the same length for the current and revision tables.
395       $entity_type = substr($storage_definition->getTargetEntityTypeId(), 0, 34);
396       $field_hash = substr(hash('sha256', $storage_definition->getUniqueStorageIdentifier()), 0, 10);
397       $table_name = $entity_type . $separator . $field_hash;
398     }
399     return $table_name;
400   }
401
402 }