Passed
Push — 1.x ( d64946...b02e86 )
by Kevin
01:42
created

EnvelopeCollection::back()   A

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