comparison core/modules/field/tests/src/Kernel/BulkDeleteTest.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 129ea1e6d783
comparison
equal deleted inserted replaced
-1:000000000000 0:4c8ae668cc8c
1 <?php
2
3 namespace Drupal\Tests\field\Kernel;
4
5 use Drupal\Core\Entity\EntityInterface;
6 use Drupal\field\Entity\FieldConfig;
7 use Drupal\field\Entity\FieldStorageConfig;
8
9 /**
10 * Bulk delete storages and fields, and clean up afterwards.
11 *
12 * @group field
13 */
14 class BulkDeleteTest extends FieldKernelTestBase {
15
16 /**
17 * The fields to use in this test.
18 *
19 * @var array
20 */
21 protected $fieldStorages;
22
23 /**
24 * The entities to use in this test.
25 *
26 * @var array
27 */
28 protected $entities;
29
30 /**
31 * The entities to use in this test, keyed by bundle.
32 *
33 * @var array
34 */
35 protected $entitiesByBundles;
36
37 /**
38 * The bundles for the entities used in this test.
39 *
40 * @var array
41 */
42 protected $bundles;
43
44 /**
45 * The entity type to be used in the test classes.
46 *
47 * @var string
48 */
49 protected $entityTypeId = 'entity_test';
50
51 /**
52 * Tests that the expected hooks have been invoked on the expected entities.
53 *
54 * @param $expected_hooks
55 * An array keyed by hook name, with one entry per expected invocation.
56 * Each entry is the value of the "$entity" parameter the hook is expected
57 * to have been passed.
58 * @param $actual_hooks
59 * The array of actual hook invocations recorded by field_test_memorize().
60 */
61 public function checkHooksInvocations($expected_hooks, $actual_hooks) {
62 foreach ($expected_hooks as $hook => $invocations) {
63 $actual_invocations = $actual_hooks[$hook];
64
65 // Check that the number of invocations is correct.
66 $this->assertEqual(count($actual_invocations), count($invocations), "$hook() was called the expected number of times.");
67
68 // Check that the hook was called for each expected argument.
69 foreach ($invocations as $argument) {
70 $found = FALSE;
71 foreach ($actual_invocations as $actual_arguments) {
72 // The argument we are looking for is either an array of entities as
73 // the second argument or a single entity object as the first.
74 if ($argument instanceof EntityInterface && $actual_arguments[0]->id() == $argument->id()) {
75 $found = TRUE;
76 break;
77 }
78 // In case of an array, compare the array size and make sure it
79 // contains the same elements.
80 elseif (is_array($argument) && count($actual_arguments[1]) == count($argument) && count(array_diff_key($actual_arguments[1], $argument)) == 0) {
81 $found = TRUE;
82 break;
83 }
84 }
85 $this->assertTrue($found, "$hook() was called on expected argument");
86 }
87 }
88 }
89
90 protected function setUp() {
91 parent::setUp();
92
93 $this->fieldStorages = [];
94 $this->entities = [];
95 $this->entitiesByBundles = [];
96
97 // Create two bundles.
98 $this->bundles = ['bb_1' => 'bb_1', 'bb_2' => 'bb_2'];
99 foreach ($this->bundles as $name => $desc) {
100 entity_test_create_bundle($name, $desc);
101 }
102
103 // Create two field storages.
104 $field_storage = FieldStorageConfig::create([
105 'field_name' => 'bf_1',
106 'entity_type' => $this->entityTypeId,
107 'type' => 'test_field',
108 'cardinality' => 1
109 ]);
110 $field_storage->save();
111 $this->fieldStorages[] = $field_storage;
112 $field_storage = FieldStorageConfig::create([
113 'field_name' => 'bf_2',
114 'entity_type' => $this->entityTypeId,
115 'type' => 'test_field',
116 'cardinality' => 4
117 ]);
118 $field_storage->save();
119 $this->fieldStorages[] = $field_storage;
120
121 // For each bundle, create each field, and 10 entities with values for the
122 // fields.
123 foreach ($this->bundles as $bundle) {
124 foreach ($this->fieldStorages as $field_storage) {
125 FieldConfig::create([
126 'field_storage' => $field_storage,
127 'bundle' => $bundle,
128 ])->save();
129 }
130 for ($i = 0; $i < 10; $i++) {
131 $entity = $this->container->get('entity_type.manager')
132 ->getStorage($this->entityTypeId)
133 ->create(['type' => $bundle]);
134 foreach ($this->fieldStorages as $field_storage) {
135 $entity->{$field_storage->getName()}->setValue($this->_generateTestFieldValues($field_storage->getCardinality()));
136 }
137 $entity->save();
138 }
139 }
140 $this->entities = $this->container->get('entity_type.manager')
141 ->getStorage($this->entityTypeId)->loadMultiple();
142 foreach ($this->entities as $entity) {
143 // This test relies on the entities having stale field definitions
144 // so that the deleted field can be accessed on them. Access the field
145 // now, so that they are always loaded.
146 $entity->bf_1->value;
147
148 // Also keep track of the entities per bundle.
149 $this->entitiesByBundles[$entity->bundle()][$entity->id()] = $entity;
150 }
151 }
152
153 /**
154 * Verify that deleting a field leaves the field data items in the database
155 * and that the appropriate Field API functions can operate on the deleted
156 * data and field definition.
157 *
158 * This tests how EntityFieldQuery interacts with field deletion and could be
159 * moved to FieldCrudTestCase, but depends on this class's setUp().
160 */
161 public function testDeleteField() {
162 $bundle = reset($this->bundles);
163 $field_storage = reset($this->fieldStorages);
164 $field_name = $field_storage->getName();
165 $factory = \Drupal::service('entity.query');
166
167 // There are 10 entities of this bundle.
168 $found = $factory->get('entity_test')
169 ->condition('type', $bundle)
170 ->execute();
171 $this->assertEqual(count($found), 10, 'Correct number of entities found before deleting');
172
173 // Delete the field.
174 $field = FieldConfig::loadByName($this->entityTypeId, $bundle, $field_name);
175 $field->delete();
176
177 // The field still exists, deleted.
178 $fields = entity_load_multiple_by_properties('field_config', ['field_storage_uuid' => $field_storage->uuid(), 'deleted' => TRUE, 'include_deleted' => TRUE]);
179 $this->assertEqual(count($fields), 1, 'There is one deleted field');
180 $field = $fields[$field->uuid()];
181 $this->assertEqual($field->getTargetBundle(), $bundle, 'The deleted field is for the correct bundle');
182
183 // Check that the actual stored content did not change during delete.
184 $storage = \Drupal::entityManager()->getStorage($this->entityTypeId);
185 /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
186 $table_mapping = $storage->getTableMapping();
187 $table = $table_mapping->getDedicatedDataTableName($field_storage);
188 $column = $table_mapping->getFieldColumnName($field_storage, 'value');
189 $result = db_select($table, 't')
190 ->fields('t')
191 ->execute();
192 foreach ($result as $row) {
193 $this->assertEqual($this->entities[$row->entity_id]->{$field_name}->value, $row->$column);
194 }
195
196 // There are 0 entities of this bundle with non-deleted data.
197 $found = $factory->get('entity_test')
198 ->condition('type', $bundle)
199 ->condition("$field_name.deleted", 0)
200 ->execute();
201 $this->assertFalse($found, 'No entities found after deleting');
202
203 // There are 10 entities of this bundle when deleted fields are allowed, and
204 // their values are correct.
205 $found = $factory->get('entity_test')
206 ->condition('type', $bundle)
207 ->condition("$field_name.deleted", 1)
208 ->sort('id')
209 ->execute();
210 $this->assertEqual(count($found), 10, 'Correct number of entities found after deleting');
211 $this->assertFalse(array_diff($found, array_keys($this->entities)));
212 }
213
214 /**
215 * Tests that recreating a field with the name as a deleted field works.
216 */
217 public function testPurgeWithDeletedAndActiveField() {
218 $bundle = reset($this->bundles);
219 // Create another field storage.
220 $field_name = 'bf_3';
221 $deleted_field_storage = FieldStorageConfig::create([
222 'field_name' => $field_name,
223 'entity_type' => $this->entityTypeId,
224 'type' => 'test_field',
225 'cardinality' => 1
226 ]);
227 $deleted_field_storage->save();
228 // Create the field.
229 FieldConfig::create([
230 'field_storage' => $deleted_field_storage,
231 'bundle' => $bundle,
232 ])->save();
233
234 for ($i = 0; $i < 20; $i++) {
235 $entity = $this->container->get('entity_type.manager')
236 ->getStorage($this->entityTypeId)
237 ->create(['type' => $bundle]);
238 $entity->{$field_name}->setValue($this->_generateTestFieldValues(1));
239 $entity->save();
240 }
241
242 // Delete the field.
243 $deleted_field = FieldConfig::loadByName($this->entityTypeId, $bundle, $field_name);
244 $deleted_field->delete();
245 $deleted_field_uuid = $deleted_field->uuid();
246
247 // Reload the field storage.
248 $field_storages = entity_load_multiple_by_properties('field_storage_config', ['uuid' => $deleted_field_storage->uuid(), 'include_deleted' => TRUE]);
249 $deleted_field_storage = reset($field_storages);
250
251 // Create the field again.
252 $field_storage = FieldStorageConfig::create([
253 'field_name' => $field_name,
254 'entity_type' => $this->entityTypeId,
255 'type' => 'test_field',
256 'cardinality' => 1
257 ]);
258 $field_storage->save();
259 FieldConfig::create([
260 'field_storage' => $field_storage,
261 'bundle' => $bundle,
262 ])->save();
263
264 // The field still exists, deleted, with the same field name.
265 $fields = entity_load_multiple_by_properties('field_config', ['uuid' => $deleted_field_uuid, 'include_deleted' => TRUE]);
266 $this->assertTrue(isset($fields[$deleted_field_uuid]) && $fields[$deleted_field_uuid]->isDeleted(), 'The field exists and is deleted');
267 $this->assertTrue(isset($fields[$deleted_field_uuid]) && $fields[$deleted_field_uuid]->getName() == $field_name);
268
269 for ($i = 0; $i < 10; $i++) {
270 $entity = $this->container->get('entity_type.manager')
271 ->getStorage($this->entityTypeId)
272 ->create(['type' => $bundle]);
273 $entity->{$field_name}->setValue($this->_generateTestFieldValues(1));
274 $entity->save();
275 }
276
277 // Check that the two field storages have different tables.
278 $storage = \Drupal::entityManager()->getStorage($this->entityTypeId);
279 /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
280 $table_mapping = $storage->getTableMapping();
281 $deleted_table_name = $table_mapping->getDedicatedDataTableName($deleted_field_storage, TRUE);
282 $active_table_name = $table_mapping->getDedicatedDataTableName($field_storage);
283
284 field_purge_batch(50);
285
286 // Ensure the new field still has its table and the deleted one has been
287 // removed.
288 $this->assertTrue(\Drupal::database()->schema()->tableExists($active_table_name));
289 $this->assertFalse(\Drupal::database()->schema()->tableExists($deleted_table_name));
290
291 // The field has been removed from the system.
292 $fields = entity_load_multiple_by_properties('field_config', ['field_storage_uuid' => $deleted_field_storage->uuid(), 'deleted' => TRUE, 'include_deleted' => TRUE]);
293 $this->assertEqual(count($fields), 0, 'The field is gone');
294
295 // Verify there are still 10 entries in the main table.
296 $count = \Drupal::database()
297 ->select('entity_test__' . $field_name, 'f')
298 ->fields('f', ['entity_id'])
299 ->condition('bundle', $bundle)
300 ->countQuery()
301 ->execute()
302 ->fetchField();
303 $this->assertEqual($count, 10);
304 }
305
306 /**
307 * Verify that field data items and fields are purged when a field storage is
308 * deleted.
309 */
310 public function testPurgeField() {
311 // Start recording hook invocations.
312 field_test_memorize();
313
314 $bundle = reset($this->bundles);
315 $field_storage = reset($this->fieldStorages);
316 $field_name = $field_storage->getName();
317
318 // Delete the field.
319 $field = FieldConfig::loadByName($this->entityTypeId, $bundle, $field_name);
320 $field->delete();
321
322 // No field hooks were called.
323 $mem = field_test_memorize();
324 $this->assertEqual(count($mem), 0, 'No field hooks were called');
325
326 $batch_size = 2;
327 for ($count = 8; $count >= 0; $count -= $batch_size) {
328 // Purge two entities.
329 field_purge_batch($batch_size);
330
331 // There are $count deleted entities left.
332 $found = \Drupal::entityQuery('entity_test')
333 ->condition('type', $bundle)
334 ->condition($field_name . '.deleted', 1)
335 ->execute();
336 $this->assertEqual(count($found), $count, 'Correct number of entities found after purging 2');
337 }
338
339 // Check hooks invocations.
340 // FieldItemInterface::delete() should have been called once for each entity in the
341 // bundle.
342 $actual_hooks = field_test_memorize();
343 $hooks = [];
344 $entities = $this->entitiesByBundles[$bundle];
345 foreach ($entities as $id => $entity) {
346 $hooks['field_test_field_delete'][] = $entity;
347 }
348 $this->checkHooksInvocations($hooks, $actual_hooks);
349
350 // The field still exists, deleted.
351 $fields = entity_load_multiple_by_properties('field_config', ['field_storage_uuid' => $field_storage->uuid(), 'deleted' => TRUE, 'include_deleted' => TRUE]);
352 $this->assertEqual(count($fields), 1, 'There is one deleted field');
353
354 // Purge the field.
355 field_purge_batch($batch_size);
356
357 // The field is gone.
358 $fields = entity_load_multiple_by_properties('field_config', ['field_storage_uuid' => $field_storage->uuid(), 'deleted' => TRUE, 'include_deleted' => TRUE]);
359 $this->assertEqual(count($fields), 0, 'The field is gone');
360
361 // The field storage still exists, not deleted, because it has a second
362 // field.
363 $storages = entity_load_multiple_by_properties('field_storage_config', ['uuid' => $field_storage->uuid(), 'include_deleted' => TRUE]);
364 $this->assertTrue(isset($storages[$field_storage->uuid()]), 'The field storage exists and is not deleted');
365 }
366
367 /**
368 * Verify that field storages are preserved and purged correctly as multiple
369 * fields are deleted and purged.
370 */
371 public function testPurgeFieldStorage() {
372 // Start recording hook invocations.
373 field_test_memorize();
374
375 $field_storage = reset($this->fieldStorages);
376 $field_name = $field_storage->getName();
377
378 // Delete the first field.
379 $bundle = reset($this->bundles);
380 $field = FieldConfig::loadByName($this->entityTypeId, $bundle, $field_name);
381 $field->delete();
382
383 // Assert that FieldItemInterface::delete() was not called yet.
384 $mem = field_test_memorize();
385 $this->assertEqual(count($mem), 0, 'No field hooks were called.');
386
387 // Purge the data.
388 field_purge_batch(10);
389
390 // Check hooks invocations.
391 // FieldItemInterface::delete() should have been called once for each entity in the
392 // bundle.
393 $actual_hooks = field_test_memorize();
394 $hooks = [];
395 $entities = $this->entitiesByBundles[$bundle];
396 foreach ($entities as $id => $entity) {
397 $hooks['field_test_field_delete'][] = $entity;
398 }
399 $this->checkHooksInvocations($hooks, $actual_hooks);
400
401 // The field still exists, deleted.
402 $fields = entity_load_multiple_by_properties('field_config', ['uuid' => $field->uuid(), 'include_deleted' => TRUE]);
403 $this->assertTrue(isset($fields[$field->uuid()]) && $fields[$field->uuid()]->isDeleted(), 'The field exists and is deleted');
404
405 // Purge again to purge the field.
406 field_purge_batch(0);
407
408 // The field is gone.
409 $fields = entity_load_multiple_by_properties('field_config', ['uuid' => $field->uuid(), 'include_deleted' => TRUE]);
410 $this->assertEqual(count($fields), 0, 'The field is purged.');
411 // The field storage still exists, not deleted.
412 $storages = entity_load_multiple_by_properties('field_storage_config', ['uuid' => $field_storage->uuid(), 'include_deleted' => TRUE]);
413 $this->assertTrue(isset($storages[$field_storage->uuid()]) && !$storages[$field_storage->uuid()]->isDeleted(), 'The field storage exists and is not deleted');
414
415 // Delete the second field.
416 $bundle = next($this->bundles);
417 $field = FieldConfig::loadByName($this->entityTypeId, $bundle, $field_name);
418 $field->delete();
419
420 // Assert that FieldItemInterface::delete() was not called yet.
421 $mem = field_test_memorize();
422 $this->assertEqual(count($mem), 0, 'No field hooks were called.');
423
424 // Purge the data.
425 field_purge_batch(10);
426
427 // Check hooks invocations (same as above, for the 2nd bundle).
428 $actual_hooks = field_test_memorize();
429 $hooks = [];
430 $entities = $this->entitiesByBundles[$bundle];
431 foreach ($entities as $id => $entity) {
432 $hooks['field_test_field_delete'][] = $entity;
433 }
434 $this->checkHooksInvocations($hooks, $actual_hooks);
435
436 // The field and the storage still exist, deleted.
437 $fields = entity_load_multiple_by_properties('field_config', ['uuid' => $field->uuid(), 'include_deleted' => TRUE]);
438 $this->assertTrue(isset($fields[$field->uuid()]) && $fields[$field->uuid()]->isDeleted(), 'The field exists and is deleted');
439 $storages = entity_load_multiple_by_properties('field_storage_config', ['uuid' => $field_storage->uuid(), 'include_deleted' => TRUE]);
440 $this->assertTrue(isset($storages[$field_storage->uuid()]) && $storages[$field_storage->uuid()]->isDeleted(), 'The field storage exists and is deleted');
441
442 // Purge again to purge the field and the storage.
443 field_purge_batch(0);
444
445 // The field and the storage are gone.
446 $fields = entity_load_multiple_by_properties('field_config', ['uuid' => $field->uuid(), 'include_deleted' => TRUE]);
447 $this->assertEqual(count($fields), 0, 'The field is purged.');
448 $storages = entity_load_multiple_by_properties('field_storage_config', ['uuid' => $field_storage->uuid(), 'include_deleted' => TRUE]);
449 $this->assertEqual(count($storages), 0, 'The field storage is purged.');
450 }
451
452 }