diff --git a/Cake/Collection/CollectionTrait.php b/Cake/Collection/CollectionTrait.php index 7da7820dde1..2de52c75fea 100644 --- a/Cake/Collection/CollectionTrait.php +++ b/Cake/Collection/CollectionTrait.php @@ -649,4 +649,40 @@ public function jsonSerialize() { return $this->toArray(); } +/** + * Iterates once all elements in this collection and executes all stacked + * operations of them, finally it returns a new collection with the result. + * This is useful for converting non-rewindable internal iterators into + * a collection that can be rewound and used multiple times. + * + * A common use case is to re-use the same variable for calculating different + * data. In those cases it may be helpful and more performant to first compile + * a collection and then apply more operations to it. + * + * ### Example: + * + * {{{ + * $collection->map($mapper)->sortBy('age')->extract('name'); + * $compiled = $collection->compile(); + * $isJohnHere = $compiled->some($johnMatcher); + * $allButJohn = $compiled->filter($johnMatcher); + * }}} + * + * In the above example, had not the collection compiled before, the iterations + * for `map`, `sortBy` and `extract` would've been executed twice: once for + * getting `$isJohnHere` and once for `$allButJohn` + * + * You can think of this method as a way to create save points for complex + * calculations in a collection. + * + * @param boolean $preserveKeys whether to use the keys returned by this + * collection as the array keys. Keep in mind that it is valid for iterators + * to return the same key for different elements, setting this value to false + * can help getting all items if keys are not important in the result. + * @return \Cake\Collection\Collection + */ + public function compile($preserveKeys = true) { + return new Collection($this->toArray($preserveKeys)); + } + } diff --git a/Cake/Test/TestCase/Collection/CollectionTest.php b/Cake/Test/TestCase/Collection/CollectionTest.php index accce154131..b454cbb5d2f 100644 --- a/Cake/Test/TestCase/Collection/CollectionTest.php +++ b/Cake/Test/TestCase/Collection/CollectionTest.php @@ -604,4 +604,31 @@ public function testAppend() { $this->assertEquals(['a' => 4, 'b' => 2, 'c' => 3], $combined->toArray()); } +/** + * Tests that by calling compile internal iteration operations are not done + * more than once + * + * @return void + */ + public function testCompile() { + $items = ['a' => 1, 'b' => 2, 'c' => 3]; + $collection = new Collection($items); + $callable = $this->getMock('stdClass', ['__invoke']); + $callable->expects($this->at(0)) + ->method('__invoke') + ->with(1, 'a') + ->will($this->returnValue(4)); + $callable->expects($this->at(1)) + ->method('__invoke') + ->with(2, 'b') + ->will($this->returnValue(5)); + $callable->expects($this->at(2)) + ->method('__invoke') + ->with(3, 'c') + ->will($this->returnValue(6)); + $compiled = $collection->map($callable)->compile(); + $this->assertEquals(['a' => 4, 'b' => 5, 'c' => 6], $compiled->toArray()); + $this->assertEquals(['a' => 4, 'b' => 5, 'c' => 6], $compiled->toArray()); + } + }