summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNathaniel Catchpole2017-11-10 10:54:05 +0000
committerNathaniel Catchpole2017-11-10 10:54:05 +0000
commitf1adffe2b48fc645fb77e97eca30257b7fa79987 (patch)
treedfe6aa0e0f5ef6174140d1eccef556d4b4bfaff8
parent43812961b6aec39a144a0fa961b820b50f8cb9ee (diff)
Issue #2392845 by amateescu, nuez, seanB, Alumei, penyaskito, vprocessor, steveoliver, lokapujya, tim.plunkett, yched, Sam152, Wim Leers, jibran, kristiaanvandeneynde, Berdir, larowlan, hchonov, fago: Add a trait to standardize handling of computed item lists
-rw-r--r--core/lib/Drupal/Core/TypedData/ComputedItemListTrait.php151
-rw-r--r--core/lib/Drupal/Core/TypedData/Plugin/DataType/ItemList.php3
-rw-r--r--core/modules/system/tests/modules/entity_test/src/Plugin/Field/ComputedTestFieldItemList.php26
-rw-r--r--core/tests/Drupal/KernelTests/Core/Entity/EntityFieldTest.php126
-rw-r--r--core/tests/Drupal/Tests/Listeners/DeprecationListener.php1
5 files changed, 288 insertions, 19 deletions
diff --git a/core/lib/Drupal/Core/TypedData/ComputedItemListTrait.php b/core/lib/Drupal/Core/TypedData/ComputedItemListTrait.php
new file mode 100644
index 0000000..f6660c4
--- /dev/null
+++ b/core/lib/Drupal/Core/TypedData/ComputedItemListTrait.php
@@ -0,0 +1,151 @@
+<?php
+
+namespace Drupal\Core\TypedData;
+
+/**
+ * Provides common functionality for computed item lists.
+ *
+ * @see \Drupal\Core\TypedData\ListInterface
+ * @see \Drupal\Core\TypedData\Plugin\DataType\ItemList
+ * @see \Drupal\Core\Field\FieldItemListInterface
+ * @see \Drupal\Core\Field\FieldItemList
+ *
+ * @ingroup typed_data
+ */
+trait ComputedItemListTrait {
+
+ /**
+ * Whether the values have already been computed or not.
+ *
+ * @var bool
+ */
+ protected $valueComputed = FALSE;
+
+ /**
+ * Computes the values for an item list.
+ */
+ abstract protected function computeValue();
+
+ /**
+ * Ensures that values are only computed once.
+ */
+ protected function ensureComputedValue() {
+ if ($this->valueComputed === FALSE) {
+ $this->computeValue();
+ $this->valueComputed = TRUE;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getValue() {
+ $this->ensureComputedValue();
+ 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->ensureComputedValue();
+ return parent::getString();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($index) {
+ if (!is_numeric($index)) {
+ throw new \InvalidArgumentException('Unable to get a value with a non-numeric delta in a list.');
+ }
+
+ // Unlike the base implementation of
+ // \Drupal\Core\TypedData\ListInterface::get(), we do not add an empty item
+ // automatically because computed item lists need to behave like
+ // non-computed ones. For example, calling isEmpty() on a computed item list
+ // should return TRUE when the values were computed and the item list is
+ // truly empty.
+ // @see \Drupal\Core\TypedData\Plugin\DataType\ItemList::get().
+ $this->ensureComputedValue();
+
+ return isset($this->list[$index]) ? $this->list[$index] : NULL;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($index, $value) {
+ $this->ensureComputedValue();
+ return parent::set($index, $value);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function appendItem($value = NULL) {
+ $this->ensureComputedValue();
+ return parent::appendItem($value);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeItem($index) {
+ $this->ensureComputedValue();
+ return parent::removeItem($index);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isEmpty() {
+ $this->ensureComputedValue();
+ return parent::isEmpty();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function offsetExists($offset) {
+ $this->ensureComputedValue();
+ return parent::offsetExists($offset);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getIterator() {
+ $this->ensureComputedValue();
+ return parent::getIterator();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function count() {
+ $this->ensureComputedValue();
+ return parent::count();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function applyDefaultValue($notify = TRUE) {
+ // Default values do not make sense for computed item lists. However, this
+ // method can be overridden if needed.
+ return $this;
+ }
+
+}
diff --git a/core/lib/Drupal/Core/TypedData/Plugin/DataType/ItemList.php b/core/lib/Drupal/Core/TypedData/Plugin/DataType/ItemList.php
index 4f756da..0d58b71 100644
--- a/core/lib/Drupal/Core/TypedData/Plugin/DataType/ItemList.php
+++ b/core/lib/Drupal/Core/TypedData/Plugin/DataType/ItemList.php
@@ -98,7 +98,10 @@ class ItemList extends TypedData implements \IteratorAggregate, ListInterface {
throw new \InvalidArgumentException('Unable to get a value with a non-numeric delta in a list.');
}
// Automatically create the first item for computed fields.
+ // @deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0.
+ // Use \Drupal\Core\TypedData\ComputedItemListTrait instead.
if ($index == 0 && !isset($this->list[0]) && $this->definition->isComputed()) {
+ @trigger_error('Automatically creating the first item for computed fields is deprecated in Drupal 8.5.x and will be removed before Drupal 9.0.0. Use \Drupal\Core\TypedData\ComputedItemListTrait instead.', E_USER_DEPRECATED);
$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..0dfa3e1 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
@@ -2,6 +2,7 @@
namespace Drupal\entity_test\Plugin\Field;
+use Drupal\Core\TypedData\ComputedItemListTrait;
use Drupal\Core\Field\FieldItemList;
/**
@@ -9,29 +10,20 @@ use Drupal\Core\Field\FieldItemList;
*/
class ComputedTestFieldItemList extends FieldItemList {
+ use ComputedItemListTrait;
+
/**
* Compute the list property from state.
*/
- protected function computedListProperty() {
+ protected function computeValue() {
+ // Count the number of times this method has been executed during the
+ // lifecycle of an entity.
+ $execution_count = \Drupal::state()->get('computed_test_field_execution', 0);
+ \Drupal::state()->set('computed_test_field_execution', ++$execution_count);
+
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..a42a74b 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/EntityFieldTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityFieldTest.php
@@ -739,13 +739,135 @@ class EntityFieldTest extends EntityKernelTestBase {
}
/**
- * Test computed fields.
+ * Tests all the interaction points of a computed field.
*/
public function testComputedFields() {
+ $this->installEntitySchema('entity_test_computed_field');
+
\Drupal::state()->set('entity_test_computed_field_item_list_value', ['foo computed']);
+ // Check that the values are not computed unnecessarily during the lifecycle
+ // of an entity when the field is not interacted with directly.
+ \Drupal::state()->set('computed_test_field_execution', 0);
+ $entity = EntityTestComputedField::create([]);
+ $this->assertSame(0, \Drupal::state()->get('computed_test_field_execution', 0));
+
+ $entity->name->value = $this->randomString();
+ $this->assertSame(0, \Drupal::state()->get('computed_test_field_execution', 0));
+
+ $entity->save();
+ $this->assertSame(0, \Drupal::state()->get('computed_test_field_execution', 0));
+
+ // Test \Drupal\Core\TypedData\ComputedItemListTrait::getValue().
+ \Drupal::state()->set('computed_test_field_execution', 0);
$entity = EntityTestComputedField::create([]);
- $this->assertEquals($entity->computed_string_field->value, 'foo computed');
+ $this->assertSame([['value' => 'foo computed']], $entity->computed_string_field->getValue());
+
+ // Check that the values are only computed once.
+ $this->assertSame(1, \Drupal::state()->get('computed_test_field_execution', 0));
+
+ // Test \Drupal\Core\TypedData\ComputedItemListTrait::setValue(). This also
+ // checks that a subsequent getter does not try to re-compute the value.
+ \Drupal::state()->set('computed_test_field_execution', 0);
+ $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());
+
+ // Check that the values have not been computed when they were explicitly
+ // set.
+ $this->assertSame(0, \Drupal::state()->get('computed_test_field_execution', 0));
+
+ // 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);
+
+ // Add a non-empty item to the list and check that it is not filtered out.
+ $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->assertCount(0, $entity->computed_string_field);
+
+ // Test \Drupal\Core\Field\FieldItemList::equals() for a computed field.
+ \Drupal::state()->set('entity_test_computed_field_item_list_value', ['foo computed']);
+ $entity = EntityTestComputedField::create([]);
+ $computed_item_list1 = $entity->computed_string_field;
+
+ $entity = EntityTestComputedField::create([]);
+ $computed_item_list2 = $entity->computed_string_field;
+
+ $this->assertTrue($computed_item_list1->equals($computed_item_list2));
+
+ $computed_item_list2->value = 'foo computed 2';
+ $this->assertFalse($computed_item_list1->equals($computed_item_list2));
}
/**
diff --git a/core/tests/Drupal/Tests/Listeners/DeprecationListener.php b/core/tests/Drupal/Tests/Listeners/DeprecationListener.php
index a037c02..3673376 100644
--- a/core/tests/Drupal/Tests/Listeners/DeprecationListener.php
+++ b/core/tests/Drupal/Tests/Listeners/DeprecationListener.php
@@ -110,6 +110,7 @@ class DeprecationListener extends \PHPUnit_Framework_BaseTestListener {
'The Drupal\config_translation\Plugin\migrate\source\d6\I18nProfileField is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, use Drupal\config_translation\Plugin\migrate\source\d6\ProfileFieldTranslation',
'The Drupal\migrate_drupal\Plugin\migrate\source\d6\i18nVariable is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, use Drupal\migrate_drupal\Plugin\migrate\source\d6\VariableTranslation',
'Implicit cacheability metadata bubbling (onto the global render context) in normalizers is deprecated since Drupal 8.5.0 and will be removed in Drupal 9.0.0. Use the "cacheability" serialization context instead, for explicit cacheability metadata bubbling. See https://www.drupal.org/node/2918937',
+ 'Automatically creating the first item for computed fields is deprecated in Drupal 8.5.x and will be removed before Drupal 9.0.0. Use \Drupal\Core\TypedData\ComputedItemListTrait instead.',
];
}