Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
IteratorProxy
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
6 / 6
11
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getIterator
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 unwrap
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 is
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 in
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 toArray
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Celema\Boiler\Proxy;
6
7use Celema\Boiler\Contract\Wrapper;
8use Generator;
9use IteratorAggregate;
10use JsonSerializable;
11use Override;
12use Stringable;
13use Traversable;
14
15/**
16 * Wraps a traversable value. A loop gets its wrapped items, and an object such
17 * as a collection or a menu item with children keeps its properties and
18 * methods, as with ObjectProxy. The proxy's own methods take precedence over
19 * same-named methods of the object.
20 *
21 * @api
22 *
23 * @template-implements IteratorAggregate<mixed, mixed>
24 * @implements Proxy<Traversable<mixed, mixed>>
25 */
26final class IteratorProxy implements IteratorAggregate, JsonSerializable, Proxy, Stringable
27{
28    use ObjectAccess;
29    use RefusesJson;
30
31    /**
32     * @param Traversable<mixed, mixed> $value
33     * @param bool $safe whether the object's string conversion is safe HTML and skips escaping
34     */
35    public function __construct(
36        private readonly Traversable $value,
37        private readonly Wrapper $wrapper,
38        private readonly bool $safe = false,
39    ) {}
40
41    /**
42     * Traverses the wrapped value anew on every loop, like a native foreach:
43     * an IteratorAggregate hands out a fresh iterator each time, while an
44     * Iterator or a generator keeps its single cursor. Keys are wrapped like
45     * values, since a template can output either.
46     *
47     * @return Generator<mixed, mixed>
48     */
49    #[Override]
50    public function getIterator(): Generator
51    {
52        /**
53         * @var mixed $key
54         * @var mixed $item
55         */
56        foreach ($this->value as $key => $item) {
57            yield $this->wrapper->wrap($key) => $this->wrapper->wrap($item);
58        }
59    }
60
61    /** @return Traversable<mixed, mixed> */
62    #[Override]
63    public function unwrap(): Traversable
64    {
65        return $this->value;
66    }
67
68    #[Override]
69    public function is(mixed $other): bool
70    {
71        return $this->value === ($other instanceof Proxy ? $other->unwrap() : $other);
72    }
73
74    /** @param ArrayProxy|array<array-key, mixed> $haystack */
75    #[Override]
76    public function in(ArrayProxy|array $haystack): bool
77    {
78        if ($haystack instanceof ArrayProxy) {
79            $haystack = $haystack->unwrap();
80        }
81
82        /** @var mixed $item */
83        foreach ($haystack as $item) {
84            if ($this->is($item)) {
85                return true;
86            }
87        }
88
89        return false;
90    }
91
92    public function toArray(): ArrayProxy
93    {
94        return new ArrayProxy(iterator_to_array($this->value), $this->wrapper);
95    }
96}