Skip to content

Commit d78a57d

Browse files
committed
Fix nested iteration on collections
1 parent b0ea7d3 commit d78a57d

2 files changed

Lines changed: 78 additions & 75 deletions

File tree

test/Mad/Model/CollectionTest.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,52 @@ public function testDoubleIterationModels()
273273
$this->assertEquals(6, $j);
274274
}
275275

276+
/*##########################################################################
277+
# Nested Iteration
278+
##########################################################################*/
279+
280+
// nested foreach over the same collection must not corrupt the outer loop
281+
public function testNestedForeachResults()
282+
{
283+
$outerIds = [];
284+
foreach ($this->_results as $outer) {
285+
$outerIds[] = $outer->id;
286+
// inner loop iterates the same collection
287+
foreach ($this->_results as $inner) {
288+
// just iterate
289+
}
290+
}
291+
$this->assertEquals(6, count($outerIds));
292+
$this->assertEquals('1', $outerIds[0]);
293+
$this->assertEquals('6', $outerIds[5]);
294+
}
295+
296+
public function testNestedForeachModels()
297+
{
298+
$outerIds = [];
299+
foreach ($this->_models as $outer) {
300+
$outerIds[] = $outer->id;
301+
foreach ($this->_models as $inner) {
302+
// just iterate
303+
}
304+
}
305+
$this->assertEquals(6, count($outerIds));
306+
$this->assertEquals('1', $outerIds[0]);
307+
$this->assertEquals('6', $outerIds[5]);
308+
}
309+
310+
// iterating while calling a method that also iterates the collection
311+
public function testIterationWithCountDuringLoop()
312+
{
313+
$items = [];
314+
foreach ($this->_results as $item) {
315+
$items[] = $item;
316+
// count() shouldn't affect the foreach
317+
$this->assertEquals(6, count($this->_results));
318+
}
319+
$this->assertEquals(6, count($items));
320+
}
321+
276322
/*##########################################################################
277323
# ArrayAccess Interface
278324
##########################################################################*/

vendor/Mad/Model/Collection.php

