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