Skip to content

Commit f6bb4ad

Browse files
Merge branch '5.2' into 5.x
* 5.2: (23 commits) [Console] Fix Windows code page support [SecurityBundle] Allow ips parameter in access_control accept comma-separated string [Form] Add TranslatableMessage support to choice_label option of ChoiceType Remove code that deals with legacy behavior of PHP_Incomplete_Class [Config][DependencyInjection] Uniformize trailing slash handling [PropertyInfo] Make ReflectionExtractor correctly extract nullability [PropertyInfo] fix attribute namespace with recursive traits [PhpUnitBridge] Fix tests with `@doesNotPerformAssertions` annotations Check redis extension version [Security] Update Russian translations [Notifier] Fix return SentMessage then Messenger not used [VarExporter] Add support of PHP enumerations [Security] Added missing Japanese translations [Security] Added missing Polish translations [Security] Add missing Italian translations #41051 [Security] Missing translations pt_BR getProtocolVersion may return null Fix return type on isAllowedProperty method Make FailoverTransport always pick the first transport [TwigBridge] Fix HTML for translatable custom-file label in Bootstrap 4 theme ...
2 parents 16ee13d + bafefff commit f6bb4ad

File tree

8 files changed

+317
-11
lines changed

8 files changed

+317
-11
lines changed