Lines changed: 32 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
* @copyright (c) 2007-2009 Maintainable Software, LLC
2020
* @license http://opensource.org/licenses/bsd-license.php BSD
2121
*/
22-
class Mad_Model_Collection implements ArrayAccess, Iterator, Countable
22+
class Mad_Model_Collection implements ArrayAccess, IteratorAggregate, Countable
2323
{
2424
/**
2525
* The {@link Mad_Model_Base} class used to instantiate new objects
@@ -34,12 +34,6 @@ class Mad_Model_Collection implements ArrayAccess, Iterator, Countable
3434
*/
3535
protected $_collection = [];
3636

37-
/**
38-
* The position in the iterator over the objects.
39-
* @var int
40-
*/
41-
protected $_position = 0;
42-
4337

4438
/*##########################################################################
4539
# Construct/Destruct
@@ -86,16 +80,16 @@ public function __toString()
8680
/*##########################################################################
8781
# Accessors
8882
##########################################################################*/
89-
83+
9084
public function getCollection()
9185
{
9286
return $this->_collection;
9387
}
9488

95-
/**
89+
/**
9690
* Serialize the collection to XML.
9791
*/
98-
public function toXml($options = [])
92+
public function toXml($options = [])
9993
{
10094
if (!isset($options['root'])) {
10195
$options['root'] = Mad_Support_Inflector::pluralize(get_class($this->_model));
@@ -134,90 +128,53 @@ public function count()
134128

135129

136130
/*##########################################################################
137-
# Iterator Interface
131+
# IteratorAggregate Interface
138132
##########################################################################*/
139133

140134
/**
141-
* Get the current object from the collection
135+
* Return a new iterator for each foreach loop. This allows nested
136+
* iteration over the same collection without corrupting position.
142137
*
143-
* <code>
144-
* <?php
145-
* ...
146-
* current($folders);
147-
* ...
148-
* ?>
149-
* </code>
150-
* @return Mad_Model_Base
138+
* @return ArrayIterator
151139
*/
152140
#[\ReturnTypeWillChange]
153-
public function current()
141+
public function getIterator()
154142
{
155-
return $this->offsetGet($this->_position);
143+
return new ArrayIterator($this->_collection);
156144
}
157145

146+
147+
/*##########################################################################
148+
# Direct iteration methods (backwards compatibility)
149+
##########################################################################*/
150+
158151
/**
159-
* Get the current position in the Collection
160-
*
161-
* <code>
162-
* <?php
163-
* ...
164-
* key($folders);
165-
* ...
166-
* ?>
167-
* </code>
168-
*
169-
* @return int
152+
* @var int
170153
*/
171-
#[\ReturnTypeWillChange]
154+
protected $_position = 0;
155+
156+
public function current()
157+
{
158+
return $this->offsetGet($this->_position);
159+
}
160+
172161
public function key()
173162
{
174163
return $this->_position;
175164
}
176165

177-
/**
178-
* Get the next element on the Collection
179-
*
180-
* <code>
181-
* <?php
182-
* ...
183-
* next($folders);
184-
* ...
185-
* ?>
186-
* </code>
187-
*
188-
* @return Mad_Model_Base
189-
*/
190-
#[\ReturnTypeWillChange]
191166
public function next()
192167
{
193168
$this->_position++;
194169
return $this->current();
195170
}
196171

197-
/**
198-
* Rewind collection to first element
199-
*
200-
* <code>
201-
* <?php
202-
* ...
203-
* rewind($folders);
204-
* ...
205-
* ?>
206-
* </code>
207-
*
208-
*/
209-
#[\ReturnTypeWillChange]
210172
public function rewind()
211173
{
212174
$this->_position = 0;
213175
return $this->current();
214176
}
215177

216-
/**
217-
* Check if the current element exists
218-
* @return boolean
219-
*/
220-
#[\ReturnTypeWillChange]
221178
public function valid()
222179
{
223180
return $this->offsetExists($this->_position);
@@ -230,7 +187,7 @@ public function valid()
230187

231188
/**
232189
* Check if the given offset exists
233-
*
190+
*
234191
* @param int $offset
235192
* @return boolean
236193
*/
@@ -264,22 +221,22 @@ public function offsetGet($offset)
264221

265222
/**
266223
* Collection is readonly, so this is not allowed (method required by interface)
267-
*
224+
*
268225
* @param int $offset
269226
* @param mixed $value
270227
*/
271228
#[\ReturnTypeWillChange]
272-
public function offsetSet($offset, $value)
229+
public function offsetSet($offset, $value)
273230
{
274231
// Can only add Models to the collection
275232
if ($value instanceof Mad_Model_Base) {
276233
$this->_collection[] = $value;
277-
}
234+
}
278235
}
279236

280237
/**
281238
* Collection is readonly, so this is not allowed (method required by interface)
282-
*
239+
*
283240
* @param int $offset
284241
*/
285242
#[\ReturnTypeWillChange]
@@ -290,7 +247,7 @@ public function offsetUnset($offset) {}
290247
* a property or calling a method, and return all of the
291248
* results in an array.
292249
*
293-
* The first argument ($property) is interpreted as a property name.
250+
* The first argument ($property) is interpreted as a property name.
294251
* However, if the first argument ends with "()" then it will be
295252
* interpreted as a method and varargs be passed to that method.
296253
*
@@ -306,7 +263,7 @@ public function collect($property) {
306263
$args = func_get_args();
307264
array_shift($args);
308265

309-
foreach ($this as $member) {
266+
foreach ($this as $member) {
310267
$callback = [$member, $method];
311268
$values[] = call_user_func_array($callback, $args);
312269
}
@@ -316,13 +273,13 @@ public function collect($property) {
316273
$values[] = $member->$property;
317274
}
318275
}
319-
276+
320277
return $values;
321278
}
322279

323280
/**
324281
* Initialize result set into object collection
325-
*
282+
*
326283
* @param object $model
327284
* @param array $results
328285
*/

0 commit comments

Comments
 (0)