Issue #2365585 by yched: FieldItemList::filterEmptyItems() renumbers deltas but does not update the Items

8.0.x
Alex Pott 2014-12-01 11:06:46 +00:00
parent 36122d4cfc
commit e55a223e8f
6 changed files with 98 additions and 6 deletions

View File

@ -89,4 +89,13 @@ class Sequence extends ArrayElement implements ListInterface {
return $this; return $this;
} }
/**
* {@inheritdoc}
*/
public function filter($callback) {
$this->value = array_filter($this->value, $callback);
unset($this->elements);
return $this;
}
} }

View File

@ -101,11 +101,9 @@ class FieldItemList extends ItemList implements FieldItemListInterface {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function filterEmptyItems() { public function filterEmptyItems() {
if (isset($this->list)) { $this->filter(function ($item) {
$this->list = array_values(array_filter($this->list, function($item) { return !$item->isEmpty();
return !$item->isEmpty(); });
}));
}
} }
/** /**

View File

@ -80,4 +80,16 @@ interface ListInterface extends TraversableTypedDataInterface, \ArrayAccess, \Co
*/ */
public function first(); public function first();
/**
* Filters the items in the list using a custom callback.
*
* @param callable $callback
* The callback to use for filtering. Like with array_filter(), the
* callback is called for each item in the list. Only items for which the
* callback returns TRUE are preserved.
*
* @return $this
*/
public function filter($callback);
} }

View File

@ -229,6 +229,33 @@ class ItemList extends TypedData implements \IteratorAggregate, ListInterface {
return TRUE; return TRUE;
} }
/**
* {@inheritdoc}
*/
public function filter($callback) {
if (isset($this->list)) {
$removed = FALSE;
// Apply the filter, detecting if some items were actually removed.
$this->list = array_filter($this->list, function ($item) use ($callback, &$removed) {
if (call_user_func($callback, $item)) {
return TRUE;
}
else {
$removed = TRUE;
}
});
if ($removed) {
// Rekey the array using array_values().
$this->list = array_values($this->list);
// Manually update each item's delta.
foreach ($this->list as $delta => $item) {
$item->setContext($delta, $this);
}
}
}
return $this;
}
/** /**
* Implements \Drupal\Core\TypedData\ListInterface::onChange(). * Implements \Drupal\Core\TypedData\ListInterface::onChange().
*/ */

View File

@ -265,6 +265,14 @@ class EntityFieldTest extends EntityUnitTestBase {
$this->assertEqual(count(iterator_to_array($entity->name->getIterator())), count($entity->name), format_string('%entity_type: Count matches iterator count.', array('%entity_type' => $entity_type))); $this->assertEqual(count(iterator_to_array($entity->name->getIterator())), count($entity->name), format_string('%entity_type: Count matches iterator count.', array('%entity_type' => $entity_type)));
$this->assertTrue($entity->name->getValue() === array(0 => array('value' => NULL)), format_string('%entity_type: Name field value contains a NULL value.', array('%entity_type' => $entity_type))); $this->assertTrue($entity->name->getValue() === array(0 => array('value' => NULL)), format_string('%entity_type: Name field value contains a NULL value.', array('%entity_type' => $entity_type)));
// Test using filterEmptyItems().
$entity->name = array(NULL, 'foo');
$this->assertEqual(count($entity->name), 2, format_string('%entity_type: List has 2 items.', array('%entity_type' => $entity_type)));
$entity->name->filterEmptyItems();
$this->assertEqual(count($entity->name), 1, format_string('%entity_type: The empty item was removed.', array('%entity_type' => $entity_type)));
$this->assertEqual($entity->name[0]->value, 'foo', format_string('%entity_type: The items were renumbered.', array('%entity_type' => $entity_type)));
$this->assertEqual($entity->name[0]->getName(), 0, format_string('%entity_type: The deltas were updated in the items.', array('%entity_type' => $entity_type)));
// Test removing all list items by assigning an empty array. // Test removing all list items by assigning an empty array.
$entity->name = array(); $entity->name = array();
$this->assertIdentical(count($entity->name), 0, format_string('%entity_type: Name field contains no items.', array('%entity_type' => $entity_type))); $this->assertIdentical(count($entity->name), 0, format_string('%entity_type: Name field contains no items.', array('%entity_type' => $entity_type)));

View File

@ -11,6 +11,7 @@ use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\TypedData\DataDefinition; use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\TypedData\ListDataDefinition; use Drupal\Core\TypedData\ListDataDefinition;
use Drupal\Core\TypedData\MapDataDefinition; use Drupal\Core\TypedData\MapDataDefinition;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\simpletest\DrupalUnitTestBase; use Drupal\simpletest\DrupalUnitTestBase;
use Drupal\Core\Datetime\DrupalDateTime; use Drupal\Core\Datetime\DrupalDateTime;
@ -318,7 +319,7 @@ class TypedDataTest extends DrupalUnitTestBase {
// Test iterating. // Test iterating.
$count = 0; $count = 0;
foreach ($typed_data as $item) { foreach ($typed_data as $item) {
$this->assertTrue($item instanceof \Drupal\Core\TypedData\TypedDataInterface); $this->assertTrue($item instanceof TypedDataInterface);
$count++; $count++;
} }
$this->assertEqual($count, 3); $this->assertEqual($count, 3);
@ -394,6 +395,43 @@ class TypedDataTest extends DrupalUnitTestBase {
} }
} }
/**
* Tests the filter() method on typed data lists.
*/
public function testTypedDataListsFilter() {
// Check that an all-pass filter leaves the list untouched.
$value = array('zero', 'one');
$typed_data = $this->createTypedData(ListDataDefinition::create('string'), $value);
$typed_data->filter(function(TypedDataInterface $item) {
return TRUE;
});
$this->assertEqual($typed_data->count(), 2);
$this->assertEqual($typed_data[0]->getValue(), 'zero');
$this->assertEqual($typed_data[0]->getName(), 0);
$this->assertEqual($typed_data[1]->getValue(), 'one');
$this->assertEqual($typed_data[1]->getName(), 1);
// Check that a none-pass filter empties the list.
$value = array('zero', 'one');
$typed_data = $this->createTypedData(ListDataDefinition::create('string'), $value);
$typed_data->filter(function(TypedDataInterface $item) {
return FALSE;
});
$this->assertEqual($typed_data->count(), 0);
// Check that filtering correctly renumbers elements.
$value = array('zero', 'one', 'two');
$typed_data = $this->createTypedData(ListDataDefinition::create('string'), $value);
$typed_data->filter(function(TypedDataInterface $item) {
return $item->getValue() !== 'one';
});
$this->assertEqual($typed_data->count(), 2);
$this->assertEqual($typed_data[0]->getValue(), 'zero');
$this->assertEqual($typed_data[0]->getName(), 0);
$this->assertEqual($typed_data[1]->getValue(), 'two');
$this->assertEqual($typed_data[1]->getName(), 1);
}
/** /**
* Tests using a typed data map. * Tests using a typed data map.
*/ */