Skip to content

Commit ce6698a

Browse files
committed
bug #4713 Fix cycle() with non-countable ArrayAccess+Traversable objects (yoeunes)
This PR was merged into the 3.x branch. Discussion ---------- Fix cycle() with non-countable ArrayAccess+Traversable objects When using `cycle()` with an object that implements `\ArrayAccess` and `\Traversable` but is not `\Countable`, the function currently returns the object instance immediately after triggering the deprecation notice. This prevents the value from being converted to an array, causing the cycle logic to fail or return the object itself. This PR removes the early return to ensure `self::toArray()` is called, allowing these objects to be cycled correctly as expected. **How to test** ```php $seq = new class implements \ArrayAccess, \IteratorAggregate { public function offsetExists($offset): bool { return true; } public function offsetGet($offset): mixed { return 'val'; } public function offsetSet($offset, $value): void {} public function offsetUnset($offset): void {} public function getIterator(): \Traversable { yield 'odd'; yield 'even'; } }; // Should return 'odd', currently returns the $seq object $result = CoreExtension::cycle($seq, 0); ``` Related to #4241 Commits ------- 473653d [Core] Fix cycle() with non-countable ArrayAccess+Traversable objects
2 parents 2b7cc0b + 473653d commit ce6698a

File tree

2 files changed

+42
-3
lines changed

2 files changed

+42
-3
lines changed

src/Extension/CoreExtension.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -417,10 +417,8 @@ public static function cycle($values, $position): mixed
417417

418418
trigger_deprecation('twig/twig', '3.12', 'Passing a non-countable sequence of values to "%s()" is deprecated.', __METHOD__);
419419

420-
return $values;
420+
$values = self::toArray($values, false);
421421
}
422-
423-
$values = self::toArray($values, false);
424422
}
425423

426424
if (!$count = \count($values)) {

tests/Extension/CoreTest.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
*/
2222

2323
use PHPUnit\Framework\TestCase;
24+
use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
2425
use Twig\Environment;
2526
use Twig\Error\RuntimeError;
2627
use Twig\Extension\CoreExtension;
@@ -31,6 +32,8 @@
3132

3233
class CoreTest extends TestCase
3334
{
35+
use ExpectDeprecationTrait;
36+
3437
/**
3538
* @dataProvider provideCycleCases
3639
*/
@@ -407,6 +410,44 @@ public function testLastModified()
407410
{
408411
$this->assertGreaterThan(1000000000, (new CoreExtension())->getLastModified());
409412
}
413+
414+
/**
415+
* @group legacy
416+
*/
417+
public function testCycleWithArrayAccessAndTraversableButNotCountable()
418+
{
419+
$this->expectDeprecation('Since twig/twig 3.12: Passing a non-countable sequence of values to "Twig\Extension\CoreExtension::cycle()" is deprecated.');
420+
421+
$seq = new class implements \ArrayAccess, \IteratorAggregate {
422+
public function offsetExists($offset): bool
423+
{
424+
return true;
425+
}
426+
427+
public function offsetGet($offset): mixed
428+
{
429+
return 'val';
430+
}
431+
432+
public function offsetSet($offset, $value): void
433+
{
434+
}
435+
436+
public function offsetUnset($offset): void
437+
{
438+
}
439+
440+
public function getIterator(): \Traversable
441+
{
442+
yield 'odd';
443+
yield 'even';
444+
}
445+
};
446+
447+
$result = CoreExtension::cycle($seq, 0);
448+
449+
$this->assertEquals('odd', $result, 'cycle should return the first item from the traversable sequence, not the sequence itself.');
450+
}
410451
}
411452

412453
final class CoreTestIteratorAggregate implements \IteratorAggregate

0 commit comments

Comments
 (0)