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