diff --git a/Cake/Test/TestCase/Utility/CollectionTest.php b/Cake/Test/TestCase/Utility/CollectionTest.php index 56bf34cca4e..2fb99f77575 100644 --- a/Cake/Test/TestCase/Utility/CollectionTest.php +++ b/Cake/Test/TestCase/Utility/CollectionTest.php @@ -241,4 +241,18 @@ public function testReduce() { ->will($this->returnValue(16)); $this->assertEquals(16, $collection->reduce($callable, 10)); } + +/** + * Tests extract + * + * @return void + */ + public function testExtract() { + $items = [['a' => ['b' => ['c' => 1]]], 2]; + $collection = new Collection($items); + $map = $collection->extract('a.b.c'); + $this->assertInstanceOf('\Cake\Utility\Iterator\ExtractIterator', $map); + $this->assertEquals([1, null], iterator_to_array($map)); + } + } diff --git a/Cake/Test/TestCase/Utility/Iterator/ExtractIteratorTest.php b/Cake/Test/TestCase/Utility/Iterator/ExtractIteratorTest.php new file mode 100644 index 00000000000..308e7b7d309 --- /dev/null +++ b/Cake/Test/TestCase/Utility/Iterator/ExtractIteratorTest.php @@ -0,0 +1,83 @@ + 1, 'b' =>2], + ['a' => 3, 'b' => 4] + ]; + $extractor = new ExtractIterator($items, 'a'); + $this->assertEquals([1, 3], iterator_to_array($extractor)); + + $extractor = new ExtractIterator($items, 'b'); + $this->assertEquals([2, 4], iterator_to_array($extractor)); + + $extractor = new ExtractIterator($items, 'c'); + $this->assertEquals([null, null], iterator_to_array($extractor)); + } + +/** + * Tests it is possible to extract a column in the first level of an object + * + * @return void + */ + public function testExtractFromObjectShallow() { + $items = [ + new ArrayObject(['a' => 1, 'b' =>2]), + new ArrayObject(['a' => 3, 'b' => 4]) + ]; + $extractor = new ExtractIterator($items, 'a'); + $this->assertEquals([1, 3], iterator_to_array($extractor)); + + $extractor = new ExtractIterator($items, 'b'); + $this->assertEquals([2, 4], iterator_to_array($extractor)); + + $extractor = new ExtractIterator($items, 'c'); + $this->assertEquals([null, null], iterator_to_array($extractor)); + } + +/** + * Tests it is possible to extract a column deeply nested in the structure + * + * @return void + */ + public function testExtractFromArrayDeep() { + $items = [ + ['a' => ['b' => ['c' => 10]], 'b' =>2], + ['a' => ['b' => ['d' => 15]], 'b' => 4], + ['a' => ['x' => ['z' => 20]], 'b' => 4], + ['a' => ['b' => ['c' => 25]], 'b' =>2], + ]; + $extractor = new ExtractIterator($items, 'a.b.c'); + $this->assertEquals([10, null, null, 25], iterator_to_array($extractor)); + } + +} diff --git a/Cake/Utility/Collection.php b/Cake/Utility/Collection.php index 55bfc5b8e9d..ab2009f1349 100644 --- a/Cake/Utility/Collection.php +++ b/Cake/Utility/Collection.php @@ -15,6 +15,7 @@ namespace Cake\Utility; use ArrayIterator; +use Cake\Utility\Iterator\ExtractIterator; use Cake\Utility\Iterator\FilterIterator; use Cake\Utility\Iterator\ReplaceIterator; use InvalidArgumentException; @@ -241,12 +242,34 @@ public function reduce(callable $c, $zero) { return $result; } - public function mapReduce(callable $map, callable $reduce) { - } - - - public function extract($property) { - +/** + * Returns a new collection containing the column or property value found in each + * of th elements, as requested in the $matcher param. + * + * The matcher can be a string with a property name to extract or a dot separated + * path of properties that should be followed to get the last one in the path. + * + * If a column or property could not be found for a particular element in the + * collection, that position is filled with null. + * + * ### Example: + * + * Extract the user name for all comments in the array: + * + * {{{ + * $items = [ + * ['comment' => ['body' => 'cool', 'user' => ['name' => 'Mark']], + * ['comment' => ['body' => 'very cool', 'user' => ['name' => 'Renan']] + * ]; + * $extractor = new ExtractIterator($items, 'comment.user.name''); + * }}} + * + * @param string $path a dot separated string symbolizing the path to follow + * inside the hierarchy of each value so that the column can be extracted. + * @return \Cake\Utility\Iterator\ExtractIterator + */ + public function extract($matcher) { + return new ExtractIterator($this, $matcher); } public function max() { diff --git a/Cake/Utility/Iterator/ExtractIterator.php b/Cake/Utility/Iterator/ExtractIterator.php new file mode 100644 index 00000000000..430072d92e4 --- /dev/null +++ b/Cake/Utility/Iterator/ExtractIterator.php @@ -0,0 +1,79 @@ + ['body' => 'cool', 'user' => ['name' => 'Mark']], + * ['comment' => ['body' => 'very cool', 'user' => ['name' => 'Renan']] + * ]; + * $extractor = new ExtractIterator($items, 'comment.user.name''); + * }}} + * + * @param array|\Traversable $items The list of values to iterate + * @param string $path a dot separated string symbolizing the path to follow + * inside the hierarchy of each value so that the column can be extracted. + * @return void + */ + public function __construct($items, $path) { + $this->_path = explode('.', $path); + parent::__construct($items); + } + +/** + * Returns the column value defined in $path or null if the path could not be + * followed + * + * @return mixed + */ + public function current() { + $current = parent::current(); + $value = null; + foreach ($this->_path as $column) { + if (!isset($current[$column])) { + return null; + } + $value = $current[$column]; + $current = $value; + } + return $value; + } + +}