3 namespace Drupal\KernelTests\Core\Entity;
5 use Drupal\Component\Plugin\Exception\PluginNotFoundException;
6 use Drupal\Core\Database\Database;
7 use Drupal\Core\Database\DatabaseExceptionWrapper;
8 use Drupal\Core\Database\IntegrityConstraintViolationException;
9 use Drupal\Core\Entity\ContentEntityType;
10 use Drupal\Core\Entity\EntityStorageException;
11 use Drupal\Core\Entity\EntityTypeEvents;
12 use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
13 use Drupal\Core\Field\BaseFieldDefinition;
14 use Drupal\Core\Field\FieldException;
15 use Drupal\Core\Field\FieldStorageDefinitionEvents;
16 use Drupal\Core\Language\LanguageInterface;
17 use Drupal\entity_test_update\Entity\EntityTestUpdate;
18 use Drupal\system\Tests\Entity\EntityDefinitionTestTrait;
21 * Tests EntityDefinitionUpdateManager functionality.
25 class EntityDefinitionUpdateTest extends EntityKernelTestBase {
27 use EntityDefinitionTestTrait;
30 * The entity definition update manager.
32 * @var \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface
34 protected $entityDefinitionUpdateManager;
37 * The database connection.
39 * @var \Drupal\Core\Database\Connection
48 public static $modules = ['entity_test_update'];
53 protected function setUp() {
55 $this->entityDefinitionUpdateManager = $this->container->get('entity.definition_update_manager');
56 $this->database = $this->container->get('database');
58 // Install every entity type's schema that wasn't installed in the parent
60 foreach (array_diff_key($this->entityManager->getDefinitions(), array_flip(['user', 'entity_test'])) as $entity_type_id => $entity_type) {
61 $this->installEntitySchema($entity_type_id);
66 * Tests that new entity type definitions are correctly handled.
68 public function testNewEntityType() {
69 $entity_type_id = 'entity_test_new';
70 $schema = $this->database->schema();
72 // Check that the "entity_test_new" is not defined.
73 $entity_types = $this->entityManager->getDefinitions();
74 $this->assertFalse(isset($entity_types[$entity_type_id]), 'The "entity_test_new" entity type does not exist.');
75 $this->assertFalse($schema->tableExists($entity_type_id), 'Schema for the "entity_test_new" entity type does not exist.');
77 // Check that the "entity_test_new" is now defined and the related schema
79 $this->enableNewEntityType();
80 $entity_types = $this->entityManager->getDefinitions();
81 $this->assertTrue(isset($entity_types[$entity_type_id]), 'The "entity_test_new" entity type exists.');
82 $this->assertTrue($schema->tableExists($entity_type_id), 'Schema for the "entity_test_new" entity type has been created.');
86 * Tests when no definition update is needed.
88 public function testNoUpdates() {
89 // Ensure that the definition update manager reports no updates.
90 $this->assertFalse($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that no updates are needed.');
91 $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), [], 'EntityDefinitionUpdateManager reports an empty change summary.');
93 // Ensure that applyUpdates() runs without error (it's not expected to do
94 // anything when there aren't updates).
95 $this->entityDefinitionUpdateManager->applyUpdates();
99 * Tests updating entity schema when there are no existing entities.
101 public function testEntityTypeUpdateWithoutData() {
102 // The 'entity_test_update' entity type starts out non-revisionable, so
103 // ensure the revision table hasn't been created during setUp().
104 $this->assertFalse($this->database->schema()->tableExists('entity_test_update_revision'), 'Revision table not created for entity_test_update.');
106 // Update it to be revisionable and ensure the definition update manager
107 // reports that an update is needed.
108 $this->updateEntityTypeToRevisionable();
109 $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
111 'entity_test_update' => [
112 t('The %entity_type entity type needs to be updated.', ['%entity_type' => $this->entityManager->getDefinition('entity_test_update')->getLabel()]),
113 // The revision key is now defined, so the revision field needs to be
115 t('The %field_name field needs to be installed.', ['%field_name' => 'Revision ID']),
118 $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
120 // Run the update and ensure the revision table is created.
121 $this->entityDefinitionUpdateManager->applyUpdates();
122 $this->assertTrue($this->database->schema()->tableExists('entity_test_update_revision'), 'Revision table created for entity_test_update.');
126 * Tests updating entity schema when there are existing entities.
128 public function testEntityTypeUpdateWithData() {
130 $this->entityManager->getStorage('entity_test_update')->create()->save();
132 // Update the entity type to be revisionable and try to apply the update.
133 // It's expected to throw an exception.
134 $this->updateEntityTypeToRevisionable();
136 $this->entityDefinitionUpdateManager->applyUpdates();
137 $this->fail('EntityStorageException thrown when trying to apply an update that requires data migration.');
139 catch (EntityStorageException $e) {
140 $this->pass('EntityStorageException thrown when trying to apply an update that requires data migration.');
145 * Tests creating, updating, and deleting a base field if no entities exist.
147 public function testBaseFieldCreateUpdateDeleteWithoutData() {
148 // Add a base field, ensure the update manager reports it, and the update
149 // creates its schema.
150 $this->addBaseField();
151 $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
153 'entity_test_update' => [
154 t('The %field_name field needs to be installed.', ['%field_name' => t('A new base field')]),
157 $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
158 $this->entityDefinitionUpdateManager->applyUpdates();
159 $this->assertTrue($this->database->schema()->fieldExists('entity_test_update', 'new_base_field'), 'Column created in shared table for new_base_field.');
161 // Add an index on the base field, ensure the update manager reports it,
162 // and the update creates it.
163 $this->addBaseFieldIndex();
164 $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
166 'entity_test_update' => [
167 t('The %field_name field needs to be updated.', ['%field_name' => t('A new base field')]),
170 $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
171 $this->entityDefinitionUpdateManager->applyUpdates();
172 $this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update_field__new_base_field'), 'Index created.');
174 // Remove the above index, ensure the update manager reports it, and the
175 // update deletes it.
176 $this->removeBaseFieldIndex();
177 $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
179 'entity_test_update' => [
180 t('The %field_name field needs to be updated.', ['%field_name' => t('A new base field')]),
183 $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
184 $this->entityDefinitionUpdateManager->applyUpdates();
185 $this->assertFalse($this->database->schema()->indexExists('entity_test_update', 'entity_test_update_field__new_base_field'), 'Index deleted.');
187 // Update the type of the base field from 'string' to 'text', ensure the
188 // update manager reports it, and the update adjusts the schema
190 $this->modifyBaseField();
191 $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
193 'entity_test_update' => [
194 t('The %field_name field needs to be updated.', ['%field_name' => t('A new base field')]),
197 $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
198 $this->entityDefinitionUpdateManager->applyUpdates();
199 $this->assertFalse($this->database->schema()->fieldExists('entity_test_update', 'new_base_field'), 'Original column deleted in shared table for new_base_field.');
200 $this->assertTrue($this->database->schema()->fieldExists('entity_test_update', 'new_base_field__value'), 'Value column created in shared table for new_base_field.');
201 $this->assertTrue($this->database->schema()->fieldExists('entity_test_update', 'new_base_field__format'), 'Format column created in shared table for new_base_field.');
203 // Remove the base field, ensure the update manager reports it, and the
204 // update deletes the schema.
205 $this->removeBaseField();
206 $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
208 'entity_test_update' => [
209 t('The %field_name field needs to be uninstalled.', ['%field_name' => t('A new base field')]),
212 $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
213 $this->entityDefinitionUpdateManager->applyUpdates();
214 $this->assertFalse($this->database->schema()->fieldExists('entity_test_update', 'new_base_field_value'), 'Value column deleted from shared table for new_base_field.');
215 $this->assertFalse($this->database->schema()->fieldExists('entity_test_update', 'new_base_field_format'), 'Format column deleted from shared table for new_base_field.');
219 * Tests creating, updating, and deleting a bundle field if no entities exist.
221 public function testBundleFieldCreateUpdateDeleteWithoutData() {
222 // Add a bundle field, ensure the update manager reports it, and the update
223 // creates its schema.
224 $this->addBundleField();
225 $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
227 'entity_test_update' => [
228 t('The %field_name field needs to be installed.', ['%field_name' => t('A new bundle field')]),
231 $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
232 $this->entityDefinitionUpdateManager->applyUpdates();
233 $this->assertTrue($this->database->schema()->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table created for new_bundle_field.');
235 // Update the type of the base field from 'string' to 'text', ensure the
236 // update manager reports it, and the update adjusts the schema
238 $this->modifyBundleField();
239 $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
241 'entity_test_update' => [
242 t('The %field_name field needs to be updated.', ['%field_name' => t('A new bundle field')]),
245 $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
246 $this->entityDefinitionUpdateManager->applyUpdates();
247 $this->assertTrue($this->database->schema()->fieldExists('entity_test_update__new_bundle_field', 'new_bundle_field_format'), 'Format column created in dedicated table for new_base_field.');
249 // Remove the bundle field, ensure the update manager reports it, and the
250 // update deletes the schema.
251 $this->removeBundleField();
252 $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
254 'entity_test_update' => [
255 t('The %field_name field needs to be uninstalled.', ['%field_name' => t('A new bundle field')]),
258 $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
259 $this->entityDefinitionUpdateManager->applyUpdates();
260 $this->assertFalse($this->database->schema()->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table deleted for new_bundle_field.');
264 * Tests creating and deleting a base field if entities exist.
266 * This tests deletion when there are existing entities, but not existing data
267 * for the field being deleted.
269 * @see testBaseFieldDeleteWithExistingData()
271 public function testBaseFieldCreateDeleteWithExistingEntities() {
273 $name = $this->randomString();
274 $storage = $this->entityManager->getStorage('entity_test_update');
275 $entity = $storage->create(['name' => $name]);
278 // Add a base field and run the update. Ensure the base field's column is
279 // created and the prior saved entity data is still there.
280 $this->addBaseField();
281 $this->entityDefinitionUpdateManager->applyUpdates();
282 $schema_handler = $this->database->schema();
283 $this->assertTrue($schema_handler->fieldExists('entity_test_update', 'new_base_field'), 'Column created in shared table for new_base_field.');
284 $entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id());
285 $this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field creation.');
287 // Remove the base field and run the update. Ensure the base field's column
288 // is deleted and the prior saved entity data is still there.
289 $this->removeBaseField();
290 $this->entityDefinitionUpdateManager->applyUpdates();
291 $this->assertFalse($schema_handler->fieldExists('entity_test_update', 'new_base_field'), 'Column deleted from shared table for new_base_field.');
292 $entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id());
293 $this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field deletion.');
295 // Add a base field with a required property and run the update. Ensure
296 // 'not null' is not applied and thus no exception is thrown.
297 $this->addBaseField('shape_required');
298 $this->entityDefinitionUpdateManager->applyUpdates();
299 $assert = $schema_handler->fieldExists('entity_test_update', 'new_base_field__shape') && $schema_handler->fieldExists('entity_test_update', 'new_base_field__color');
300 $this->assertTrue($assert, 'Columns created in shared table for new_base_field.');
302 // Recreate the field after emptying the base table and check that its
303 // columns are not 'not null'.
304 // @todo Revisit this test when allowing for required storage field
305 // definitions. See https://www.drupal.org/node/2390495.
307 $this->removeBaseField();
308 $this->entityDefinitionUpdateManager->applyUpdates();
309 $assert = !$schema_handler->fieldExists('entity_test_update', 'new_base_field__shape') && !$schema_handler->fieldExists('entity_test_update', 'new_base_field__color');
310 $this->assert($assert, 'Columns removed from the shared table for new_base_field.');
311 $this->addBaseField('shape_required');
312 $this->entityDefinitionUpdateManager->applyUpdates();
313 $assert = $schema_handler->fieldExists('entity_test_update', 'new_base_field__shape') && $schema_handler->fieldExists('entity_test_update', 'new_base_field__color');
314 $this->assertTrue($assert, 'Columns created again in shared table for new_base_field.');
315 $entity = $storage->create(['name' => $name]);
317 $this->pass('The new_base_field columns are still nullable');
321 * Tests creating and deleting a bundle field if entities exist.
323 * This tests deletion when there are existing entities, but not existing data
324 * for the field being deleted.
326 * @see testBundleFieldDeleteWithExistingData()
328 public function testBundleFieldCreateDeleteWithExistingEntities() {
330 $name = $this->randomString();
331 $storage = $this->entityManager->getStorage('entity_test_update');
332 $entity = $storage->create(['name' => $name]);
335 // Add a bundle field and run the update. Ensure the bundle field's table
336 // is created and the prior saved entity data is still there.
337 $this->addBundleField();
338 $this->entityDefinitionUpdateManager->applyUpdates();
339 $schema_handler = $this->database->schema();
340 $this->assertTrue($schema_handler->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table created for new_bundle_field.');
341 $entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id());
342 $this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field creation.');
344 // Remove the base field and run the update. Ensure the bundle field's
345 // table is deleted and the prior saved entity data is still there.
346 $this->removeBundleField();
347 $this->entityDefinitionUpdateManager->applyUpdates();
348 $this->assertFalse($schema_handler->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table deleted for new_bundle_field.');
349 $entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id());
350 $this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field deletion.');
352 // Test that required columns are created as 'not null'.
353 $this->addBundleField('shape_required');
354 $this->entityDefinitionUpdateManager->applyUpdates();
355 $message = 'The new_bundle_field_shape column is not nullable.';
357 'bundle' => $entity->bundle(),
359 'entity_id' => $entity->id(),
360 'revision_id' => $entity->id(),
361 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
363 'new_bundle_field_color' => $this->randomString(),
366 // Try to insert a record without providing a value for the 'not null'
367 // column. This should fail.
368 $this->database->insert('entity_test_update__new_bundle_field')
371 $this->fail($message);
373 catch (\RuntimeException $e) {
374 if ($e instanceof DatabaseExceptionWrapper || $e instanceof IntegrityConstraintViolationException) {
375 // Now provide a value for the 'not null' column. This is expected to
377 $values['new_bundle_field_shape'] = $this->randomString();
378 $this->database->insert('entity_test_update__new_bundle_field')
381 $this->pass($message);
391 * Tests deleting a base field when it has existing data.
393 public function testBaseFieldDeleteWithExistingData() {
394 // Add the base field and run the update.
395 $this->addBaseField();
396 $this->entityDefinitionUpdateManager->applyUpdates();
398 // Save an entity with the base field populated.
399 $this->entityManager->getStorage('entity_test_update')->create(['new_base_field' => 'foo'])->save();
401 // Remove the base field and apply updates. It's expected to throw an
403 // @todo Revisit that expectation once purging is implemented for
404 // all fields: https://www.drupal.org/node/2282119.
405 $this->removeBaseField();
407 $this->entityDefinitionUpdateManager->applyUpdates();
408 $this->fail('FieldStorageDefinitionUpdateForbiddenException thrown when trying to apply an update that deletes a non-purgeable field with data.');
410 catch (FieldStorageDefinitionUpdateForbiddenException $e) {
411 $this->pass('FieldStorageDefinitionUpdateForbiddenException thrown when trying to apply an update that deletes a non-purgeable field with data.');
416 * Tests deleting a bundle field when it has existing data.
418 public function testBundleFieldDeleteWithExistingData() {
419 // Add the bundle field and run the update.
420 $this->addBundleField();
421 $this->entityDefinitionUpdateManager->applyUpdates();
423 // Save an entity with the bundle field populated.
424 entity_test_create_bundle('custom');
425 $this->entityManager->getStorage('entity_test_update')->create(['type' => 'test_bundle', 'new_bundle_field' => 'foo'])->save();
427 // Remove the bundle field and apply updates. It's expected to throw an
429 // @todo Revisit that expectation once purging is implemented for
430 // all fields: https://www.drupal.org/node/2282119.
431 $this->removeBundleField();
433 $this->entityDefinitionUpdateManager->applyUpdates();
434 $this->fail('FieldStorageDefinitionUpdateForbiddenException thrown when trying to apply an update that deletes a non-purgeable field with data.');
436 catch (FieldStorageDefinitionUpdateForbiddenException $e) {
437 $this->pass('FieldStorageDefinitionUpdateForbiddenException thrown when trying to apply an update that deletes a non-purgeable field with data.');
442 * Tests updating a base field when it has existing data.
444 public function testBaseFieldUpdateWithExistingData() {
445 // Add the base field and run the update.
446 $this->addBaseField();
447 $this->entityDefinitionUpdateManager->applyUpdates();
449 // Save an entity with the base field populated.
450 $this->entityManager->getStorage('entity_test_update')->create(['new_base_field' => 'foo'])->save();
452 // Change the field's field type and apply updates. It's expected to
453 // throw an exception.
454 $this->modifyBaseField();
456 $this->entityDefinitionUpdateManager->applyUpdates();
457 $this->fail('FieldStorageDefinitionUpdateForbiddenException thrown when trying to update a field schema that has data.');
459 catch (FieldStorageDefinitionUpdateForbiddenException $e) {
460 $this->pass('FieldStorageDefinitionUpdateForbiddenException thrown when trying to update a field schema that has data.');
465 * Tests updating a bundle field when it has existing data.
467 public function testBundleFieldUpdateWithExistingData() {
468 // Add the bundle field and run the update.
469 $this->addBundleField();
470 $this->entityDefinitionUpdateManager->applyUpdates();
472 // Save an entity with the bundle field populated.
473 entity_test_create_bundle('custom');
474 $this->entityManager->getStorage('entity_test_update')->create(['type' => 'test_bundle', 'new_bundle_field' => 'foo'])->save();
476 // Change the field's field type and apply updates. It's expected to
477 // throw an exception.
478 $this->modifyBundleField();
480 $this->entityDefinitionUpdateManager->applyUpdates();
481 $this->fail('FieldStorageDefinitionUpdateForbiddenException thrown when trying to update a field schema that has data.');
483 catch (FieldStorageDefinitionUpdateForbiddenException $e) {
484 $this->pass('FieldStorageDefinitionUpdateForbiddenException thrown when trying to update a field schema that has data.');
489 * Tests creating and deleting a multi-field index when there are no existing entities.
491 public function testEntityIndexCreateDeleteWithoutData() {
492 // Add an entity index and ensure the update manager reports that as an
493 // update to the entity type.
494 $this->addEntityIndex();
495 $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
497 'entity_test_update' => [
498 t('The %entity_type entity type needs to be updated.', ['%entity_type' => $this->entityManager->getDefinition('entity_test_update')->getLabel()]),
501 $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
503 // Run the update and ensure the new index is created.
504 $this->entityDefinitionUpdateManager->applyUpdates();
505 $this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index created.');
507 // Remove the index and ensure the update manager reports that as an
508 // update to the entity type.
509 $this->removeEntityIndex();
510 $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
512 'entity_test_update' => [
513 t('The %entity_type entity type needs to be updated.', ['%entity_type' => $this->entityManager->getDefinition('entity_test_update')->getLabel()]),
516 $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
518 // Run the update and ensure the index is deleted.
519 $this->entityDefinitionUpdateManager->applyUpdates();
520 $this->assertFalse($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index deleted.');
522 // Test that composite indexes are handled correctly when dropping and
523 // re-creating one of their columns.
524 $this->addEntityIndex();
525 $this->entityDefinitionUpdateManager->applyUpdates();
526 $storage_definition = $this->entityDefinitionUpdateManager->getFieldStorageDefinition('name', 'entity_test_update');
527 $this->entityDefinitionUpdateManager->updateFieldStorageDefinition($storage_definition);
528 $this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index created.');
529 $this->entityDefinitionUpdateManager->uninstallFieldStorageDefinition($storage_definition);
530 $this->assertFalse($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index deleted.');
531 $this->entityDefinitionUpdateManager->installFieldStorageDefinition('name', 'entity_test_update', 'entity_test', $storage_definition);
532 $this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index created again.');
536 * Tests creating a multi-field index when there are existing entities.
538 public function testEntityIndexCreateWithData() {
540 $name = $this->randomString();
541 $entity = $this->entityManager->getStorage('entity_test_update')->create(['name' => $name]);
544 // Add an entity index, run the update. Ensure that the index is created
545 // despite having data.
546 $this->addEntityIndex();
547 $this->entityDefinitionUpdateManager->applyUpdates();
548 $this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index added.');
552 * Tests entity type and field storage definition events.
554 public function testDefinitionEvents() {
555 /** @var \Drupal\entity_test\EntityTestDefinitionSubscriber $event_subscriber */
556 $event_subscriber = $this->container->get('entity_test.definition.subscriber');
557 $event_subscriber->enableEventTracking();
559 // Test field storage definition events.
560 $storage_definition = current($this->entityManager->getFieldStorageDefinitions('entity_test_rev'));
561 $this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::DELETE), 'Entity type delete was not dispatched yet.');
562 $this->entityManager->onFieldStorageDefinitionDelete($storage_definition);
563 $this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::DELETE), 'Entity type delete event successfully dispatched.');
564 $this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::CREATE), 'Entity type create was not dispatched yet.');
565 $this->entityManager->onFieldStorageDefinitionCreate($storage_definition);
566 $this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::CREATE), 'Entity type create event successfully dispatched.');
567 $this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::UPDATE), 'Entity type update was not dispatched yet.');
568 $this->entityManager->onFieldStorageDefinitionUpdate($storage_definition, $storage_definition);
569 $this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::UPDATE), 'Entity type update event successfully dispatched.');
571 // Test entity type events.
572 $entity_type = $this->entityManager->getDefinition('entity_test_rev');
573 $this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvents::CREATE), 'Entity type create was not dispatched yet.');
574 $this->entityManager->onEntityTypeCreate($entity_type);
575 $this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvents::CREATE), 'Entity type create event successfully dispatched.');
576 $this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvents::UPDATE), 'Entity type update was not dispatched yet.');
577 $this->entityManager->onEntityTypeUpdate($entity_type, $entity_type);
578 $this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvents::UPDATE), 'Entity type update event successfully dispatched.');
579 $this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvents::DELETE), 'Entity type delete was not dispatched yet.');
580 $this->entityManager->onEntityTypeDelete($entity_type);
581 $this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvents::DELETE), 'Entity type delete event successfully dispatched.');
585 * Tests updating entity schema and creating a base field.
587 * This tests updating entity schema and creating a base field at the same
588 * time when there are no existing entities.
590 public function testEntityTypeSchemaUpdateAndBaseFieldCreateWithoutData() {
591 $this->updateEntityTypeToRevisionable();
592 $this->addBaseField();
593 $message = 'Successfully updated entity schema and created base field at the same time.';
594 // Entity type updates create base fields as well, thus make sure doing both
595 // at the same time does not lead to errors due to the base field being
598 $this->entityDefinitionUpdateManager->applyUpdates();
599 $this->pass($message);
601 catch (\Exception $e) {
602 $this->fail($message);
608 * Tests updating entity schema and creating a revisionable base field.
610 * This tests updating entity schema and creating a revisionable base field
611 * at the same time when there are no existing entities.
613 public function testEntityTypeSchemaUpdateAndRevisionableBaseFieldCreateWithoutData() {
614 $this->updateEntityTypeToRevisionable();
615 $this->addRevisionableBaseField();
616 $message = 'Successfully updated entity schema and created revisionable base field at the same time.';
617 // Entity type updates create base fields as well, thus make sure doing both
618 // at the same time does not lead to errors due to the base field being
621 $this->entityDefinitionUpdateManager->applyUpdates();
622 $this->pass($message);
624 catch (\Exception $e) {
625 $this->fail($message);
631 * Tests applying single updates.
633 public function testSingleActionCalls() {
634 $db_schema = $this->database->schema();
636 // Ensure that a non-existing entity type cannot be installed.
637 $message = 'A non-existing entity type cannot be installed';
639 $this->entityDefinitionUpdateManager->installEntityType(new ContentEntityType(['id' => 'foo']));
640 $this->fail($message);
642 catch (PluginNotFoundException $e) {
643 $this->pass($message);
646 // Ensure that a field cannot be installed on non-existing entity type.
647 $message = 'A field cannot be installed on a non-existing entity type';
649 $storage_definition = BaseFieldDefinition::create('string')
650 ->setLabel(t('A new revisionable base field'))
651 ->setRevisionable(TRUE);
652 $this->entityDefinitionUpdateManager->installFieldStorageDefinition('bar', 'foo', 'entity_test', $storage_definition);
653 $this->fail($message);
655 catch (PluginNotFoundException $e) {
656 $this->pass($message);
659 // Ensure that a non-existing field cannot be installed.
660 $storage_definition = BaseFieldDefinition::create('string')
661 ->setLabel(t('A new revisionable base field'))
662 ->setRevisionable(TRUE);
663 $this->entityDefinitionUpdateManager->installFieldStorageDefinition('bar', 'entity_test_update', 'entity_test', $storage_definition);
664 $this->assertFalse($db_schema->fieldExists('entity_test_update', 'bar'), "A non-existing field cannot be installed.");
666 // Ensure that installing an existing entity type is a no-op.
667 $entity_type = $this->entityDefinitionUpdateManager->getEntityType('entity_test_update');
668 $this->entityDefinitionUpdateManager->installEntityType($entity_type);
669 $this->assertTrue($db_schema->tableExists('entity_test_update'), 'Installing an existing entity type is a no-op');
671 // Create a new base field.
672 $this->addRevisionableBaseField();
673 $storage_definition = BaseFieldDefinition::create('string')
674 ->setLabel(t('A new revisionable base field'))
675 ->setRevisionable(TRUE);
676 $this->assertFalse($db_schema->fieldExists('entity_test_update', 'new_base_field'), "New field 'new_base_field' does not exist before applying the update.");
677 $this->entityDefinitionUpdateManager->installFieldStorageDefinition('new_base_field', 'entity_test_update', 'entity_test', $storage_definition);
678 $this->assertTrue($db_schema->fieldExists('entity_test_update', 'new_base_field'), "New field 'new_base_field' has been created on the 'entity_test_update' table.");
680 // Ensure that installing an existing field is a no-op.
681 $this->entityDefinitionUpdateManager->installFieldStorageDefinition('new_base_field', 'entity_test_update', 'entity_test', $storage_definition);
682 $this->assertTrue($db_schema->fieldExists('entity_test_update', 'new_base_field'), 'Installing an existing field is a no-op');
684 // Update an existing field schema.
685 $this->modifyBaseField();
686 $storage_definition = BaseFieldDefinition::create('text')
687 ->setName('new_base_field')
688 ->setTargetEntityTypeId('entity_test_update')
689 ->setLabel(t('A new revisionable base field'))
690 ->setRevisionable(TRUE);
691 $this->entityDefinitionUpdateManager->updateFieldStorageDefinition($storage_definition);
692 $this->assertFalse($db_schema->fieldExists('entity_test_update', 'new_base_field'), "Previous schema for 'new_base_field' no longer exists.");
694 $db_schema->fieldExists('entity_test_update', 'new_base_field__value') && $db_schema->fieldExists('entity_test_update', 'new_base_field__format'),
695 "New schema for 'new_base_field' has been created."
698 // Drop an existing field schema.
699 $this->entityDefinitionUpdateManager->uninstallFieldStorageDefinition($storage_definition);
701 $db_schema->fieldExists('entity_test_update', 'new_base_field__value') || $db_schema->fieldExists('entity_test_update', 'new_base_field__format'),
702 "The schema for 'new_base_field' has been dropped."
705 // Make the entity type revisionable.
706 $this->updateEntityTypeToRevisionable();
707 $this->assertFalse($db_schema->tableExists('entity_test_update_revision'), "The 'entity_test_update_revision' does not exist before applying the update.");
708 $entity_type = $this->entityDefinitionUpdateManager->getEntityType('entity_test_update');
709 $keys = $entity_type->getKeys();
710 $keys['revision'] = 'revision_id';
711 $entity_type->set('entity_keys', $keys);
712 $this->entityDefinitionUpdateManager->updateEntityType($entity_type);
713 $this->assertTrue($db_schema->tableExists('entity_test_update_revision'), "The 'entity_test_update_revision' table has been created.");
717 * Ensures that a new field and index on a shared table are created.
719 * @see Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::createSharedTableSchema
721 public function testCreateFieldAndIndexOnSharedTable() {
722 $this->addBaseField();
723 $this->addBaseFieldIndex();
724 $this->entityDefinitionUpdateManager->applyUpdates();
725 $this->assertTrue($this->database->schema()->fieldExists('entity_test_update', 'new_base_field'), "New field 'new_base_field' has been created on the 'entity_test_update' table.");
726 $this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update_field__new_base_field'), "New index 'entity_test_update_field__new_base_field' has been created on the 'entity_test_update' table.");
727 // Check index size in for MySQL.
728 if (Database::getConnection()->driver() == 'mysql') {
729 $result = Database::getConnection()->query('SHOW INDEX FROM {entity_test_update} WHERE key_name = \'entity_test_update_field__new_base_field\' and column_name = \'new_base_field\'')->fetchObject();
730 $this->assertEqual(191, $result->Sub_part, 'The index length has been restricted to 191 characters for UTF8MB4 compatibility.');
735 * Ensures that a new entity level index is created when data exists.
737 * @see Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::onEntityTypeUpdate
739 public function testCreateIndexUsingEntityStorageSchemaWithData() {
741 $name = $this->randomString();
742 $storage = $this->entityManager->getStorage('entity_test_update');
743 $entity = $storage->create(['name' => $name]);
748 'entity_test_update__type_index' => ['type'],
750 $this->state->set('entity_test_update.additional_entity_indexes', $indexes);
751 $this->entityDefinitionUpdateManager->applyUpdates();
752 $this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__type_index'), "New index 'entity_test_update__type_index' has been created on the 'entity_test_update' table.");
753 // Check index size in for MySQL.
754 if (Database::getConnection()->driver() == 'mysql') {
755 $result = Database::getConnection()->query('SHOW INDEX FROM {entity_test_update} WHERE key_name = \'entity_test_update__type_index\' and column_name = \'type\'')->fetchObject();
756 $this->assertEqual(191, $result->Sub_part, 'The index length has been restricted to 191 characters for UTF8MB4 compatibility.');
761 * Tests updating a base field when it has existing data.
763 public function testBaseFieldEntityKeyUpdateWithExistingData() {
764 // Add the base field and run the update.
765 $this->addBaseField();
766 $this->entityDefinitionUpdateManager->applyUpdates();
768 // Save an entity with the base field populated.
769 $this->entityManager->getStorage('entity_test_update')->create(['new_base_field' => $this->randomString()])->save();
771 // Save an entity with the base field not populated.
772 /** @var \Drupal\entity_test\Entity\EntityTestUpdate $entity */
773 $entity = $this->entityManager->getStorage('entity_test_update')->create();
776 // Promote the base field to an entity key. This will trigger the addition
777 // of a NOT NULL constraint.
778 $this->makeBaseFieldEntityKey();
780 // Field storage CRUD operations use the last installed entity type
781 // definition so we need to update it before doing any other field storage
783 $this->entityDefinitionUpdateManager->updateEntityType($this->state->get('entity_test_update.entity_type'));
785 // Try to apply the update and verify they fail since we have a NULL value.
786 $message = 'An error occurs when trying to enabling NOT NULL constraints with NULL data.';
788 $this->entityDefinitionUpdateManager->applyUpdates();
789 $this->fail($message);
791 catch (EntityStorageException $e) {
792 $this->pass($message);
795 // Check that the update is correctly applied when no NULL data is left.
796 $entity->set('new_base_field', $this->randomString());
798 $this->entityDefinitionUpdateManager->applyUpdates();
799 $this->pass('The update is correctly performed when no NULL data exists.');
801 // Check that the update actually applied a NOT NULL constraint.
802 $entity->set('new_base_field', NULL);
803 $message = 'The NOT NULL constraint was correctly applied.';
806 $this->fail($message);
808 catch (EntityStorageException $e) {
809 $this->pass($message);
814 * Check that field schema is correctly handled with long-named fields.
816 public function testLongNameFieldIndexes() {
817 $this->addLongNameBaseField();
818 $entity_type_id = 'entity_test_update';
819 $entity_type = $this->entityManager->getDefinition($entity_type_id);
820 $definitions = EntityTestUpdate::baseFieldDefinitions($entity_type);
821 $name = 'new_long_named_entity_reference_base_field';
822 $this->entityDefinitionUpdateManager->installFieldStorageDefinition($name, $entity_type_id, 'entity_test', $definitions[$name]);
823 $this->assertFalse($this->entityDefinitionUpdateManager->needsUpdates(), 'Entity and field schema data are correctly detected.');
827 * Tests adding a base field with initial values.
829 public function testInitialValue() {
830 $storage = \Drupal::entityTypeManager()->getStorage('entity_test_update');
831 $db_schema = $this->database->schema();
833 // Create two entities before adding the base field.
834 /** @var \Drupal\entity_test\Entity\EntityTestUpdate $entity */
835 $storage->create()->save();
836 $storage->create()->save();
838 // Add a base field with an initial value.
839 $this->addBaseField();
840 $storage_definition = BaseFieldDefinition::create('string')
841 ->setLabel(t('A new base field'))
842 ->setInitialValue('test value');
844 $this->assertFalse($db_schema->fieldExists('entity_test_update', 'new_base_field'), "New field 'new_base_field' does not exist before applying the update.");
845 $this->entityDefinitionUpdateManager->installFieldStorageDefinition('new_base_field', 'entity_test_update', 'entity_test', $storage_definition);
846 $this->assertTrue($db_schema->fieldExists('entity_test_update', 'new_base_field'), "New field 'new_base_field' has been created on the 'entity_test_update' table.");
848 // Check that the initial values have been applied.
849 $storage = \Drupal::entityTypeManager()->getStorage('entity_test_update');
850 $entities = $storage->loadMultiple();
851 $this->assertEquals('test value', $entities[1]->get('new_base_field')->value);
852 $this->assertEquals('test value', $entities[2]->get('new_base_field')->value);
856 * Tests adding a base field with initial values inherited from another field.
858 public function testInitialValueFromField() {
859 $storage = \Drupal::entityTypeManager()->getStorage('entity_test_update');
860 $db_schema = $this->database->schema();
862 // Create two entities before adding the base field.
863 /** @var \Drupal\entity_test\Entity\EntityTestUpdate $entity */
864 $storage->create(['name' => 'First entity'])->save();
865 $storage->create(['name' => 'Second entity'])->save();
867 // Add a base field with an initial value inherited from another field.
868 $this->addBaseField();
869 $storage_definition = BaseFieldDefinition::create('string')
870 ->setLabel(t('A new base field'))
871 ->setInitialValueFromField('name');
873 $this->assertFalse($db_schema->fieldExists('entity_test_update', 'new_base_field'), "New field 'new_base_field' does not exist before applying the update.");
874 $this->entityDefinitionUpdateManager->installFieldStorageDefinition('new_base_field', 'entity_test_update', 'entity_test', $storage_definition);
875 $this->assertTrue($db_schema->fieldExists('entity_test_update', 'new_base_field'), "New field 'new_base_field' has been created on the 'entity_test_update' table.");
877 // Check that the initial values have been applied.
878 $storage = \Drupal::entityTypeManager()->getStorage('entity_test_update');
879 $entities = $storage->loadMultiple();
880 $this->assertEquals('First entity', $entities[1]->get('new_base_field')->value);
881 $this->assertEquals('Second entity', $entities[2]->get('new_base_field')->value);
885 * Tests the error handling when using initial values from another field.
887 public function testInitialValueFromFieldErrorHandling() {
888 // Check that setting invalid values for 'initial value from field' doesn't
891 $this->addBaseField();
892 $storage_definition = BaseFieldDefinition::create('string')
893 ->setLabel(t('A new base field'))
894 ->setInitialValueFromField('field_that_does_not_exist');
895 $this->entityDefinitionUpdateManager->installFieldStorageDefinition('new_base_field', 'entity_test_update', 'entity_test', $storage_definition);
896 $this->fail('Using a non-existent field as initial value does not work.');
898 catch (FieldException $e) {
899 $this->assertEquals('Illegal initial value definition on new_base_field: The field field_that_does_not_exist does not exist.', $e->getMessage());
900 $this->pass('Using a non-existent field as initial value does not work.');
904 $this->addBaseField();
905 $storage_definition = BaseFieldDefinition::create('integer')
906 ->setLabel(t('A new base field'))
907 ->setInitialValueFromField('name');
908 $this->entityDefinitionUpdateManager->installFieldStorageDefinition('new_base_field', 'entity_test_update', 'entity_test', $storage_definition);
909 $this->fail('Using a field of a different type as initial value does not work.');
911 catch (FieldException $e) {
912 $this->assertEquals('Illegal initial value definition on new_base_field: The field types do not match.', $e->getMessage());
913 $this->pass('Using a field of a different type as initial value does not work.');
917 // Add a base field that will not be stored in the shared tables.
918 $initial_field = BaseFieldDefinition::create('string')
919 ->setName('initial_field')
920 ->setLabel(t('An initial field'))
922 $this->state->set('entity_test_update.additional_base_field_definitions', ['initial_field' => $initial_field]);
923 $this->entityDefinitionUpdateManager->installFieldStorageDefinition('initial_field', 'entity_test_update', 'entity_test', $initial_field);
925 // Now add the base field which will try to use the previously added field
926 // as the source of its initial values.
927 $new_base_field = BaseFieldDefinition::create('string')
928 ->setName('new_base_field')
929 ->setLabel(t('A new base field'))
930 ->setInitialValueFromField('initial_field');
931 $this->state->set('entity_test_update.additional_base_field_definitions', ['initial_field' => $initial_field, 'new_base_field' => $new_base_field]);
932 $this->entityDefinitionUpdateManager->installFieldStorageDefinition('new_base_field', 'entity_test_update', 'entity_test', $new_base_field);
933 $this->fail('Using a field that is not stored in the shared tables as initial value does not work.');
935 catch (FieldException $e) {
936 $this->assertEquals('Illegal initial value definition on new_base_field: Both fields have to be stored in the shared entity tables.', $e->getMessage());
937 $this->pass('Using a field that is not stored in the shared tables as initial value does not work.');