Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace Drupal\Tests\field\Kernel;
|
Chris@0
|
4
|
Chris@0
|
5 use Drupal\Core\Entity\EntityStorageException;
|
Chris@0
|
6 use Drupal\Core\Field\FieldException;
|
Chris@0
|
7 use Drupal\entity_test\Entity\EntityTest;
|
Chris@0
|
8 use Drupal\field\Entity\FieldStorageConfig;
|
Chris@0
|
9 use Drupal\field\Entity\FieldConfig;
|
Chris@0
|
10
|
Chris@0
|
11 /**
|
Chris@0
|
12 * Create field entities by attaching fields to entities.
|
Chris@0
|
13 *
|
Chris@0
|
14 * @coversDefaultClass \Drupal\Core\Field\FieldConfigBase
|
Chris@0
|
15 *
|
Chris@0
|
16 * @group field
|
Chris@0
|
17 */
|
Chris@0
|
18 class FieldCrudTest extends FieldKernelTestBase {
|
Chris@0
|
19
|
Chris@0
|
20 /**
|
Chris@0
|
21 * The field storage entity.
|
Chris@0
|
22 *
|
Chris@0
|
23 * @var \Drupal\field\Entity\FieldStorageConfig
|
Chris@0
|
24 */
|
Chris@0
|
25 protected $fieldStorage;
|
Chris@0
|
26
|
Chris@0
|
27 /**
|
Chris@0
|
28 * The field entity definition.
|
Chris@0
|
29 *
|
Chris@0
|
30 * @var array
|
Chris@0
|
31 */
|
Chris@0
|
32 protected $fieldStorageDefinition;
|
Chris@0
|
33
|
Chris@0
|
34 /**
|
Chris@0
|
35 * The field entity definition.
|
Chris@0
|
36 *
|
Chris@0
|
37 * @var array
|
Chris@0
|
38 */
|
Chris@0
|
39 protected $fieldDefinition;
|
Chris@0
|
40
|
Chris@0
|
41 public function setUp() {
|
Chris@0
|
42 parent::setUp();
|
Chris@0
|
43
|
Chris@0
|
44 $this->fieldStorageDefinition = [
|
Chris@17
|
45 'field_name' => mb_strtolower($this->randomMachineName()),
|
Chris@0
|
46 'entity_type' => 'entity_test',
|
Chris@0
|
47 'type' => 'test_field',
|
Chris@0
|
48 ];
|
Chris@0
|
49 $this->fieldStorage = FieldStorageConfig::create($this->fieldStorageDefinition);
|
Chris@0
|
50 $this->fieldStorage->save();
|
Chris@0
|
51 $this->fieldDefinition = [
|
Chris@0
|
52 'field_name' => $this->fieldStorage->getName(),
|
Chris@0
|
53 'entity_type' => 'entity_test',
|
Chris@0
|
54 'bundle' => 'entity_test',
|
Chris@0
|
55 ];
|
Chris@0
|
56 }
|
Chris@0
|
57
|
Chris@0
|
58 // TODO : test creation with
|
Chris@0
|
59 // - a full fledged $field structure, check that all the values are there
|
Chris@0
|
60 // - a minimal $field structure, check all default values are set
|
Chris@0
|
61 // defer actual $field comparison to a helper function, used for the two cases above,
|
Chris@0
|
62 // and for testUpdateField
|
Chris@0
|
63
|
Chris@0
|
64 /**
|
Chris@0
|
65 * Test the creation of a field.
|
Chris@0
|
66 */
|
Chris@0
|
67 public function testCreateField() {
|
Chris@0
|
68 $field = FieldConfig::create($this->fieldDefinition);
|
Chris@0
|
69 $field->save();
|
Chris@0
|
70
|
Chris@0
|
71 $field = FieldConfig::load($field->id());
|
Chris@0
|
72 $this->assertTrue($field->getSetting('field_setting_from_config_data'));
|
Chris@0
|
73 $this->assertNull($field->getSetting('config_data_from_field_setting'));
|
Chris@0
|
74
|
Chris@0
|
75 // Read the configuration. Check against raw configuration data rather than
|
Chris@0
|
76 // the loaded ConfigEntity, to be sure we check that the defaults are
|
Chris@0
|
77 // applied on write.
|
Chris@0
|
78 $config = $this->config('field.field.' . $field->id())->get();
|
Chris@0
|
79 $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
|
Chris@0
|
80
|
Chris@0
|
81 $this->assertTrue($config['settings']['config_data_from_field_setting']);
|
Chris@0
|
82 $this->assertTrue(!isset($config['settings']['field_setting_from_config_data']));
|
Chris@0
|
83
|
Chris@0
|
84 // Since we are working with raw configuration, this needs to be unset
|
Chris@0
|
85 // manually.
|
Chris@0
|
86 // @see Drupal\field_test\Plugin\Field\FieldType\TestItem::fieldSettingsFromConfigData()
|
Chris@0
|
87 unset($config['settings']['config_data_from_field_setting']);
|
Chris@0
|
88
|
Chris@0
|
89 // Check that default values are set.
|
Chris@0
|
90 $this->assertEqual($config['required'], FALSE, 'Required defaults to false.');
|
Chris@0
|
91 $this->assertIdentical($config['label'], $this->fieldDefinition['field_name'], 'Label defaults to field name.');
|
Chris@0
|
92 $this->assertIdentical($config['description'], '', 'Description defaults to empty string.');
|
Chris@0
|
93
|
Chris@0
|
94 // Check that default settings are set.
|
Chris@0
|
95 $this->assertEqual($config['settings'], $field_type_manager->getDefaultFieldSettings($this->fieldStorageDefinition['type']), 'Default field settings have been written.');
|
Chris@0
|
96
|
Chris@0
|
97 // Check that the denormalized 'field_type' was properly written.
|
Chris@0
|
98 $this->assertEqual($config['field_type'], $this->fieldStorageDefinition['type']);
|
Chris@0
|
99
|
Chris@0
|
100 // Guarantee that the field/bundle combination is unique.
|
Chris@0
|
101 try {
|
Chris@0
|
102 FieldConfig::create($this->fieldDefinition)->save();
|
Chris@0
|
103 $this->fail(t('Cannot create two fields with the same field / bundle combination.'));
|
Chris@0
|
104 }
|
Chris@0
|
105 catch (EntityStorageException $e) {
|
Chris@0
|
106 $this->pass(t('Cannot create two fields with the same field / bundle combination.'));
|
Chris@0
|
107 }
|
Chris@0
|
108
|
Chris@0
|
109 // Check that the specified field exists.
|
Chris@0
|
110 try {
|
Chris@0
|
111 $this->fieldDefinition['field_name'] = $this->randomMachineName();
|
Chris@0
|
112 FieldConfig::create($this->fieldDefinition)->save();
|
Chris@0
|
113 $this->fail(t('Cannot create a field with a non-existing storage.'));
|
Chris@0
|
114 }
|
Chris@0
|
115 catch (FieldException $e) {
|
Chris@0
|
116 $this->pass(t('Cannot create a field with a non-existing storage.'));
|
Chris@0
|
117 }
|
Chris@0
|
118
|
Chris@0
|
119 // TODO: test other failures.
|
Chris@0
|
120 }
|
Chris@0
|
121
|
Chris@0
|
122 /**
|
Chris@0
|
123 * Tests setting and adding property constraints to a configurable field.
|
Chris@0
|
124 *
|
Chris@0
|
125 * @covers ::setPropertyConstraints
|
Chris@0
|
126 * @covers ::addPropertyConstraints
|
Chris@0
|
127 */
|
Chris@0
|
128 public function testFieldPropertyConstraints() {
|
Chris@0
|
129 $field = FieldConfig::create($this->fieldDefinition);
|
Chris@0
|
130 $field->save();
|
Chris@0
|
131 $field_name = $this->fieldStorage->getName();
|
Chris@0
|
132
|
Chris@0
|
133 // Test that constraints are applied to configurable fields. A TestField and
|
Chris@0
|
134 // a Range constraint are added dynamically to limit the field to values
|
Chris@0
|
135 // between 0 and 32.
|
Chris@0
|
136 // @see field_test_entity_bundle_field_info_alter()
|
Chris@0
|
137 \Drupal::state()->set('field_test_constraint', $field_name);
|
Chris@0
|
138
|
Chris@0
|
139 // Clear the field definitions cache so the new constraints added by
|
Chris@0
|
140 // field_test_entity_bundle_field_info_alter() are taken into consideration.
|
Chris@0
|
141 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
|
Chris@0
|
142
|
Chris@0
|
143 // Test the newly added property constraints in the same request as when the
|
Chris@0
|
144 // caches were cleared. This will test the field definitions that are stored
|
Chris@0
|
145 // in the static cache of
|
Chris@0
|
146 // \Drupal\Core\Entity\EntityFieldManager::getFieldDefinitions().
|
Chris@0
|
147 $this->doFieldPropertyConstraintsTests();
|
Chris@0
|
148
|
Chris@0
|
149 // In order to test a real-world scenario where the property constraints are
|
Chris@0
|
150 // only stored in the persistent cache of
|
Chris@0
|
151 // \Drupal\Core\Entity\EntityFieldManager::getFieldDefinitions(), we need to
|
Chris@0
|
152 // simulate a new request by removing the 'entity_field.manager' service,
|
Chris@0
|
153 // thus forcing it to be re-initialized without static caches.
|
Chris@0
|
154 \Drupal::getContainer()->set('entity_field.manager', NULL);
|
Chris@0
|
155
|
Chris@0
|
156 // This will test the field definitions that are stored in the persistent
|
Chris@0
|
157 // cache by \Drupal\Core\Entity\EntityFieldManager::getFieldDefinitions().
|
Chris@0
|
158 $this->doFieldPropertyConstraintsTests();
|
Chris@0
|
159 }
|
Chris@0
|
160
|
Chris@0
|
161 /**
|
Chris@0
|
162 * Tests configurable field validation.
|
Chris@0
|
163 *
|
Chris@0
|
164 * @see field_test_entity_bundle_field_info_alter()
|
Chris@0
|
165 */
|
Chris@0
|
166 protected function doFieldPropertyConstraintsTests() {
|
Chris@0
|
167 $field_name = $this->fieldStorage->getName();
|
Chris@0
|
168
|
Chris@0
|
169 // Check that a valid value (not -2 and between 0 and 32) doesn't trigger
|
Chris@0
|
170 // any violation.
|
Chris@0
|
171 $entity = EntityTest::create();
|
Chris@0
|
172 $entity->set($field_name, 1);
|
Chris@0
|
173 $violations = $entity->validate();
|
Chris@0
|
174 $this->assertCount(0, $violations, 'No violations found when in-range value passed.');
|
Chris@0
|
175
|
Chris@0
|
176 // Check that a value that is specifically restricted triggers both
|
Chris@0
|
177 // violations.
|
Chris@0
|
178 $entity->set($field_name, -2);
|
Chris@0
|
179 $violations = $entity->validate();
|
Chris@0
|
180 $this->assertCount(2, $violations, 'Two violations found when using a null and outside the range value.');
|
Chris@0
|
181
|
Chris@0
|
182 $this->assertEquals($field_name . '.0.value', $violations[0]->getPropertyPath());
|
Chris@0
|
183 $this->assertEquals(t('%name does not accept the value @value.', ['%name' => $field_name, '@value' => -2]), $violations[0]->getMessage());
|
Chris@0
|
184
|
Chris@0
|
185 $this->assertEquals($field_name . '.0.value', $violations[1]->getPropertyPath());
|
Chris@0
|
186 $this->assertEquals(t('This value should be %limit or more.', ['%limit' => 0]), $violations[1]->getMessage());
|
Chris@0
|
187
|
Chris@0
|
188 // Check that a value that is not specifically restricted but outside the
|
Chris@0
|
189 // range triggers the expected violation.
|
Chris@0
|
190 $entity->set($field_name, 33);
|
Chris@0
|
191 $violations = $entity->validate();
|
Chris@0
|
192 $this->assertCount(1, $violations, 'Violations found when using value outside the range.');
|
Chris@0
|
193 $this->assertEquals($field_name . '.0.value', $violations[0]->getPropertyPath());
|
Chris@0
|
194 $this->assertEquals(t('This value should be %limit or less.', ['%limit' => 32]), $violations[0]->getMessage());
|
Chris@0
|
195 }
|
Chris@0
|
196
|
Chris@0
|
197 /**
|
Chris@0
|
198 * Test creating a field with custom storage set.
|
Chris@0
|
199 */
|
Chris@0
|
200 public function testCreateFieldCustomStorage() {
|
Chris@17
|
201 $field_name = mb_strtolower($this->randomMachineName());
|
Chris@0
|
202 \Drupal::state()->set('field_test_custom_storage', $field_name);
|
Chris@0
|
203
|
Chris@0
|
204 $field_storage = FieldStorageConfig::create([
|
Chris@0
|
205 'field_name' => $field_name,
|
Chris@0
|
206 'entity_type' => 'entity_test',
|
Chris@0
|
207 'type' => 'test_field',
|
Chris@0
|
208 'custom_storage' => TRUE,
|
Chris@0
|
209 ]);
|
Chris@0
|
210 $field_storage->save();
|
Chris@0
|
211
|
Chris@0
|
212 $field = FieldConfig::create([
|
Chris@0
|
213 'field_name' => $field_storage->getName(),
|
Chris@0
|
214 'entity_type' => 'entity_test',
|
Chris@0
|
215 'bundle' => 'entity_test',
|
Chris@0
|
216 ]);
|
Chris@0
|
217 $field->save();
|
Chris@0
|
218
|
Chris@0
|
219 \Drupal::entityManager()->clearCachedFieldDefinitions();
|
Chris@0
|
220
|
Chris@0
|
221 // Check that no table has been created for the field.
|
Chris@0
|
222 $this->assertFalse(\Drupal::database()->schema()->tableExists('entity_test__' . $field_storage->getName()));
|
Chris@0
|
223
|
Chris@0
|
224 // Save an entity with a value in the custom storage field and verify no
|
Chris@0
|
225 // data is retrieved on load.
|
Chris@0
|
226 $entity = EntityTest::create(['name' => $this->randomString(), $field_name => 'Test value']);
|
Chris@0
|
227 $this->assertIdentical('Test value', $entity->{$field_name}->value, 'The test value is set on the field.');
|
Chris@0
|
228
|
Chris@0
|
229 $entity->save();
|
Chris@0
|
230 $entity = EntityTest::load($entity->id());
|
Chris@0
|
231
|
Chris@0
|
232 $this->assertNull($entity->{$field_name}->value, 'The loaded entity field value is NULL.');
|
Chris@0
|
233 }
|
Chris@0
|
234
|
Chris@0
|
235 /**
|
Chris@0
|
236 * Test reading back a field definition.
|
Chris@0
|
237 */
|
Chris@0
|
238 public function testReadField() {
|
Chris@0
|
239 FieldConfig::create($this->fieldDefinition)->save();
|
Chris@0
|
240
|
Chris@0
|
241 // Read the field back.
|
Chris@0
|
242 $field = FieldConfig::load('entity_test.' . $this->fieldDefinition['bundle'] . '.' . $this->fieldDefinition['field_name']);
|
Chris@0
|
243 $this->assertTrue($this->fieldDefinition['field_name'] == $field->getName(), 'The field was properly read.');
|
Chris@0
|
244 $this->assertTrue($this->fieldDefinition['entity_type'] == $field->getTargetEntityTypeId(), 'The field was properly read.');
|
Chris@0
|
245 $this->assertTrue($this->fieldDefinition['bundle'] == $field->getTargetBundle(), 'The field was properly read.');
|
Chris@0
|
246 }
|
Chris@0
|
247
|
Chris@0
|
248 /**
|
Chris@0
|
249 * Test the update of a field.
|
Chris@0
|
250 */
|
Chris@0
|
251 public function testUpdateField() {
|
Chris@0
|
252 FieldConfig::create($this->fieldDefinition)->save();
|
Chris@0
|
253
|
Chris@0
|
254 // Check that basic changes are saved.
|
Chris@0
|
255 $field = FieldConfig::load('entity_test.' . $this->fieldDefinition['bundle'] . '.' . $this->fieldDefinition['field_name']);
|
Chris@0
|
256 $field->setRequired(!$field->isRequired());
|
Chris@0
|
257 $field->setLabel($this->randomMachineName());
|
Chris@0
|
258 $field->set('description', $this->randomMachineName());
|
Chris@0
|
259 $field->setSetting('test_field_setting', $this->randomMachineName());
|
Chris@0
|
260 $field->save();
|
Chris@0
|
261
|
Chris@0
|
262 $field_new = FieldConfig::load('entity_test.' . $this->fieldDefinition['bundle'] . '.' . $this->fieldDefinition['field_name']);
|
Chris@0
|
263 $this->assertEqual($field->isRequired(), $field_new->isRequired(), '"required" change is saved');
|
Chris@0
|
264 $this->assertEqual($field->getLabel(), $field_new->getLabel(), '"label" change is saved');
|
Chris@0
|
265 $this->assertEqual($field->getDescription(), $field_new->getDescription(), '"description" change is saved');
|
Chris@0
|
266
|
Chris@0
|
267 // TODO: test failures.
|
Chris@0
|
268 }
|
Chris@0
|
269
|
Chris@0
|
270 /**
|
Chris@0
|
271 * Test the deletion of a field with no data.
|
Chris@0
|
272 */
|
Chris@0
|
273 public function testDeleteFieldNoData() {
|
Chris@0
|
274 // Deleting and purging fields with data is tested in
|
Chris@0
|
275 // \Drupal\Tests\field\Kernel\BulkDeleteTest.
|
Chris@0
|
276
|
Chris@0
|
277 // Create two fields for the same field storage so we can test that only one
|
Chris@0
|
278 // is deleted.
|
Chris@0
|
279 FieldConfig::create($this->fieldDefinition)->save();
|
Chris@0
|
280 $another_field_definition = $this->fieldDefinition;
|
Chris@0
|
281 $another_field_definition['bundle'] .= '_another_bundle';
|
Chris@0
|
282 entity_test_create_bundle($another_field_definition['bundle']);
|
Chris@0
|
283 FieldConfig::create($another_field_definition)->save();
|
Chris@0
|
284
|
Chris@0
|
285 // Test that the first field is not deleted, and then delete it.
|
Chris@0
|
286 $field = current(entity_load_multiple_by_properties('field_config', ['entity_type' => 'entity_test', 'field_name' => $this->fieldDefinition['field_name'], 'bundle' => $this->fieldDefinition['bundle'], 'include_deleted' => TRUE]));
|
Chris@0
|
287 $this->assertTrue(!empty($field) && empty($field->deleted), 'A new field is not marked for deletion.');
|
Chris@0
|
288 $field->delete();
|
Chris@0
|
289
|
Chris@0
|
290 // Make sure the field was deleted without being marked for purging as there
|
Chris@0
|
291 // was no data.
|
Chris@0
|
292 $fields = entity_load_multiple_by_properties('field_config', ['entity_type' => 'entity_test', 'field_name' => $this->fieldDefinition['field_name'], 'bundle' => $this->fieldDefinition['bundle'], 'include_deleted' => TRUE]);
|
Chris@0
|
293 $this->assertEquals(0, count($fields), 'A deleted field is marked for deletion.');
|
Chris@0
|
294
|
Chris@0
|
295 // Try to load the field normally and make sure it does not show up.
|
Chris@0
|
296 $field = FieldConfig::load('entity_test.' . '.' . $this->fieldDefinition['bundle'] . '.' . $this->fieldDefinition['field_name']);
|
Chris@0
|
297 $this->assertTrue(empty($field), 'Field was deleted');
|
Chris@0
|
298
|
Chris@0
|
299 // Make sure the other field is not deleted.
|
Chris@0
|
300 $another_field = FieldConfig::load('entity_test.' . $another_field_definition['bundle'] . '.' . $another_field_definition['field_name']);
|
Chris@0
|
301 $this->assertTrue(!empty($another_field) && !$another_field->isDeleted(), 'A non-deleted field is not marked for deletion.');
|
Chris@0
|
302 }
|
Chris@0
|
303
|
Chris@0
|
304 /**
|
Chris@0
|
305 * Tests the cross deletion behavior between field storages and fields.
|
Chris@0
|
306 */
|
Chris@0
|
307 public function testDeleteFieldCrossDeletion() {
|
Chris@0
|
308 $field_definition_2 = $this->fieldDefinition;
|
Chris@0
|
309 $field_definition_2['bundle'] .= '_another_bundle';
|
Chris@0
|
310 entity_test_create_bundle($field_definition_2['bundle']);
|
Chris@0
|
311
|
Chris@0
|
312 // Check that deletion of a field storage deletes its fields.
|
Chris@0
|
313 $field_storage = $this->fieldStorage;
|
Chris@0
|
314 FieldConfig::create($this->fieldDefinition)->save();
|
Chris@0
|
315 FieldConfig::create($field_definition_2)->save();
|
Chris@0
|
316 $field_storage->delete();
|
Chris@0
|
317 $this->assertFalse(FieldConfig::loadByName('entity_test', $this->fieldDefinition['bundle'], $field_storage->getName()));
|
Chris@0
|
318 $this->assertFalse(FieldConfig::loadByName('entity_test', $field_definition_2['bundle'], $field_storage->getName()));
|
Chris@0
|
319
|
Chris@0
|
320 // Check that deletion of the last field deletes the storage.
|
Chris@0
|
321 $field_storage = FieldStorageConfig::create($this->fieldStorageDefinition);
|
Chris@0
|
322 $field_storage->save();
|
Chris@0
|
323 $field = FieldConfig::create($this->fieldDefinition);
|
Chris@0
|
324 $field->save();
|
Chris@0
|
325 $field_2 = FieldConfig::create($field_definition_2);
|
Chris@0
|
326 $field_2->save();
|
Chris@0
|
327 $field->delete();
|
Chris@0
|
328 $this->assertTrue(FieldStorageConfig::loadByName('entity_test', $field_storage->getName()));
|
Chris@0
|
329 $field_2->delete();
|
Chris@0
|
330 $this->assertFalse(FieldStorageConfig::loadByName('entity_test', $field_storage->getName()));
|
Chris@0
|
331
|
Chris@0
|
332 // Check that deletion of all fields using a storage simultaneously deletes
|
Chris@0
|
333 // the storage.
|
Chris@0
|
334 $field_storage = FieldStorageConfig::create($this->fieldStorageDefinition);
|
Chris@0
|
335 $field_storage->save();
|
Chris@0
|
336 $field = FieldConfig::create($this->fieldDefinition);
|
Chris@0
|
337 $field->save();
|
Chris@0
|
338 $field_2 = FieldConfig::create($field_definition_2);
|
Chris@0
|
339 $field_2->save();
|
Chris@0
|
340 $this->container->get('entity.manager')->getStorage('field_config')->delete([$field, $field_2]);
|
Chris@0
|
341 $this->assertFalse(FieldStorageConfig::loadByName('entity_test', $field_storage->getName()));
|
Chris@0
|
342 }
|
Chris@0
|
343
|
Chris@0
|
344 }
|