diff --git a/core/lib/Drupal/Core/TypedData/ComputedItemListTrait.php b/core/lib/Drupal/Core/TypedData/ComputedItemListTrait.php new file mode 100644 index 0000000..92ad1e5 --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/ComputedItemListTrait.php @@ -0,0 +1,148 @@ +valueComputed === FALSE) { + $this->computeValue(); + $this->valueComputed = TRUE; + } + } + + /** + * {@inheritdoc} + */ + public function getValue() { + $this->doComputeValue(); + return parent::getValue(); + } + + /** + * {@inheritdoc} + */ + public function setValue($values, $notify = TRUE) { + parent::setValue($values, $notify); + + // Make sure that subsequent getter calls do not try to compute the values + // again. + $this->valueComputed = TRUE; + } + + /** + * {@inheritdoc} + */ + public function getString() { + $this->doComputeValue(); + return parent::getString(); + } + + /** + * {@inheritdoc} + */ + public function get($index) { + $this->doComputeValue(); + return parent::get($index); + } + + /** + * {@inheritdoc} + */ + public function set($index, $value) { + // Ensure that existing values are loaded when setting a value, this also + // ensures that it is possible to set a new value immediately after loading + // an entity. + $this->doComputeValue(); + return parent::set($index, $value); + } + + /** + * {@inheritdoc} + */ + public function appendItem($value = NULL) { + $this->doComputeValue(); + return parent::appendItem($value); + } + + /** + * {@inheritdoc} + */ + public function removeItem($index) { + $this->doComputeValue(); + return parent::removeItem($index); + } + + /** + * {@inheritdoc} + */ + public function isEmpty() { + $this->doComputeValue(); + return parent::isEmpty(); + } + + /** + * {@inheritdoc} + */ + public function filter($callback) { + $this->doComputeValue(); + return parent::filter($callback); + } + + /** + * {@inheritdoc} + */ + public function offsetExists($offset) { + $this->doComputeValue(); + return parent::offsetExists($offset); + } + + /** + * {@inheritdoc} + */ + public function getIterator() { + $this->doComputeValue(); + return parent::getIterator(); + } + + /** + * {@inheritdoc} + */ + public function count() { + $this->doComputeValue(); + return parent::count(); + } + + /** + * {@inheritdoc} + */ + public function applyDefaultValue($notify = TRUE) { + // Default values do not make sense for computed fields. However, this + // method can be overridden if needed. + return; + } + +} diff --git a/core/lib/Drupal/Core/TypedData/Plugin/DataType/ItemList.php b/core/lib/Drupal/Core/TypedData/Plugin/DataType/ItemList.php index 4f756da..8bec55c 100644 --- a/core/lib/Drupal/Core/TypedData/Plugin/DataType/ItemList.php +++ b/core/lib/Drupal/Core/TypedData/Plugin/DataType/ItemList.php @@ -97,10 +97,6 @@ public function get($index) { if (!is_numeric($index)) { throw new \InvalidArgumentException('Unable to get a value with a non-numeric delta in a list.'); } - // Automatically create the first item for computed fields. - if ($index == 0 && !isset($this->list[0]) && $this->definition->isComputed()) { - $this->list[0] = $this->createItem(0); - } return isset($this->list[$index]) ? $this->list[$index] : NULL; } diff --git a/core/modules/system/tests/modules/entity_test/src/Plugin/Field/ComputedTestFieldItemList.php b/core/modules/system/tests/modules/entity_test/src/Plugin/Field/ComputedTestFieldItemList.php index 864d33f..cacc6e0 100644 --- a/core/modules/system/tests/modules/entity_test/src/Plugin/Field/ComputedTestFieldItemList.php +++ b/core/modules/system/tests/modules/entity_test/src/Plugin/Field/ComputedTestFieldItemList.php @@ -3,35 +3,22 @@ namespace Drupal\entity_test\Plugin\Field; use Drupal\Core\Field\FieldItemList; +use Drupal\Core\TypedData\ComputedItemListTrait; /** * A computed field item list. */ class ComputedTestFieldItemList extends FieldItemList { + use ComputedItemListTrait; + /** * Compute the list property from state. */ - protected function computedListProperty() { + protected function computeValue() { foreach (\Drupal::state()->get('entity_test_computed_field_item_list_value', []) as $delta => $item) { $this->list[$delta] = $this->createItem($delta, $item); } } - /** - * {@inheritdoc} - */ - public function get($index) { - $this->computedListProperty(); - return isset($this->list[$index]) ? $this->list[$index] : NULL; - } - - /** - * {@inheritdoc} - */ - public function getIterator() { - $this->computedListProperty(); - return parent::getIterator(); - } - } diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityFieldTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityFieldTest.php index 31b1f9e..a528db8 100644 --- a/core/tests/Drupal/KernelTests/Core/Entity/EntityFieldTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityFieldTest.php @@ -739,13 +739,100 @@ public function testComputedProperties() { } /** - * Test computed fields. + * Tests all the interaction points of a computed field. + * + * @group failing */ public function testComputedFields() { \Drupal::state()->set('entity_test_computed_field_item_list_value', ['foo computed']); + // Test \Drupal\Core\TypedData\ComputedItemListTrait::getValue(). + $entity = EntityTestComputedField::create([]); + $this->assertSame([['value' => 'foo computed']], $entity->computed_string_field->getValue()); + + // Test \Drupal\Core\TypedData\ComputedItemListTrait::setValue(). This also + // checks that a subsequent getter does not try to re-compute the value. + $entity = EntityTestComputedField::create([]); + $entity->computed_string_field->setValue([ + ['value' => 'foo computed 1'], + ['value' => 'foo computed 2'], + ]); + $this->assertSame([['value' => 'foo computed 1'], ['value' => 'foo computed 2']], $entity->computed_string_field->getValue()); + + // Test \Drupal\Core\TypedData\ComputedItemListTrait::getString(). + $entity = EntityTestComputedField::create([]); + $this->assertSame('foo computed', $entity->computed_string_field->getString()); + + // Test \Drupal\Core\TypedData\ComputedItemListTrait::get(). + $entity = EntityTestComputedField::create([]); + $this->assertSame('foo computed', $entity->computed_string_field->get(0)->value); + $this->assertEmpty($entity->computed_string_field->get(1)); + + // Test \Drupal\Core\TypedData\ComputedItemListTrait::set(). + $entity = EntityTestComputedField::create([]); + $entity->computed_string_field->set(1, 'foo computed 1'); + $this->assertSame('foo computed', $entity->computed_string_field[0]->value); + $this->assertSame('foo computed 1', $entity->computed_string_field[1]->value); + $entity->computed_string_field->set(0, 'foo computed 0'); + $this->assertSame('foo computed 0', $entity->computed_string_field[0]->value); + $this->assertSame('foo computed 1', $entity->computed_string_field[1]->value); + + // Test \Drupal\Core\TypedData\ComputedItemListTrait::appendItem(). + $entity = EntityTestComputedField::create([]); + $entity->computed_string_field->appendItem('foo computed 1'); + $this->assertSame('foo computed', $entity->computed_string_field[0]->value); + $this->assertSame('foo computed 1', $entity->computed_string_field[1]->value); + + // Test \Drupal\Core\TypedData\ComputedItemListTrait::removeItem(). + $entity = EntityTestComputedField::create([]); + $entity->computed_string_field->removeItem(0); + $this->assertTrue($entity->computed_string_field->isEmpty()); + + // Test \Drupal\Core\TypedData\ComputedItemListTrait::isEmpty(). + \Drupal::state()->set('entity_test_computed_field_item_list_value', []); + $entity = EntityTestComputedField::create([]); + $this->assertTrue($entity->computed_string_field->isEmpty()); + + \Drupal::state()->set('entity_test_computed_field_item_list_value', ['foo computed']); + $entity = EntityTestComputedField::create([]); + $this->assertFalse($entity->computed_string_field->isEmpty()); + + // Test \Drupal\Core\TypedData\ComputedItemListTrait::filter(). + $filter_callback = function ($item) { + return !$item->isEmpty(); + }; + $entity = EntityTestComputedField::create([]); + $entity->computed_string_field->filter($filter_callback); + $this->assertCount(1, $entity->computed_string_field); + + // Add an empty item to the list and check that it is filtered out. + $entity->computed_string_field->appendItem(); + $entity->computed_string_field->filter($filter_callback); + $this->assertCount(1, $entity->computed_string_field); + + $entity->computed_string_field->appendItem('foo computed 1'); + $entity->computed_string_field->filter($filter_callback); + $this->assertCount(2, $entity->computed_string_field); + + // Test \Drupal\Core\TypedData\ComputedItemListTrait::offsetExists(). + $entity = EntityTestComputedField::create([]); + $this->assertTrue($entity->computed_string_field->offsetExists(0)); + $this->assertFalse($entity->computed_string_field->offsetExists(1)); + + // Test \Drupal\Core\TypedData\ComputedItemListTrait::getIterator(). + $entity = EntityTestComputedField::create([]); + foreach ($entity->computed_string_field as $delta => $item) { + $this->assertSame('foo computed', $item->value); + } + + // Test \Drupal\Core\TypedData\ComputedItemListTrait::count(). + $entity = EntityTestComputedField::create([]); + $this->assertCount(1, $entity->computed_string_field); + + // Check that computed items are not auto-created when they have no values. + \Drupal::state()->set('entity_test_computed_field_item_list_value', []); $entity = EntityTestComputedField::create([]); - $this->assertEquals($entity->computed_string_field->value, 'foo computed'); + $this->assertCount(0, $entity->computed_string_field); } /**