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