EnvelopeCollection::back()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace Zenstruck\Messenger\Test;
4
5
use Symfony\Component\Messenger\Envelope;
6
use Zenstruck\Assert;
7
use Zenstruck\Messenger\Test\Transport\TestTransport;
8
9
/**
10
 * @author Kevin Bond <[email protected]>
11
 *
12
 * @implements \IteratorAggregate<TestEnvelope>
13
 */
14
final class EnvelopeCollection implements \IteratorAggregate, \Countable
15
{
16
    private TestTransport $transport;
17
18
    /** @var Envelope[] */
19
    private array $envelopes;
20
21
    /**
22
     * @internal
23
     */
24
    public function __construct(TestTransport $transport, Envelope ...$envelopes)
25
    {
26
        $this->transport = $transport;
27
        $this->envelopes = $envelopes;
28
    }
29
30
    public function back(): TestTransport
31
    {
32
        return $this->transport;
33
    }
34
35
    public function assertEmpty(): self
36
    {
37
        return $this->assertCount(0);
38
    }
39
40
    public function assertNotEmpty(): self
41
    {
42
        Assert::that($this)->isNotEmpty('Expected some messages but found none.');
43
44
        return $this;
45
    }
46
47
    public function assertCount(int $count): self
48
    {
49
        Assert::that($this->envelopes)->hasCount($count, 'Expected {expected} messages but {actual} messages found.');
50
51
        return $this;
52
    }
53
54
    public function assertContains(string $messageClass, ?int $times = null): self
55
    {
56
        $messages = $this->messages($messageClass);
57
58
        if (null !== $times) {
59
            Assert::that($messages)->hasCount(
60
                $times,
61
                'Expected to find "{message}" {expected} times but found {actual} times.',
62
                ['message' => $messageClass]
63
            );
64
65
            return $this;
66
        }
67
68
        Assert::that($messages)->isNotEmpty('Message "{message}" not found.', ['message' => $messageClass]);
69
70
        return $this;
71
    }
72
73
    public function assertNotContains(string $messageClass): self
74
    {
75
        Assert::that($this->messages($messageClass))->isEmpty(
76
            'Found message "{message}" but should not.',
77
            ['message' => $messageClass]
78
        );
79
80
        return $this;
81
    }
82
83
    /**
84
     * @param string|callable|null $filter
85
     */
86
    public function first($filter = null): TestEnvelope
87
    {
88
        if (null === $filter) {
89
            // just the first envelope
90
            return $this->first(fn() => true);
91
        }
92
93
        if (!\is_callable($filter)) {
94
            // first envelope for message class
95
            return $this->first(fn(Envelope $e) => $filter === \get_class($e->getMessage()));
96
        }
97
98
        $filter = self::normalizeFilter($filter);
99
100
        foreach ($this->envelopes as $envelope) {
101
            if ($filter($envelope)) {
102
                return new TestEnvelope($envelope);
103
            }
104
        }
105
106
        throw new \RuntimeException('No envelopes found.');
107
    }
108
109
    /**
110
     * The messages extracted from envelopes.
111
     *
112
     * @param string|null $class Only messages of this class
113
     *
114
     * @return object[]
115
     */
116
    public function messages(?string $class = null): array
117
    {
118
        $messages = \array_map(static fn(Envelope $envelope) => $envelope->getMessage(), $this->envelopes);
119
120
        if (!$class) {
121
            return $messages;
122
        }
123
124
        return \array_values(\array_filter($messages, static fn(object $message) => $class === \get_class($message)));
125
    }
126
127
    /**
128
     * @return TestEnvelope[]
129
     */
130
    public function all(): array
131
    {
132
        return \iterator_to_array($this);
133
    }
134
135
    /**
136
     * @return \Traversable|TestEnvelope[]
137
     */
138
    public function getIterator(): \Traversable
139
    {
140
        foreach ($this->envelopes as $envelope) {
141
            yield new TestEnvelope($envelope);
142
        }
143
    }
144
145
    public function count(): int
146
    {
147
        return \count($this->envelopes);
148
    }
149
150
    private static function normalizeFilter(callable $filter): callable
151
    {
152
        $function = new \ReflectionFunction(\Closure::fromCallable($filter));
153
154
        if (!$parameter = $function->getParameters()[0] ?? null) {
155
            return $filter;
156
        }
157
158
        if (!$type = $parameter->getType()) {
159
            return $filter;
160
        }
161
162
        if (!$type instanceof \ReflectionNamedType || $type->isBuiltin() || Envelope::class === $type->getName()) {
163
            return $filter;
164
        }
165
166
        // user used message class name as type-hint
167
        return function(Envelope $envelope) use ($filter, $type) {
168
            if ($type->getName() !== \get_class($envelope->getMessage())) {
169
                return false;
170
            }
171
172
            return $filter($envelope->getMessage());
173
        };
174
    }
175
}
176