Chatter.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,7 @@ public function supports(MessageInterface $message): bool
4949
public function send(MessageInterface $message): ?SentMessage
5050
{
5151
if (null === $this->bus) {
52-
$this->transport->send($message);
53-
54-
return null;
52+
return $this->transport->send($message);
5553
}
5654

5755
if (null !== $this->dispatcher) {

Tests/ChatterTest.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
namespace Symfony\Component\Notifier\Tests;
4+
5+
use PHPUnit\Framework\MockObject\MockObject;
6+
use PHPUnit\Framework\TestCase;
7+
use Symfony\Component\Messenger\Envelope;
8+
use Symfony\Component\Messenger\MessageBusInterface;
9+
use Symfony\Component\Notifier\Chatter;
10+
use Symfony\Component\Notifier\Message\SentMessage;
11+
use Symfony\Component\Notifier\Tests\Transport\DummyMessage;
12+
use Symfony\Component\Notifier\Transport\TransportInterface;
13+
14+
class ChatterTest extends TestCase
15+
{
16+
/** @var MockObject&TransportInterface */
17+
private $transport;
18+
19+
/** @var MockObject&MessageBusInterface */
20+
private $bus;
21+
22+
protected function setUp(): void
23+
{
24+
$this->transport = $this->createMock(TransportInterface::class);
25+
$this->bus = $this->createMock(MessageBusInterface::class);
26+
}
27+
28+
public function testSendWithoutBus()
29+
{
30+
$message = new DummyMessage();
31+
32+
$sentMessage = new SentMessage($message, 'any');
33+
34+
$this->transport
35+
->expects($this->once())
36+
->method('send')
37+
->with($message)
38+
->willReturn($sentMessage);
39+
40+
$chatter = new Chatter($this->transport);
41+
$this->assertSame($sentMessage, $chatter->send($message));
42+
$this->assertSame($message, $sentMessage->getOriginalMessage());
43+
}
44+
45+
public function testSendWithBus()
46+
{
47+
$message = new DummyMessage();
48+
49+
$this->transport
50+
->expects($this->never())
51+
->method('send')
52+
->with($message);
53+
54+
$this->bus
55+
->expects($this->once())
56+
->method('dispatch')
57+
->with($message)
58+
->willReturn(new Envelope(new \stdClass()));
59+
60+
$chatter = new Chatter($this->transport, $this->bus);
61+
$this->assertNull($chatter->send($message));
62+
}
63+
}

Tests/TexterTest.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
namespace Symfony\Component\Notifier\Tests;
4+
5+
use PHPUnit\Framework\MockObject\MockObject;
6+
use PHPUnit\Framework\TestCase;
7+
use Symfony\Component\Messenger\Envelope;
8+
use Symfony\Component\Messenger\MessageBusInterface;
9+
use Symfony\Component\Notifier\Message\SentMessage;
10+
use Symfony\Component\Notifier\Tests\Transport\DummyMessage;
11+
use Symfony\Component\Notifier\Texter;
12+
use Symfony\Component\Notifier\Transport\TransportInterface;
13+
14+
class TexterTest extends TestCase
15+
{
16+
/** @var MockObject&TransportInterface */
17+
private $transport;
18+
19+
/** @var MockObject&MessageBusInterface */
20+
private $bus;
21+
22+
protected function setUp(): void
23+
{
24+
$this->transport = $this->createMock(TransportInterface::class);
25+
$this->bus = $this->createMock(MessageBusInterface::class);
26+
}
27+
28+
public function testSendWithoutBus()
29+
{
30+
$message = new DummyMessage();
31+
$sentMessage = new SentMessage($message, 'any');
32+
33+
$this->transport
34+
->expects($this->once())
35+
->method('send')
36+
->with($message)
37+
->willReturn($sentMessage);
38+
39+
$texter = new Texter($this->transport);
40+
$this->assertSame($sentMessage, $texter->send($message));
41+
$this->assertSame($message, $sentMessage->getOriginalMessage());
42+
}
43+
44+
public function testSendWithBus()
45+
{
46+
$message = new DummyMessage();
47+
48+
$this->transport
49+
->expects($this->never())
50+
->method('send')
51+
->with($message);
52+
53+
$this->bus
54+
->expects($this->once())
55+
->method('dispatch')
56+
->with($message)
57+
->willReturn(new Envelope(new \stdClass()));
58+
59+
$texter = new Texter($this->transport, $this->bus);
60+
$this->assertNull($texter->send($message));
61+
}
62+
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Notifier\Tests\Transport;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Notifier\Exception\LogicException;
16+
use Symfony\Component\Notifier\Exception\RuntimeException;
17+
use Symfony\Component\Notifier\Exception\TransportExceptionInterface;
18+
use Symfony\Component\Notifier\Message\SentMessage;
19+
use Symfony\Component\Notifier\Transport\FailoverTransport;
20+
use Symfony\Component\Notifier\Transport\TransportInterface;
21+
22+
/**
23+
* @group time-sensitive
24+
*/
25+
class FailoverTransportTest extends TestCase
26+
{
27+
public function testSendNoTransports()
28+
{
29+
$this->expectException(LogicException::class);
30+
31+
new FailoverTransport([]);
32+
}
33+
34+
public function testToString()
35+
{
36+
$t1 = $this->createMock(TransportInterface::class);
37+
$t1->expects($this->once())->method('__toString')->willReturn('t1://local');
38+
39+
$t2 = $this->createMock(TransportInterface::class);
40+
$t2->expects($this->once())->method('__toString')->willReturn('t2://local');
41+
42+
$t = new FailoverTransport([$t1, $t2]);
43+
44+
$this->assertEquals('t1://local || t2://local', (string) $t);
45+
}
46+
47+
public function testSendMessageNotSupportedByAnyTransport()
48+
{
49+
$t1 = $this->createMock(TransportInterface::class);
50+
$t2 = $this->createMock(TransportInterface::class);
51+
52+
$t = new FailoverTransport([$t1, $t2]);
53+
54+
$this->expectException(LogicException::class);
55+
56+
$t->send(new DummyMessage());
57+
}
58+
59+
public function testSendFirstWork()
60+
{
61+
$message = new DummyMessage();
62+
63+
$t1 = $this->createMock(TransportInterface::class);
64+
$t1->method('supports')->with($message)->willReturn(true);
65+
$t1->expects($this->exactly(3))->method('send')->with($message)->willReturn(new SentMessage($message, 'test'));
66+
67+
$t2 = $this->createMock(TransportInterface::class);
68+
$t2->expects($this->never())->method('send');
69+
70+
$t = new FailoverTransport([$t1, $t2]);
71+
72+
$t->send($message);
73+
$t->send($message);
74+
$t->send($message);
75+
}
76+
77+
public function testSendAllDead()
78+
{
79+
$message = new DummyMessage();
80+
81+
$t1 = $this->createMock(TransportInterface::class);
82+
$t1->method('supports')->with($message)->willReturn(true);
83+
$t1->expects($this->once())->method('send')->with($message)->will($this->throwException($this->createMock(TransportExceptionInterface::class)));
84+
85+
$t2 = $this->createMock(TransportInterface::class);
86+
$t2->method('supports')->with($message)->willReturn(true);
87+
$t2->expects($this->once())->method('send')->with($message)->will($this->throwException($this->createMock(TransportExceptionInterface::class)));
88+
89+
$t = new FailoverTransport([$t1, $t2]);
90+
91+
$this->expectException(RuntimeException::class);
92+
$this->expectExceptionMessage('All transports failed.');
93+
94+
$t->send($message);
95+
}
96+
97+
public function testSendOneDead()
98+
{
99+
$message = new DummyMessage();
100+
101+
$t1 = $this->createMock(TransportInterface::class);
102+
$t1->method('supports')->with($message)->willReturn(true);
103+
$t1->expects($this->once())->method('send')->will($this->throwException($this->createMock(TransportExceptionInterface::class)));
104+
105+
$t2 = $this->createMock(TransportInterface::class);
106+
$t2->method('supports')->with($message)->willReturn(true);
107+
$t2->expects($this->exactly(1))->method('send')->with($message)->willReturn(new SentMessage($message, 'test'));
108+
109+
$t = new FailoverTransport([$t1, $t2]);
110+
111+
$t->send($message);
112+
}
113+
114+
public function testSendAllDeadWithinRetryPeriod()
115+
{
116+
$message = new DummyMessage();
117+
118+
$t1 = $this->createMock(TransportInterface::class);
119+
$t1->method('supports')->with($message)->willReturn(true);
120+
$t1->method('send')->will($this->throwException($this->createMock(TransportExceptionInterface::class)));
121+
$t1->expects($this->once())->method('send');
122+
$t2 = $this->createMock(TransportInterface::class);
123+
$t2->method('supports')->with($message)->willReturn(true);
124+
$t2->expects($this->exactly(3))
125+
->method('send')
126+
->willReturnOnConsecutiveCalls(
127+
new SentMessage($message, 't2'),
128+
new SentMessage($message, 't2'),
129+
$this->throwException($this->createMock(TransportExceptionInterface::class))
130+
);
131+
$t = new FailoverTransport([$t1, $t2], 40);
132+
$t->send($message);
133+
sleep(4);
134+
$t->send($message);
135+
sleep(4);
136+
137+
$this->expectException(RuntimeException::class);
138+
$this->expectExceptionMessage('All transports failed.');
139+
140+
$t->send($message);
141+
}
142+
143+
public function testSendOneDeadButRecover()
144+
{
145+
$message = new DummyMessage();
146+
147+
$t1 = $this->createMock(TransportInterface::class);
148+
$t1->method('supports')->with($message)->willReturn(true);
149+
$t1->expects($this->exactly(2))->method('send')->willReturnOnConsecutiveCalls(
150+
$this->throwException($this->createMock(TransportExceptionInterface::class)),
151+
new SentMessage($message, 't1')
152+
);
153+
$t2 = $this->createMock(TransportInterface::class);
154+
$t2->method('supports')->with($message)->willReturn(true);
155+
$t2->expects($this->exactly(2))->method('send')->willReturnOnConsecutiveCalls(
156+
new SentMessage($message, 't2'),
157+
$this->throwException($this->createMock(TransportExceptionInterface::class))
158+
);
159+
160+
$t = new FailoverTransport([$t1, $t2], 1);
161+
162+
$t->send($message);
163+
sleep(2);
164+
$t->send($message);
165+
}
166+
}

Texter.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,7 @@ public function supports(MessageInterface $message): bool
4949
public function send(MessageInterface $message): ?SentMessage
5050
{
5151
if (null === $this->bus) {
52-
$this->transport->send($message);
53-
54-
return null;
52+
return $this->transport->send($message);
5553
}
5654

5755
if (null !== $this->dispatcher) {

Transport/FailoverTransport.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ protected function getNextTransport(MessageInterface $message): ?TransportInterf
3131
return $this->currentTransport;
3232
}
3333

34+
protected function getInitialCursor(): int
35+
{
36+
return 0;
37+
}
38+
3439
protected function getNameSymbol(): string
3540
{
3641
return '||';

Transport/RoundRobinTransport.php

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class RoundRobinTransport implements TransportInterface
2727
private $deadTransports;
2828
private $transports = [];
2929
private $retryPeriod;
30-
private $cursor = 0;
30+
private $cursor = -1;
3131

3232
/**
3333
* @param TransportInterface[] $transports
@@ -41,9 +41,6 @@ public function __construct(array $transports, int $retryPeriod = 60)
4141
$this->transports = $transports;
4242
$this->deadTransports = new \SplObjectStorage();
4343
$this->retryPeriod = $retryPeriod;
44-
// the cursor initial value is randomized so that
45-
// when are not in a daemon, we are still rotating the transports
46-
$this->cursor = mt_rand(0, \count($transports) - 1);
4744
}
4845

4946
public function __toString(): string
@@ -64,6 +61,10 @@ public function supports(MessageInterface $message): bool
6461

6562
public function send(MessageInterface $message): SentMessage
6663
{
64+
if (!$this->supports($message)) {
65+
throw new LogicException(sprintf('None of the configured Transports of "%s" supports the given message.', static::class));
66+
}
67+
6768
while ($transport = $this->getNextTransport($message)) {
6869
try {
6970
return $transport->send($message);
@@ -80,12 +81,17 @@ public function send(MessageInterface $message): SentMessage
8081
*/
8182
protected function getNextTransport(MessageInterface $message): ?TransportInterface
8283
{
84+
if (-1 === $this->cursor) {
85+
$this->cursor = $this->getInitialCursor();
86+
}
87+
8388
$cursor = $this->cursor;
8489
while (true) {
8590
$transport = $this->transports[$cursor];
8691

8792
if (!$transport->supports($message)) {
8893
$cursor = $this->moveCursor($cursor);
94+
8995
continue;
9096
}
9197

@@ -114,6 +120,13 @@ protected function isTransportDead(TransportInterface $transport): bool
114120
return $this->deadTransports->contains($transport);
115121
}
116122

123+
protected function getInitialCursor(): int
124+
{
125+
// the cursor initial value is randomized so that
126+
// when are not in a daemon, we are still rotating the transports
127+
return mt_rand(0, \count($this->transports) - 1);
128+
}
129+
117130
protected function getNameSymbol(): string
118131
{
119132
return '&&';

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
},
2323
"require-dev": {
2424
"symfony/event-dispatcher-contracts": "^2",
25-
"symfony/http-client-contracts": "^2"
25+
"symfony/http-client-contracts": "^2",
26+
"symfony/messenger": "^4.4 || ^5.0"
2627
},
2728
"conflict": {
2829
"symfony/http-kernel": "<4.4",

0 commit comments

Comments
 (0)