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