Further Drupal 8.6.4 changes. Some core files were not committed before a commit...
[yaffs-website] / web / core / lib / Drupal / Core / Entity / EntityDefinitionUpdateManager.php
1 <?php
2
3 namespace Drupal\Core\Entity;
4
5 use Drupal\Core\Entity\Schema\DynamicallyFieldableEntityStorageSchemaInterface;
6 use Drupal\Core\Entity\Schema\EntityStorageSchemaInterface;
7 use Drupal\Core\Field\BaseFieldDefinition;
8 use Drupal\Core\Field\FieldStorageDefinitionInterface;
9 use Drupal\Core\StringTranslation\StringTranslationTrait;
10
11 /**
12  * Manages entity definition updates.
13  */
14 class EntityDefinitionUpdateManager implements EntityDefinitionUpdateManagerInterface {
15   use StringTranslationTrait;
16
17   /**
18    * The entity manager service.
19    *
20    * @var \Drupal\Core\Entity\EntityManagerInterface
21    */
22   protected $entityManager;
23
24   /**
25    * The last installed schema repository.
26    *
27    * @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface
28    */
29   protected $entityLastInstalledSchemaRepository;
30
31   /**
32    * Constructs a new EntityDefinitionUpdateManager.
33    *
34    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
35    *   The entity manager.
36    * @param \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $entity_last_installed_schema_repository
37    *   The last installed schema repository service.
38    */
39   public function __construct(EntityManagerInterface $entity_manager, EntityLastInstalledSchemaRepositoryInterface $entity_last_installed_schema_repository = NULL) {
40     $this->entityManager = $entity_manager;
41
42     if (!isset($entity_last_installed_schema_repository)) {
43       @trigger_error('The $entity_last_installed_schema_repository parameter was added in Drupal 8.6.x and will be required in 9.0.0. See https://www.drupal.org/node/2973262.', E_USER_DEPRECATED);
44       $entity_last_installed_schema_repository = \Drupal::service('entity.last_installed_schema.repository');
45     }
46     $this->entityLastInstalledSchemaRepository = $entity_last_installed_schema_repository;
47   }
48
49   /**
50    * {@inheritdoc}
51    */
52   public function needsUpdates() {
53     return (bool) $this->getChangeList();
54   }
55
56   /**
57    * {@inheritdoc}
58    */
59   public function getChangeSummary() {
60     $summary = [];
61
62     foreach ($this->getChangeList() as $entity_type_id => $change_list) {
63       // Process entity type definition changes.
64       if (!empty($change_list['entity_type'])) {
65         $entity_type = $this->entityManager->getDefinition($entity_type_id);
66
67         switch ($change_list['entity_type']) {
68           case static::DEFINITION_CREATED:
69             $summary[$entity_type_id][] = $this->t('The %entity_type entity type needs to be installed.', ['%entity_type' => $entity_type->getLabel()]);
70             break;
71
72           case static::DEFINITION_UPDATED:
73             $summary[$entity_type_id][] = $this->t('The %entity_type entity type needs to be updated.', ['%entity_type' => $entity_type->getLabel()]);
74             break;
75         }
76       }
77
78       // Process field storage definition changes.
79       if (!empty($change_list['field_storage_definitions'])) {
80         $storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
81         $original_storage_definitions = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($entity_type_id);
82
83         foreach ($change_list['field_storage_definitions'] as $field_name => $change) {
84           switch ($change) {
85             case static::DEFINITION_CREATED:
86               $summary[$entity_type_id][] = $this->t('The %field_name field needs to be installed.', ['%field_name' => $storage_definitions[$field_name]->getLabel()]);
87               break;
88
89             case static::DEFINITION_UPDATED:
90               $summary[$entity_type_id][] = $this->t('The %field_name field needs to be updated.', ['%field_name' => $storage_definitions[$field_name]->getLabel()]);
91               break;
92
93             case static::DEFINITION_DELETED:
94               $summary[$entity_type_id][] = $this->t('The %field_name field needs to be uninstalled.', ['%field_name' => $original_storage_definitions[$field_name]->getLabel()]);
95               break;
96           }
97         }
98       }
99     }
100
101     return $summary;
102   }
103
104   /**
105    * {@inheritdoc}
106    */
107   public function applyUpdates() {
108     $complete_change_list = $this->getChangeList();
109     if ($complete_change_list) {
110       // self::getChangeList() only disables the cache and does not invalidate.
111       // In case there are changes, explicitly invalidate caches.
112       $this->entityManager->clearCachedDefinitions();
113     }
114     foreach ($complete_change_list as $entity_type_id => $change_list) {
115       // Process entity type definition changes before storage definitions ones
116       // this is necessary when you change an entity type from non-revisionable
117       // to revisionable and at the same time add revisionable fields to the
118       // entity type.
119       if (!empty($change_list['entity_type'])) {
120         $this->doEntityUpdate($change_list['entity_type'], $entity_type_id);
121       }
122
123       // Process field storage definition changes.
124       if (!empty($change_list['field_storage_definitions'])) {
125         $storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
126         $original_storage_definitions = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($entity_type_id);
127
128         foreach ($change_list['field_storage_definitions'] as $field_name => $change) {
129           $storage_definition = isset($storage_definitions[$field_name]) ? $storage_definitions[$field_name] : NULL;
130           $original_storage_definition = isset($original_storage_definitions[$field_name]) ? $original_storage_definitions[$field_name] : NULL;
131           $this->doFieldUpdate($change, $storage_definition, $original_storage_definition);
132         }
133       }
134     }
135   }
136
137   /**
138    * {@inheritdoc}
139    */
140   public function getEntityType($entity_type_id) {
141     $entity_type = $this->entityLastInstalledSchemaRepository->getLastInstalledDefinition($entity_type_id);
142     return $entity_type ? clone $entity_type : NULL;
143   }
144
145   /**
146    * {@inheritdoc}
147    */
148   public function getEntityTypes() {
149     return $this->entityLastInstalledSchemaRepository->getLastInstalledDefinitions();
150   }
151
152   /**
153    * {@inheritdoc}
154    */
155   public function installEntityType(EntityTypeInterface $entity_type) {
156     $this->entityManager->clearCachedDefinitions();
157     $this->entityManager->onEntityTypeCreate($entity_type);
158   }
159
160   /**
161    * {@inheritdoc}
162    */
163   public function updateEntityType(EntityTypeInterface $entity_type) {
164     $original = $this->getEntityType($entity_type->id());
165     $this->entityManager->clearCachedDefinitions();
166     $this->entityManager->onEntityTypeUpdate($entity_type, $original);
167   }
168
169   /**
170    * {@inheritdoc}
171    */
172   public function uninstallEntityType(EntityTypeInterface $entity_type) {
173     $this->entityManager->clearCachedDefinitions();
174     $this->entityManager->onEntityTypeDelete($entity_type);
175   }
176
177   /**
178    * {@inheritdoc}
179    */
180   public function installFieldStorageDefinition($name, $entity_type_id, $provider, FieldStorageDefinitionInterface $storage_definition) {
181     // @todo Pass a mutable field definition interface when we have one. See
182     //   https://www.drupal.org/node/2346329.
183     if ($storage_definition instanceof BaseFieldDefinition) {
184       $storage_definition
185         ->setName($name)
186         ->setTargetEntityTypeId($entity_type_id)
187         ->setProvider($provider)
188         ->setTargetBundle(NULL);
189     }
190     $this->entityManager->clearCachedDefinitions();
191     $this->entityManager->onFieldStorageDefinitionCreate($storage_definition);
192   }
193
194   /**
195    * {@inheritdoc}
196    */
197   public function getFieldStorageDefinition($name, $entity_type_id) {
198     $storage_definitions = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($entity_type_id);
199     return isset($storage_definitions[$name]) ? clone $storage_definitions[$name] : NULL;
200   }
201
202   /**
203    * {@inheritdoc}
204    */
205   public function updateFieldStorageDefinition(FieldStorageDefinitionInterface $storage_definition) {
206     $original = $this->getFieldStorageDefinition($storage_definition->getName(), $storage_definition->getTargetEntityTypeId());
207     $this->entityManager->clearCachedDefinitions();
208     $this->entityManager->onFieldStorageDefinitionUpdate($storage_definition, $original);
209   }
210
211   /**
212    * {@inheritdoc}
213    */
214   public function uninstallFieldStorageDefinition(FieldStorageDefinitionInterface $storage_definition) {
215     $this->entityManager->clearCachedDefinitions();
216     $this->entityManager->onFieldStorageDefinitionDelete($storage_definition);
217   }
218
219   /**
220    * Performs an entity type definition update.
221    *
222    * @param string $op
223    *   The operation to perform, either static::DEFINITION_CREATED or
224    *   static::DEFINITION_UPDATED.
225    * @param string $entity_type_id
226    *   The entity type ID.
227    */
228   protected function doEntityUpdate($op, $entity_type_id) {
229     $entity_type = $this->entityManager->getDefinition($entity_type_id);
230     switch ($op) {
231       case static::DEFINITION_CREATED:
232         $this->entityManager->onEntityTypeCreate($entity_type);
233         break;
234
235       case static::DEFINITION_UPDATED:
236         $original = $this->entityLastInstalledSchemaRepository->getLastInstalledDefinition($entity_type_id);
237         $this->entityManager->onEntityTypeUpdate($entity_type, $original);
238         break;
239     }
240   }
241
242   /**
243    * Performs a field storage definition update.
244    *
245    * @param string $op
246    *   The operation to perform, possible values are static::DEFINITION_CREATED,
247    *   static::DEFINITION_UPDATED or static::DEFINITION_DELETED.
248    * @param array|null $storage_definition
249    *   The new field storage definition.
250    * @param array|null $original_storage_definition
251    *   The original field storage definition.
252    */
253   protected function doFieldUpdate($op, $storage_definition = NULL, $original_storage_definition = NULL) {
254     switch ($op) {
255       case static::DEFINITION_CREATED:
256         $this->entityManager->onFieldStorageDefinitionCreate($storage_definition);
257         break;
258
259       case static::DEFINITION_UPDATED:
260         $this->entityManager->onFieldStorageDefinitionUpdate($storage_definition, $original_storage_definition);
261         break;
262
263       case static::DEFINITION_DELETED:
264         $this->entityManager->onFieldStorageDefinitionDelete($original_storage_definition);
265         break;
266     }
267   }
268
269   /**
270    * Gets a list of changes to entity type and field storage definitions.
271    *
272    * @return array
273    *   An associative array keyed by entity type id of change descriptors. Every
274    *   entry is an associative array with the following optional keys:
275    *   - entity_type: a scalar having only the DEFINITION_UPDATED value.
276    *   - field_storage_definitions: an associative array keyed by field name of
277    *     scalars having one value among:
278    *     - DEFINITION_CREATED
279    *     - DEFINITION_UPDATED
280    *     - DEFINITION_DELETED
281    */
282   protected function getChangeList() {
283     $this->entityManager->useCaches(FALSE);
284     $change_list = [];
285
286     foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
287       $original = $this->entityLastInstalledSchemaRepository->getLastInstalledDefinition($entity_type_id);
288
289       // @todo Support non-storage-schema-changing definition updates too:
290       //   https://www.drupal.org/node/2336895.
291       if (!$original) {
292         $change_list[$entity_type_id]['entity_type'] = static::DEFINITION_CREATED;
293       }
294       else {
295         if ($this->requiresEntityStorageSchemaChanges($entity_type, $original)) {
296           $change_list[$entity_type_id]['entity_type'] = static::DEFINITION_UPDATED;
297         }
298
299         if ($this->entityManager->getStorage($entity_type_id) instanceof DynamicallyFieldableEntityStorageInterface) {
300           $field_changes = [];
301           $storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
302           $original_storage_definitions = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($entity_type_id);
303
304           // Detect created field storage definitions.
305           foreach (array_diff_key($storage_definitions, $original_storage_definitions) as $field_name => $storage_definition) {
306             $field_changes[$field_name] = static::DEFINITION_CREATED;
307           }
308
309           // Detect deleted field storage definitions.
310           foreach (array_diff_key($original_storage_definitions, $storage_definitions) as $field_name => $original_storage_definition) {
311             $field_changes[$field_name] = static::DEFINITION_DELETED;
312           }
313
314           // Detect updated field storage definitions.
315           foreach (array_intersect_key($storage_definitions, $original_storage_definitions) as $field_name => $storage_definition) {
316             // @todo Support non-storage-schema-changing definition updates too:
317             //   https://www.drupal.org/node/2336895. So long as we're checking
318             //   based on schema change requirements rather than definition
319             //   equality, skip the check if the entity type itself needs to be
320             //   updated, since that can affect the schema of all fields, so we
321             //   want to process that update first without reporting false
322             //   positives here.
323             if (!isset($change_list[$entity_type_id]['entity_type']) && $this->requiresFieldStorageSchemaChanges($storage_definition, $original_storage_definitions[$field_name])) {
324               $field_changes[$field_name] = static::DEFINITION_UPDATED;
325             }
326           }
327
328           if ($field_changes) {
329             $change_list[$entity_type_id]['field_storage_definitions'] = $field_changes;
330           }
331         }
332       }
333     }
334
335     // @todo Support deleting entity definitions when we support base field
336     //   purging.
337     // @see https://www.drupal.org/node/2907779
338
339     $this->entityManager->useCaches(TRUE);
340
341     return array_filter($change_list);
342   }
343
344   /**
345    * Checks if the changes to the entity type requires storage schema changes.
346    *
347    * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
348    *   The updated entity type definition.
349    * @param \Drupal\Core\Entity\EntityTypeInterface $original
350    *   The original entity type definition.
351    *
352    * @return bool
353    *   TRUE if storage schema changes are required, FALSE otherwise.
354    */
355   protected function requiresEntityStorageSchemaChanges(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
356     $storage = $this->entityManager->getStorage($entity_type->id());
357     return ($storage instanceof EntityStorageSchemaInterface) && $storage->requiresEntityStorageSchemaChanges($entity_type, $original);
358   }
359
360   /**
361    * Checks if the changes to the storage definition requires schema changes.
362    *
363    * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
364    *   The updated field storage definition.
365    * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $original
366    *   The original field storage definition.
367    *
368    * @return bool
369    *   TRUE if storage schema changes are required, FALSE otherwise.
370    */
371   protected function requiresFieldStorageSchemaChanges(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
372     $storage = $this->entityManager->getStorage($storage_definition->getTargetEntityTypeId());
373     return ($storage instanceof DynamicallyFieldableEntityStorageSchemaInterface) && $storage->requiresFieldStorageSchemaChanges($storage_definition, $original);
374   }
375
376 }