Commit 3f20ff5f authored by catch's avatar catch

Issue #2906600 by amateescu, Wim Leers, tstoeckler, cburschka:...

Issue #2906600 by amateescu, Wim Leers, tstoeckler, cburschka: FieldItemList::equals() doesn't work correctly for computed fields with custom storage
parent 300d00ab
......@@ -7,6 +7,7 @@
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TypedData\DataDefinitionInterface;
use Drupal\Core\TypedData\Plugin\DataType\ItemList;
/**
......@@ -378,7 +379,6 @@ protected function defaultValueWidget(FormStateInterface $form_state) {
* {@inheritdoc}
*/
public function equals(FieldItemListInterface $list_to_compare) {
$columns = $this->getFieldDefinition()->getFieldStorageDefinition()->getColumns();
$count1 = count($this);
$count2 = count($list_to_compare);
if ($count1 === 0 && $count2 === 0) {
......@@ -396,9 +396,13 @@ public function equals(FieldItemListInterface $list_to_compare) {
}
// If the values are not equal ensure a consistent order of field item
// properties and remove properties which will not be saved.
$callback = function (&$value) use ($columns) {
$property_definitions = $this->getFieldDefinition()->getFieldStorageDefinition()->getPropertyDefinitions();
$non_computed_properties = array_filter($property_definitions, function (DataDefinitionInterface $property) {
return !$property->isComputed();
});
$callback = function (&$value) use ($non_computed_properties) {
if (is_array($value)) {
$value = array_intersect_key($value, $columns);
$value = array_intersect_key($value, $non_computed_properties);
ksort($value);
}
};
......
<?php
namespace Drupal\Core\Field;
/**
* Defines a item list class for map fields.
*/
class MapFieldItemList extends FieldItemList {
/**
* {@inheritdoc}
*/
public function equals(FieldItemListInterface $list_to_compare) {
$count1 = count($this);
$count2 = count($list_to_compare);
if ($count1 === 0 && $count2 === 0) {
// Both are empty we can safely assume that it did not change.
return TRUE;
}
if ($count1 !== $count2) {
// The number of items is different so they do not have the same values.
return FALSE;
}
// The map field type does not have any property defined (because they are
// dynamic), so the only way to evaluate the equality for it is to rely
// solely on its values.
$value1 = $this->getValue();
$value2 = $list_to_compare->getValue();
return $value1 == $value2;
}
}
......@@ -12,7 +12,8 @@
* id = "map",
* label = @Translation("Map"),
* description = @Translation("An entity field for storing a serialized array of values."),
* no_ui = TRUE
* no_ui = TRUE,
* list_class = "\Drupal\Core\Field\MapFieldItemList",
* )
*/
class MapItem extends FieldItemBase {
......
......@@ -171,6 +171,24 @@ public function testPathItem() {
$this->assertEquals('/foobar', $loaded_node->get('path')->alias);
$stored_alias = $alias_storage->lookupPathAlias('/' . $node->toUrl()->getInternalPath(), $node->language()->getId());
$this->assertEquals('/foobar', $stored_alias);
// Check that \Drupal\Core\Field\FieldItemList::equals() for the path field
// type.
$node = Node::create([
'title' => $this->randomString(),
'type' => 'foo',
'path' => ['alias' => '/foo'],
]);
$second_node = Node::create([
'title' => $this->randomString(),
'type' => 'foo',
'path' => ['alias' => '/foo'],
]);
$this->assertTrue($node->get('path')->equals($second_node->get('path')));
// Change the alias for the second node to a different one and try again.
$second_node->get('path')->alias = '/foobar';
$this->assertFalse($node->get('path')->equals($second_node->get('path')));
}
}
......@@ -28,10 +28,24 @@ public function testEquals($expected, FieldItemInterface $first_field_item = NUL
$container->set('plugin.manager.field.field_type', $field_type_manager);
\Drupal::setContainer($container);
// Set up three properties, one of them being computed.
$property_definitions['0'] = $this->getMock('Drupal\Core\TypedData\DataDefinitionInterface');
$property_definitions['0']->expects($this->any())
->method('isComputed')
->willReturn(FALSE);
$property_definitions['1'] = $this->getMock('Drupal\Core\TypedData\DataDefinitionInterface');
$property_definitions['1']->expects($this->any())
->method('isComputed')
->willReturn(FALSE);
$property_definitions['2'] = $this->getMock('Drupal\Core\TypedData\DataDefinitionInterface');
$property_definitions['2']->expects($this->any())
->method('isComputed')
->willReturn(TRUE);
$field_storage_definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
$field_storage_definition->expects($this->any())
->method('getColumns')
->willReturn([0 => '0', 1 => '1']);
->method('getPropertyDefinitions')
->will($this->returnValue($property_definitions));
$field_definition = $this->getMock('Drupal\Core\Field\FieldDefinitionInterface');
$field_definition->expects($this->any())
->method('getFieldStorageDefinition')
......@@ -95,6 +109,30 @@ public function providerTestEquals() {
// types.
$datasets[] = [TRUE, $field_item_b, $field_item_e];
/** @var \Drupal\Core\Field\FieldItemBase $field_item_f */
$field_item_f = $this->getMockForAbstractClass('Drupal\Core\Field\FieldItemBase', [], '', FALSE);
$field_item_f->setValue(['0' => 1, '1' => 2, '2' => 3]);
/** @var \Drupal\Core\Field\FieldItemBase $field_item_g */
$field_item_g = $this->getMockForAbstractClass('Drupal\Core\Field\FieldItemBase', [], '', FALSE);
$field_item_g->setValue(['0' => 1, '1' => 2, '2' => 4]);
// Tests field item lists where both have same values for the non-computed
// properties ('0' and '1') and a different value for the computed one
// ('2').
$datasets[] = [TRUE, $field_item_f, $field_item_g];
/** @var \Drupal\Core\Field\FieldItemBase $field_item_h */
$field_item_h = $this->getMockForAbstractClass('Drupal\Core\Field\FieldItemBase', [], '', FALSE);
$field_item_h->setValue(['0' => 1, '1' => 2, '3' => 3]);
/** @var \Drupal\Core\Field\FieldItemBase $field_item_i */
$field_item_i = $this->getMockForAbstractClass('Drupal\Core\Field\FieldItemBase', [], '', FALSE);
$field_item_i->setValue(['0' => 1, '1' => 2, '3' => 4]);
// Tests field item lists where both have same values for the non-computed
// properties ('0' and '1') and a different value for a property that does
// not exist ('3').
$datasets[] = [TRUE, $field_item_h, $field_item_i];
return $datasets;
}
......@@ -114,10 +152,20 @@ public function testEqualsEmptyItems() {
$container->set('plugin.manager.field.field_type', $field_type_manager);
\Drupal::setContainer($container);
// Set up the properties of the field item.
$property_definitions['0'] = $this->getMock('Drupal\Core\TypedData\DataDefinitionInterface');
$property_definitions['0']->expects($this->any())
->method('isComputed')
->willReturn(FALSE);
$property_definitions['1'] = $this->getMock('Drupal\Core\TypedData\DataDefinitionInterface');
$property_definitions['1']->expects($this->any())
->method('isComputed')
->willReturn(FALSE);
$field_storage_definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
$field_storage_definition->expects($this->any())
->method('getColumns')
->willReturn([0 => '0', 1 => '1']);
->method('getPropertyDefinitions')
->will($this->returnValue($property_definitions));
$field_definition = $this->getMock('Drupal\Core\Field\FieldDefinitionInterface');
$field_definition->expects($this->any())
->method('getFieldStorageDefinition')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment