Mercurial > hg > isophonics-drupal-site
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 } |