1. 8.3.x core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php
  2. 8.0.x core/modules/system/src/Tests/Entity/EntityDefinitionUpdateTest.php
  3. 8.1.x core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php
  4. 8.2.x core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php
  5. 8.4.x core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php

Namespace

Drupal\KernelTests\Core\Entity

File

core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php
View source
  1. <?php
  2. namespace Drupal\KernelTests\Core\Entity;
  3. use Drupal\Component\Plugin\Exception\PluginNotFoundException;
  4. use Drupal\Core\Database\Database;
  5. use Drupal\Core\Database\DatabaseExceptionWrapper;
  6. use Drupal\Core\Database\IntegrityConstraintViolationException;
  7. use Drupal\Core\Entity\ContentEntityType;
  8. use Drupal\Core\Entity\EntityStorageException;
  9. use Drupal\Core\Entity\EntityTypeEvents;
  10. use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
  11. use Drupal\Core\Field\BaseFieldDefinition;
  12. use Drupal\Core\Field\FieldStorageDefinitionEvents;
  13. use Drupal\Core\Language\LanguageInterface;
  14. use Drupal\entity_test\Entity\EntityTestUpdate;
  15. use Drupal\system\Tests\Entity\EntityDefinitionTestTrait;
  16. /**
  17. * Tests EntityDefinitionUpdateManager functionality.
  18. *
  19. * @group Entity
  20. */
  21. class EntityDefinitionUpdateTest extends EntityKernelTestBase {
  22. use EntityDefinitionTestTrait;
  23. /**
  24. * The entity definition update manager.
  25. *
  26. * @var \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface
  27. */
  28. protected $entityDefinitionUpdateManager;
  29. /**
  30. * The database connection.
  31. *
  32. * @var \Drupal\Core\Database\Connection
  33. */
  34. protected $database;
  35. /**
  36. * {@inheritdoc}
  37. */
  38. protected function setUp() {
  39. parent::setUp();
  40. $this->entityDefinitionUpdateManager = $this->container->get('entity.definition_update_manager');
  41. $this->database = $this->container->get('database');
  42. // Install every entity type's schema that wasn't installed in the parent
  43. // method.
  44. foreach (array_diff_key($this->entityManager->getDefinitions(), array_flip(array('user', 'entity_test'))) as $entity_type_id => $entity_type) {
  45. $this->installEntitySchema($entity_type_id);
  46. }
  47. }
  48. /**
  49. * Tests that new entity type definitions are correctly handled.
  50. */
  51. public function testNewEntityType() {
  52. $entity_type_id = 'entity_test_new';
  53. $schema = $this->database->schema();
  54. // Check that the "entity_test_new" is not defined.
  55. $entity_types = $this->entityManager->getDefinitions();
  56. $this->assertFalse(isset($entity_types[$entity_type_id]), 'The "entity_test_new" entity type does not exist.');
  57. $this->assertFalse($schema->tableExists($entity_type_id), 'Schema for the "entity_test_new" entity type does not exist.');
  58. // Check that the "entity_test_new" is now defined and the related schema
  59. // has been created.
  60. $this->enableNewEntityType();
  61. $entity_types = $this->entityManager->getDefinitions();
  62. $this->assertTrue(isset($entity_types[$entity_type_id]), 'The "entity_test_new" entity type exists.');
  63. $this->assertTrue($schema->tableExists($entity_type_id), 'Schema for the "entity_test_new" entity type has been created.');
  64. }
  65. /**
  66. * Tests when no definition update is needed.
  67. */
  68. public function testNoUpdates() {
  69. // Ensure that the definition update manager reports no updates.
  70. $this->assertFalse($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that no updates are needed.');
  71. $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), array(), 'EntityDefinitionUpdateManager reports an empty change summary.');
  72. // Ensure that applyUpdates() runs without error (it's not expected to do
  73. // anything when there aren't updates).
  74. $this->entityDefinitionUpdateManager->applyUpdates();
  75. }
  76. /**
  77. * Tests updating entity schema when there are no existing entities.
  78. */
  79. public function testEntityTypeUpdateWithoutData() {
  80. // The 'entity_test_update' entity type starts out non-revisionable, so
  81. // ensure the revision table hasn't been created during setUp().
  82. $this->assertFalse($this->database->schema()->tableExists('entity_test_update_revision'), 'Revision table not created for entity_test_update.');
  83. // Update it to be revisionable and ensure the definition update manager
  84. // reports that an update is needed.
  85. $this->updateEntityTypeToRevisionable();
  86. $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
  87. $expected = array(
  88. 'entity_test_update' => array(
  89. t('The %entity_type entity type needs to be updated.', ['%entity_type' => $this->entityManager->getDefinition('entity_test_update')->getLabel()]),
  90. ),
  91. );
  92. $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected); //, 'EntityDefinitionUpdateManager reports the expected change summary.');
  93. // Run the update and ensure the revision table is created.
  94. $this->entityDefinitionUpdateManager->applyUpdates();
  95. $this->assertTrue($this->database->schema()->tableExists('entity_test_update_revision'), 'Revision table created for entity_test_update.');
  96. }
  97. /**
  98. * Tests updating entity schema when there are existing entities.
  99. */
  100. public function testEntityTypeUpdateWithData() {
  101. // Save an entity.
  102. $this->entityManager->getStorage('entity_test_update')->create()->save();
  103. // Update the entity type to be revisionable and try to apply the update.
  104. // It's expected to throw an exception.
  105. $this->updateEntityTypeToRevisionable();
  106. try {
  107. $this->entityDefinitionUpdateManager->applyUpdates();
  108. $this->fail('EntityStorageException thrown when trying to apply an update that requires data migration.');
  109. }
  110. catch (EntityStorageException $e) {
  111. $this->pass('EntityStorageException thrown when trying to apply an update that requires data migration.');
  112. }
  113. }
  114. /**
  115. * Tests creating, updating, and deleting a base field if no entities exist.
  116. */
  117. public function testBaseFieldCreateUpdateDeleteWithoutData() {
  118. // Add a base field, ensure the update manager reports it, and the update
  119. // creates its schema.
  120. $this->addBaseField();
  121. $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
  122. $expected = array(
  123. 'entity_test_update' => array(
  124. t('The %field_name field needs to be installed.', ['%field_name' => t('A new base field')]),
  125. ),
  126. );
  127. $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
  128. $this->entityDefinitionUpdateManager->applyUpdates();
  129. $this->assertTrue($this->database->schema()->fieldExists('entity_test_update', 'new_base_field'), 'Column created in shared table for new_base_field.');
  130. // Add an index on the base field, ensure the update manager reports it,
  131. // and the update creates it.
  132. $this->addBaseFieldIndex();
  133. $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
  134. $expected = array(
  135. 'entity_test_update' => array(
  136. t('The %field_name field needs to be updated.', ['%field_name' => t('A new base field')]),
  137. ),
  138. );
  139. $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
  140. $this->entityDefinitionUpdateManager->applyUpdates();
  141. $this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update_field__new_base_field'), 'Index created.');
  142. // Remove the above index, ensure the update manager reports it, and the
  143. // update deletes it.
  144. $this->removeBaseFieldIndex();
  145. $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
  146. $expected = array(
  147. 'entity_test_update' => array(
  148. t('The %field_name field needs to be updated.', ['%field_name' => t('A new base field')]),
  149. ),
  150. );
  151. $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
  152. $this->entityDefinitionUpdateManager->applyUpdates();
  153. $this->assertFalse($this->database->schema()->indexExists('entity_test_update', 'entity_test_update_field__new_base_field'), 'Index deleted.');
  154. // Update the type of the base field from 'string' to 'text', ensure the
  155. // update manager reports it, and the update adjusts the schema
  156. // accordingly.
  157. $this->modifyBaseField();
  158. $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
  159. $expected = array(
  160. 'entity_test_update' => array(
  161. t('The %field_name field needs to be updated.', ['%field_name' => t('A new base field')]),
  162. ),
  163. );
  164. $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
  165. $this->entityDefinitionUpdateManager->applyUpdates();
  166. $this->assertFalse($this->database->schema()->fieldExists('entity_test_update', 'new_base_field'), 'Original column deleted in shared table for new_base_field.');
  167. $this->assertTrue($this->database->schema()->fieldExists('entity_test_update', 'new_base_field__value'), 'Value column created in shared table for new_base_field.');
  168. $this->assertTrue($this->database->schema()->fieldExists('entity_test_update', 'new_base_field__format'), 'Format column created in shared table for new_base_field.');
  169. // Remove the base field, ensure the update manager reports it, and the
  170. // update deletes the schema.
  171. $this->removeBaseField();
  172. $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
  173. $expected = array(
  174. 'entity_test_update' => array(
  175. t('The %field_name field needs to be uninstalled.', ['%field_name' => t('A new base field')]),
  176. ),
  177. );
  178. $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
  179. $this->entityDefinitionUpdateManager->applyUpdates();
  180. $this->assertFalse($this->database->schema()->fieldExists('entity_test_update', 'new_base_field_value'), 'Value column deleted from shared table for new_base_field.');
  181. $this->assertFalse($this->database->schema()->fieldExists('entity_test_update', 'new_base_field_format'), 'Format column deleted from shared table for new_base_field.');
  182. }
  183. /**
  184. * Tests creating, updating, and deleting a bundle field if no entities exist.
  185. */
  186. public function testBundleFieldCreateUpdateDeleteWithoutData() {
  187. // Add a bundle field, ensure the update manager reports it, and the update
  188. // creates its schema.
  189. $this->addBundleField();
  190. $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
  191. $expected = array(
  192. 'entity_test_update' => array(
  193. t('The %field_name field needs to be installed.', ['%field_name' => t('A new bundle field')]),
  194. ),
  195. );
  196. $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
  197. $this->entityDefinitionUpdateManager->applyUpdates();
  198. $this->assertTrue($this->database->schema()->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table created for new_bundle_field.');
  199. // Update the type of the base field from 'string' to 'text', ensure the
  200. // update manager reports it, and the update adjusts the schema
  201. // accordingly.
  202. $this->modifyBundleField();
  203. $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
  204. $expected = array(
  205. 'entity_test_update' => [
  206. t('The %field_name field needs to be updated.', ['%field_name' => t('A new bundle field')]),
  207. ],
  208. );
  209. $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
  210. $this->entityDefinitionUpdateManager->applyUpdates();
  211. $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.');
  212. // Remove the bundle field, ensure the update manager reports it, and the
  213. // update deletes the schema.
  214. $this->removeBundleField();
  215. $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
  216. $expected = array(
  217. 'entity_test_update' => array(
  218. t('The %field_name field needs to be uninstalled.', ['%field_name' => t('A new bundle field')]),
  219. ),
  220. );
  221. $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
  222. $this->entityDefinitionUpdateManager->applyUpdates();
  223. $this->assertFalse($this->database->schema()->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table deleted for new_bundle_field.');
  224. }
  225. /**
  226. * Tests creating and deleting a base field if entities exist.
  227. *
  228. * This tests deletion when there are existing entities, but not existing data
  229. * for the field being deleted.
  230. *
  231. * @see testBaseFieldDeleteWithExistingData()
  232. */
  233. public function testBaseFieldCreateDeleteWithExistingEntities() {
  234. // Save an entity.
  235. $name = $this->randomString();
  236. $storage = $this->entityManager->getStorage('entity_test_update');
  237. $entity = $storage->create(array('name' => $name));
  238. $entity->save();
  239. // Add a base field and run the update. Ensure the base field's column is
  240. // created and the prior saved entity data is still there.
  241. $this->addBaseField();
  242. $this->entityDefinitionUpdateManager->applyUpdates();
  243. $schema_handler = $this->database->schema();
  244. $this->assertTrue($schema_handler->fieldExists('entity_test_update', 'new_base_field'), 'Column created in shared table for new_base_field.');
  245. $entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id());
  246. $this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field creation.');
  247. // Remove the base field and run the update. Ensure the base field's column
  248. // is deleted and the prior saved entity data is still there.
  249. $this->removeBaseField();
  250. $this->entityDefinitionUpdateManager->applyUpdates();
  251. $this->assertFalse($schema_handler->fieldExists('entity_test_update', 'new_base_field'), 'Column deleted from shared table for new_base_field.');
  252. $entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id());
  253. $this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field deletion.');
  254. // Add a base field with a required property and run the update. Ensure
  255. // 'not null' is not applied and thus no exception is thrown.
  256. $this->addBaseField('shape_required');
  257. $this->entityDefinitionUpdateManager->applyUpdates();
  258. $assert = $schema_handler->fieldExists('entity_test_update', 'new_base_field__shape') && $schema_handler->fieldExists('entity_test_update', 'new_base_field__color');
  259. $this->assertTrue($assert, 'Columns created in shared table for new_base_field.');
  260. // Recreate the field after emptying the base table and check that its
  261. // columns are not 'not null'.
  262. // @todo Revisit this test when allowing for required storage field
  263. // definitions. See https://www.drupal.org/node/2390495.
  264. $entity->delete();
  265. $this->removeBaseField();
  266. $this->entityDefinitionUpdateManager->applyUpdates();
  267. $assert = !$schema_handler->fieldExists('entity_test_update', 'new_base_field__shape') && !$schema_handler->fieldExists('entity_test_update', 'new_base_field__color');
  268. $this->assert($assert, 'Columns removed from the shared table for new_base_field.');
  269. $this->addBaseField('shape_required');
  270. $this->entityDefinitionUpdateManager->applyUpdates();
  271. $assert = $schema_handler->fieldExists('entity_test_update', 'new_base_field__shape') && $schema_handler->fieldExists('entity_test_update', 'new_base_field__color');
  272. $this->assertTrue($assert, 'Columns created again in shared table for new_base_field.');
  273. $entity = $storage->create(array('name' => $name));
  274. $entity->save();
  275. $this->pass('The new_base_field columns are still nullable');
  276. }
  277. /**
  278. * Tests creating and deleting a bundle field if entities exist.
  279. *
  280. * This tests deletion when there are existing entities, but not existing data
  281. * for the field being deleted.
  282. *
  283. * @see testBundleFieldDeleteWithExistingData()
  284. */
  285. public function testBundleFieldCreateDeleteWithExistingEntities() {
  286. // Save an entity.
  287. $name = $this->randomString();
  288. $storage = $this->entityManager->getStorage('entity_test_update');
  289. $entity = $storage->create(array('name' => $name));
  290. $entity->save();
  291. // Add a bundle field and run the update. Ensure the bundle field's table
  292. // is created and the prior saved entity data is still there.
  293. $this->addBundleField();
  294. $this->entityDefinitionUpdateManager->applyUpdates();
  295. $schema_handler = $this->database->schema();
  296. $this->assertTrue($schema_handler->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table created for new_bundle_field.');
  297. $entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id());
  298. $this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field creation.');
  299. // Remove the base field and run the update. Ensure the bundle field's
  300. // table is deleted and the prior saved entity data is still there.
  301. $this->removeBundleField();
  302. $this->entityDefinitionUpdateManager->applyUpdates();
  303. $this->assertFalse($schema_handler->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table deleted for new_bundle_field.');
  304. $entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id());
  305. $this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field deletion.');
  306. // Test that required columns are created as 'not null'.
  307. $this->addBundleField('shape_required');
  308. $this->entityDefinitionUpdateManager->applyUpdates();
  309. $message = 'The new_bundle_field_shape column is not nullable.';
  310. $values = array(
  311. 'bundle' => $entity->bundle(),
  312. 'deleted' => 0,
  313. 'entity_id' => $entity->id(),
  314. 'revision_id' => $entity->id(),
  315. 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
  316. 'delta' => 0,
  317. 'new_bundle_field_color' => $this->randomString(),
  318. );
  319. try {
  320. // Try to insert a record without providing a value for the 'not null'
  321. // column. This should fail.
  322. $this->database->insert('entity_test_update__new_bundle_field')
  323. ->fields($values)
  324. ->execute();
  325. $this->fail($message);
  326. }
  327. catch (\RuntimeException $e) {
  328. if ($e instanceof DatabaseExceptionWrapper || $e instanceof IntegrityConstraintViolationException) {
  329. // Now provide a value for the 'not null' column. This is expected to
  330. // succeed.
  331. $values['new_bundle_field_shape'] = $this->randomString();
  332. $this->database->insert('entity_test_update__new_bundle_field')
  333. ->fields($values)
  334. ->execute();
  335. $this->pass($message);
  336. }
  337. else {
  338. // Keep throwing it.
  339. throw $e;
  340. }
  341. }
  342. }
  343. /**
  344. * Tests deleting a base field when it has existing data.
  345. */
  346. public function testBaseFieldDeleteWithExistingData() {
  347. // Add the base field and run the update.
  348. $this->addBaseField();
  349. $this->entityDefinitionUpdateManager->applyUpdates();
  350. // Save an entity with the base field populated.
  351. $this->entityManager->getStorage('entity_test_update')->create(array('new_base_field' => 'foo'))->save();
  352. // Remove the base field and apply updates. It's expected to throw an
  353. // exception.
  354. // @todo Revisit that expectation once purging is implemented for
  355. // all fields: https://www.drupal.org/node/2282119.
  356. $this->removeBaseField();
  357. try {
  358. $this->entityDefinitionUpdateManager->applyUpdates();
  359. $this->fail('FieldStorageDefinitionUpdateForbiddenException thrown when trying to apply an update that deletes a non-purgeable field with data.');
  360. }
  361. catch (FieldStorageDefinitionUpdateForbiddenException $e) {
  362. $this->pass('FieldStorageDefinitionUpdateForbiddenException thrown when trying to apply an update that deletes a non-purgeable field with data.');
  363. }
  364. }
  365. /**
  366. * Tests deleting a bundle field when it has existing data.
  367. */
  368. public function testBundleFieldDeleteWithExistingData() {
  369. // Add the bundle field and run the update.
  370. $this->addBundleField();
  371. $this->entityDefinitionUpdateManager->applyUpdates();
  372. // Save an entity with the bundle field populated.
  373. entity_test_create_bundle('custom');
  374. $this->entityManager->getStorage('entity_test_update')->create(array('type' => 'test_bundle', 'new_bundle_field' => 'foo'))->save();
  375. // Remove the bundle field and apply updates. It's expected to throw an
  376. // exception.
  377. // @todo Revisit that expectation once purging is implemented for
  378. // all fields: https://www.drupal.org/node/2282119.
  379. $this->removeBundleField();
  380. try {
  381. $this->entityDefinitionUpdateManager->applyUpdates();
  382. $this->fail('FieldStorageDefinitionUpdateForbiddenException thrown when trying to apply an update that deletes a non-purgeable field with data.');
  383. }
  384. catch (FieldStorageDefinitionUpdateForbiddenException $e) {
  385. $this->pass('FieldStorageDefinitionUpdateForbiddenException thrown when trying to apply an update that deletes a non-purgeable field with data.');
  386. }
  387. }
  388. /**
  389. * Tests updating a base field when it has existing data.
  390. */
  391. public function testBaseFieldUpdateWithExistingData() {
  392. // Add the base field and run the update.
  393. $this->addBaseField();
  394. $this->entityDefinitionUpdateManager->applyUpdates();
  395. // Save an entity with the base field populated.
  396. $this->entityManager->getStorage('entity_test_update')->create(array('new_base_field' => 'foo'))->save();
  397. // Change the field's field type and apply updates. It's expected to
  398. // throw an exception.
  399. $this->modifyBaseField();
  400. try {
  401. $this->entityDefinitionUpdateManager->applyUpdates();
  402. $this->fail('FieldStorageDefinitionUpdateForbiddenException thrown when trying to update a field schema that has data.');
  403. }
  404. catch (FieldStorageDefinitionUpdateForbiddenException $e) {
  405. $this->pass('FieldStorageDefinitionUpdateForbiddenException thrown when trying to update a field schema that has data.');
  406. }
  407. }
  408. /**
  409. * Tests updating a bundle field when it has existing data.
  410. */
  411. public function testBundleFieldUpdateWithExistingData() {
  412. // Add the bundle field and run the update.
  413. $this->addBundleField();
  414. $this->entityDefinitionUpdateManager->applyUpdates();
  415. // Save an entity with the bundle field populated.
  416. entity_test_create_bundle('custom');
  417. $this->entityManager->getStorage('entity_test_update')->create(array('type' => 'test_bundle', 'new_bundle_field' => 'foo'))->save();
  418. // Change the field's field type and apply updates. It's expected to
  419. // throw an exception.
  420. $this->modifyBundleField();
  421. try {
  422. $this->entityDefinitionUpdateManager->applyUpdates();
  423. $this->fail('FieldStorageDefinitionUpdateForbiddenException thrown when trying to update a field schema that has data.');
  424. }
  425. catch (FieldStorageDefinitionUpdateForbiddenException $e) {
  426. $this->pass('FieldStorageDefinitionUpdateForbiddenException thrown when trying to update a field schema that has data.');
  427. }
  428. }
  429. /**
  430. * Tests creating and deleting a multi-field index when there are no existing entities.
  431. */
  432. public function testEntityIndexCreateDeleteWithoutData() {
  433. // Add an entity index and ensure the update manager reports that as an
  434. // update to the entity type.
  435. $this->addEntityIndex();
  436. $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
  437. $expected = array(
  438. 'entity_test_update' => array(
  439. t('The %entity_type entity type needs to be updated.', ['%entity_type' => $this->entityManager->getDefinition('entity_test_update')->getLabel()]),
  440. ),
  441. );
  442. $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
  443. // Run the update and ensure the new index is created.
  444. $this->entityDefinitionUpdateManager->applyUpdates();
  445. $this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index created.');
  446. // Remove the index and ensure the update manager reports that as an
  447. // update to the entity type.
  448. $this->removeEntityIndex();
  449. $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
  450. $expected = array(
  451. 'entity_test_update' => array(
  452. t('The %entity_type entity type needs to be updated.', ['%entity_type' => $this->entityManager->getDefinition('entity_test_update')->getLabel()]),
  453. ),
  454. );
  455. $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
  456. // Run the update and ensure the index is deleted.
  457. $this->entityDefinitionUpdateManager->applyUpdates();
  458. $this->assertFalse($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index deleted.');
  459. // Test that composite indexes are handled correctly when dropping and
  460. // re-creating one of their columns.
  461. $this->addEntityIndex();
  462. $this->entityDefinitionUpdateManager->applyUpdates();
  463. $storage_definition = $this->entityDefinitionUpdateManager->getFieldStorageDefinition('name', 'entity_test_update');
  464. $this->entityDefinitionUpdateManager->updateFieldStorageDefinition($storage_definition);
  465. $this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index created.');
  466. $this->entityDefinitionUpdateManager->uninstallFieldStorageDefinition($storage_definition);
  467. $this->assertFalse($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index deleted.');
  468. $this->entityDefinitionUpdateManager->installFieldStorageDefinition('name', 'entity_test_update', 'entity_test', $storage_definition);
  469. $this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index created again.');
  470. }
  471. /**
  472. * Tests creating a multi-field index when there are existing entities.
  473. */
  474. public function testEntityIndexCreateWithData() {
  475. // Save an entity.
  476. $name = $this->randomString();
  477. $entity = $this->entityManager->getStorage('entity_test_update')->create(array('name' => $name));
  478. $entity->save();
  479. // Add an entity index, run the update. Ensure that the index is created
  480. // despite having data.
  481. $this->addEntityIndex();
  482. $this->entityDefinitionUpdateManager->applyUpdates();
  483. $this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index added.');
  484. }
  485. /**
  486. * Tests entity type and field storage definition events.
  487. */
  488. public function testDefinitionEvents() {
  489. /** @var \Drupal\entity_test\EntityTestDefinitionSubscriber $event_subscriber */
  490. $event_subscriber = $this->container->get('entity_test.definition.subscriber');
  491. $event_subscriber->enableEventTracking();
  492. // Test field storage definition events.
  493. $storage_definition = current($this->entityManager->getFieldStorageDefinitions('entity_test_rev'));
  494. $this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::DELETE), 'Entity type delete was not dispatched yet.');
  495. $this->entityManager->onFieldStorageDefinitionDelete($storage_definition);
  496. $this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::DELETE), 'Entity type delete event successfully dispatched.');
  497. $this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::CREATE), 'Entity type create was not dispatched yet.');
  498. $this->entityManager->onFieldStorageDefinitionCreate($storage_definition);
  499. $this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::CREATE), 'Entity type create event successfully dispatched.');
  500. $this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::UPDATE), 'Entity type update was not dispatched yet.');
  501. $this->entityManager->onFieldStorageDefinitionUpdate($storage_definition, $storage_definition);
  502. $this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::UPDATE), 'Entity type update event successfully dispatched.');
  503. // Test entity type events.
  504. $entity_type = $this->entityManager->getDefinition('entity_test_rev');
  505. $this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvents::CREATE), 'Entity type create was not dispatched yet.');
  506. $this->entityManager->onEntityTypeCreate($entity_type);
  507. $this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvents::CREATE), 'Entity type create event successfully dispatched.');
  508. $this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvents::UPDATE), 'Entity type update was not dispatched yet.');
  509. $this->entityManager->onEntityTypeUpdate($entity_type, $entity_type);
  510. $this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvents::UPDATE), 'Entity type update event successfully dispatched.');
  511. $this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvents::DELETE), 'Entity type delete was not dispatched yet.');
  512. $this->entityManager->onEntityTypeDelete($entity_type);
  513. $this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvents::DELETE), 'Entity type delete event successfully dispatched.');
  514. }
  515. /**
  516. * Tests updating entity schema and creating a base field.
  517. *
  518. * This tests updating entity schema and creating a base field at the same
  519. * time when there are no existing entities.
  520. */
  521. public function testEntityTypeSchemaUpdateAndBaseFieldCreateWithoutData() {
  522. $this->updateEntityTypeToRevisionable();
  523. $this->addBaseField();
  524. $message = 'Successfully updated entity schema and created base field at the same time.';
  525. // Entity type updates create base fields as well, thus make sure doing both
  526. // at the same time does not lead to errors due to the base field being
  527. // created twice.
  528. try {
  529. $this->entityDefinitionUpdateManager->applyUpdates();
  530. $this->pass($message);
  531. }
  532. catch (\Exception $e) {
  533. $this->fail($message);
  534. throw $e;
  535. }
  536. }
  537. /**
  538. * Tests updating entity schema and creating a revisionable base field.
  539. *
  540. * This tests updating entity schema and creating a revisionable base field
  541. * at the same time when there are no existing entities.
  542. */
  543. public function testEntityTypeSchemaUpdateAndRevisionableBaseFieldCreateWithoutData() {
  544. $this->updateEntityTypeToRevisionable();
  545. $this->addRevisionableBaseField();
  546. $message = 'Successfully updated entity schema and created revisionable base field at the same time.';
  547. // Entity type updates create base fields as well, thus make sure doing both
  548. // at the same time does not lead to errors due to the base field being
  549. // created twice.
  550. try {
  551. $this->entityDefinitionUpdateManager->applyUpdates();
  552. $this->pass($message);
  553. }
  554. catch (\Exception $e) {
  555. $this->fail($message);
  556. throw $e;
  557. }
  558. }
  559. /**
  560. * Tests applying single updates.
  561. */
  562. public function testSingleActionCalls() {
  563. $db_schema = $this->database->schema();
  564. // Ensure that a non-existing entity type cannot be installed.
  565. $message = 'A non-existing entity type cannot be installed';
  566. try {
  567. $this->entityDefinitionUpdateManager->installEntityType(new ContentEntityType(['id' => 'foo']));
  568. $this->fail($message);
  569. }
  570. catch (PluginNotFoundException $e) {
  571. $this->pass($message);
  572. }
  573. // Ensure that a field cannot be installed on non-existing entity type.
  574. $message = 'A field cannot be installed on a non-existing entity type';
  575. try {
  576. $storage_definition = BaseFieldDefinition::create('string')
  577. ->setLabel(t('A new revisionable base field'))
  578. ->setRevisionable(TRUE);
  579. $this->entityDefinitionUpdateManager->installFieldStorageDefinition('bar', 'foo', 'entity_test', $storage_definition);
  580. $this->fail($message);
  581. }
  582. catch (PluginNotFoundException $e) {
  583. $this->pass($message);
  584. }
  585. // Ensure that a non-existing field cannot be installed.
  586. $storage_definition = BaseFieldDefinition::create('string')
  587. ->setLabel(t('A new revisionable base field'))
  588. ->setRevisionable(TRUE);
  589. $this->entityDefinitionUpdateManager->installFieldStorageDefinition('bar', 'entity_test_update', 'entity_test', $storage_definition);
  590. $this->assertFalse($db_schema->fieldExists('entity_test_update', 'bar'), "A non-existing field cannot be installed.");
  591. // Ensure that installing an existing entity type is a no-op.
  592. $entity_type = $this->entityDefinitionUpdateManager->getEntityType('entity_test_update');
  593. $this->entityDefinitionUpdateManager->installEntityType($entity_type);
  594. $this->assertTrue($db_schema->tableExists('entity_test_update'), 'Installing an existing entity type is a no-op');
  595. // Create a new base field.
  596. $this->addRevisionableBaseField();
  597. $storage_definition = BaseFieldDefinition::create('string')
  598. ->setLabel(t('A new revisionable base field'))
  599. ->setRevisionable(TRUE);
  600. $this->assertFalse($db_schema->fieldExists('entity_test_update', 'new_base_field'), "New field 'new_base_field' does not exist before applying the update.");
  601. $this->entityDefinitionUpdateManager->installFieldStorageDefinition('new_base_field', 'entity_test_update', 'entity_test', $storage_definition);
  602. $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.");
  603. // Ensure that installing an existing field is a no-op.
  604. $this->entityDefinitionUpdateManager->installFieldStorageDefinition('new_base_field', 'entity_test_update', 'entity_test', $storage_definition);
  605. $this->assertTrue($db_schema->fieldExists('entity_test_update', 'new_base_field'), 'Installing an existing field is a no-op');
  606. // Update an existing field schema.
  607. $this->modifyBaseField();
  608. $storage_definition = BaseFieldDefinition::create('text')
  609. ->setName('new_base_field')
  610. ->setTargetEntityTypeId('entity_test_update')
  611. ->setLabel(t('A new revisionable base field'))
  612. ->setRevisionable(TRUE);
  613. $this->entityDefinitionUpdateManager->updateFieldStorageDefinition($storage_definition);
  614. $this->assertFalse($db_schema->fieldExists('entity_test_update', 'new_base_field'), "Previous schema for 'new_base_field' no longer exists.");
  615. $this->assertTrue(
  616. $db_schema->fieldExists('entity_test_update', 'new_base_field__value') && $db_schema->fieldExists('entity_test_update', 'new_base_field__format'),
  617. "New schema for 'new_base_field' has been created."
  618. );
  619. // Drop an existing field schema.
  620. $this->entityDefinitionUpdateManager->uninstallFieldStorageDefinition($storage_definition);
  621. $this->assertFalse(
  622. $db_schema->fieldExists('entity_test_update', 'new_base_field__value') || $db_schema->fieldExists('entity_test_update', 'new_base_field__format'),
  623. "The schema for 'new_base_field' has been dropped."
  624. );
  625. // Make the entity type revisionable.
  626. $this->updateEntityTypeToRevisionable();
  627. $this->assertFalse($db_schema->tableExists('entity_test_update_revision'), "The 'entity_test_update_revision' does not exist before applying the update.");
  628. $entity_type = $this->entityDefinitionUpdateManager->getEntityType('entity_test_update');
  629. $keys = $entity_type->getKeys();
  630. $keys['revision'] = 'revision_id';
  631. $entity_type->set('entity_keys', $keys);
  632. $this->entityDefinitionUpdateManager->updateEntityType($entity_type);
  633. $this->assertTrue($db_schema->tableExists('entity_test_update_revision'), "The 'entity_test_update_revision' table has been created.");
  634. }
  635. /**
  636. * Ensures that a new field and index on a shared table are created.
  637. *
  638. * @see Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::createSharedTableSchema
  639. */
  640. public function testCreateFieldAndIndexOnSharedTable() {
  641. $this->addBaseField();
  642. $this->addBaseFieldIndex();
  643. $this->entityDefinitionUpdateManager->applyUpdates();
  644. $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.");
  645. $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.");
  646. // Check index size in for MySQL.
  647. if (Database::getConnection()->driver() == 'mysql') {
  648. $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();
  649. $this->assertEqual(191, $result->Sub_part, 'The index length has been restricted to 191 characters for UTF8MB4 compatibility.');
  650. }
  651. }
  652. /**
  653. * Ensures that a new entity level index is created when data exists.
  654. *
  655. * @see Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::onEntityTypeUpdate
  656. */
  657. public function testCreateIndexUsingEntityStorageSchemaWithData() {
  658. // Save an entity.
  659. $name = $this->randomString();
  660. $storage = $this->entityManager->getStorage('entity_test_update');
  661. $entity = $storage->create(array('name' => $name));
  662. $entity->save();
  663. // Create an index.
  664. $indexes = array(
  665. 'entity_test_update__type_index' => array('type'),
  666. );
  667. $this->state->set('entity_test_update.additional_entity_indexes', $indexes);
  668. $this->entityDefinitionUpdateManager->applyUpdates();
  669. $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.");
  670. // Check index size in for MySQL.
  671. if (Database::getConnection()->driver() == 'mysql') {
  672. $result = Database::getConnection()->query('SHOW INDEX FROM {entity_test_update} WHERE key_name = \'entity_test_update__type_index\' and column_name = \'type\'')->fetchObject();
  673. $this->assertEqual(191, $result->Sub_part, 'The index length has been restricted to 191 characters for UTF8MB4 compatibility.');
  674. }
  675. }
  676. /**
  677. * Tests updating a base field when it has existing data.
  678. */
  679. public function testBaseFieldEntityKeyUpdateWithExistingData() {
  680. // Add the base field and run the update.
  681. $this->addBaseField();
  682. $this->entityDefinitionUpdateManager->applyUpdates();
  683. // Save an entity with the base field populated.
  684. $this->entityManager->getStorage('entity_test_update')->create(['new_base_field' => $this->randomString()])->save();
  685. // Save an entity with the base field not populated.
  686. /** @var \Drupal\entity_test\Entity\EntityTestUpdate $entity */
  687. $entity = $this->entityManager->getStorage('entity_test_update')->create();
  688. $entity->save();
  689. // Promote the base field to an entity key. This will trigger the addition
  690. // of a NOT NULL constraint.
  691. $this->makeBaseFieldEntityKey();
  692. // Try to apply the update and verify they fail since we have a NULL value.
  693. $message = 'An error occurs when trying to enabling NOT NULL constraints with NULL data.';
  694. try {
  695. $this->entityDefinitionUpdateManager->applyUpdates();
  696. $this->fail($message);
  697. }
  698. catch (EntityStorageException $e) {
  699. $this->pass($message);
  700. }
  701. // Check that the update is correctly applied when no NULL data is left.
  702. $entity->set('new_base_field', $this->randomString());
  703. $entity->save();
  704. $this->entityDefinitionUpdateManager->applyUpdates();
  705. $this->pass('The update is correctly performed when no NULL data exists.');
  706. // Check that the update actually applied a NOT NULL constraint.
  707. $entity->set('new_base_field', NULL);
  708. $message = 'The NOT NULL constraint was correctly applied.';
  709. try {
  710. $entity->save();
  711. $this->fail($message);
  712. }
  713. catch (EntityStorageException $e) {
  714. $this->pass($message);
  715. }
  716. }
  717. /**
  718. * Check that field schema is correctly handled with long-named fields.
  719. */
  720. function testLongNameFieldIndexes() {
  721. $this->addLongNameBaseField();
  722. $entity_type_id = 'entity_test_update';
  723. $entity_type = $this->entityManager->getDefinition($entity_type_id);
  724. $definitions = EntityTestUpdate::baseFieldDefinitions($entity_type);
  725. $name = 'new_long_named_entity_reference_base_field';
  726. $this->entityDefinitionUpdateManager->installFieldStorageDefinition($name, $entity_type_id, 'entity_test', $definitions[$name]);
  727. $this->assertFalse($this->entityDefinitionUpdateManager->needsUpdates(), 'Entity and field schema data are correctly detected.');
  728. }
  729. }

Classes

Namesort descending Description
EntityDefinitionUpdateTest Tests EntityDefinitionUpdateManager functionality.