Skip to content

Commit 6119f04

Browse files
authored
Merge pull request #152 from php-enqueue/delay_strategy
[amqp] Delay Strategy
2 parents df2dd22 + a797648 commit 6119f04

28 files changed

+1048
-46
lines changed

composer.json

+5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"enqueue/amqp-ext": "*@dev",
1010
"enqueue/amqp-lib": "*@dev",
1111
"enqueue/amqp-bunny": "*@dev",
12+
"enqueue/amqp-tools": "*@dev",
1213
"php-amqplib/php-amqplib": "^2.7@dev",
1314
"enqueue/redis": "*@dev",
1415
"enqueue/fs": "*@dev",
@@ -84,6 +85,10 @@
8485
"type": "path",
8586
"url": "pkg/amqp-bunny"
8687
},
88+
{
89+
"type": "path",
90+
"url": "pkg/amqp-tools"
91+
},
8792
{
8893
"type": "path",
8994
"url": "pkg/redis"

pkg/amqp-bunny/AmqpConnectionFactory.php

+13-3
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@
33
namespace Enqueue\AmqpBunny;
44

55
use Bunny\Client;
6+
use Enqueue\AmqpTools\DelayStrategyAware;
7+
use Enqueue\AmqpTools\DelayStrategyAwareTrait;
68
use Interop\Amqp\AmqpConnectionFactory as InteropAmqpConnectionFactory;
79

8-
class AmqpConnectionFactory implements InteropAmqpConnectionFactory
10+
class AmqpConnectionFactory implements InteropAmqpConnectionFactory, DelayStrategyAware
911
{
12+
use DelayStrategyAwareTrait;
13+
1014
/**
1115
* @var array
1216
*/
@@ -72,12 +76,18 @@ public function __construct($config = 'amqp://')
7276
public function createContext()
7377
{
7478
if ($this->config['lazy']) {
75-
return new AmqpContext(function () {
79+
$context = new AmqpContext(function () {
7680
return $this->establishConnection()->channel();
7781
}, $this->config);
82+
$context->setDelayStrategy($this->delayStrategy);
83+
84+
return $context;
7885
}
7986

80-
return new AmqpContext($this->establishConnection()->channel(), $this->config);
87+
$context = new AmqpContext($this->establishConnection()->channel(), $this->config);
88+
$context->setDelayStrategy($this->delayStrategy);
89+
90+
return $context;
8191
}
8292

8393
/**

pkg/amqp-bunny/AmqpContext.php

+9-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
namespace Enqueue\AmqpBunny;
44

55
use Bunny\Channel;
6+
use Enqueue\AmqpTools\DelayStrategyAware;
7+
use Enqueue\AmqpTools\DelayStrategyAwareTrait;
68
use Interop\Amqp\AmqpBind as InteropAmqpBind;
79
use Interop\Amqp\AmqpContext as InteropAmqpContext;
810
use Interop\Amqp\AmqpMessage as InteropAmqpMessage;
@@ -17,8 +19,10 @@
1719
use Interop\Queue\PsrDestination;
1820
use Interop\Queue\PsrTopic;
1921

20-
class AmqpContext implements InteropAmqpContext
22+
class AmqpContext implements InteropAmqpContext, DelayStrategyAware
2123
{
24+
use DelayStrategyAwareTrait;
25+
2226
/**
2327
* @var Channel
2428
*/
@@ -124,7 +128,10 @@ public function createConsumer(PsrDestination $destination)
124128
*/
125129
public function createProducer()
126130
{
127-
return new AmqpProducer($this->getBunnyChannel());
131+
$producer = new AmqpProducer($this->getBunnyChannel(), $this);
132+
$producer->setDelayStrategy($this->delayStrategy);
133+
134+
return $producer;
128135
}
129136

130137
/**

pkg/amqp-bunny/AmqpProducer.php

+27-10
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
namespace Enqueue\AmqpBunny;
44

55
use Bunny\Channel;
6+
use Enqueue\AmqpTools\DelayStrategyAware;
7+
use Enqueue\AmqpTools\DelayStrategyAwareTrait;
68
use Interop\Amqp\AmqpMessage as InteropAmqpMessage;
79
use Interop\Amqp\AmqpProducer as InteropAmqpProducer;
810
use Interop\Amqp\AmqpQueue as InteropAmqpQueue;
@@ -14,8 +16,10 @@
1416
use Interop\Queue\PsrMessage;
1517
use Interop\Queue\PsrTopic;
1618

17-
class AmqpProducer implements InteropAmqpProducer
19+
class AmqpProducer implements InteropAmqpProducer, DelayStrategyAware
1820
{
21+
use DelayStrategyAwareTrait;
22+
1923
/**
2024
* @var int|null
2125
*/
@@ -32,11 +36,23 @@ class AmqpProducer implements InteropAmqpProducer
3236
private $channel;
3337

3438
/**
35-
* @param Channel $channel
39+
* @var int
40+
*/
41+
private $deliveryDelay;
42+
43+
/**
44+
* @var AmqpContext
45+
*/
46+
private $context;
47+
48+
/**
49+
* @param Channel $channel
50+
* @param AmqpContext $context
3651
*/
37-
public function __construct(Channel $channel)
52+
public function __construct(Channel $channel, AmqpContext $context)
3853
{
3954
$this->channel = $channel;
55+
$this->context = $context;
4056
}
4157

4258
/**
@@ -47,8 +63,7 @@ public function send(PsrDestination $destination, PsrMessage $message)
4763
{
4864
$destination instanceof PsrTopic
4965
? InvalidDestinationException::assertDestinationInstanceOf($destination, InteropAmqpTopic::class)
50-
: InvalidDestinationException::assertDestinationInstanceOf($destination, InteropAmqpQueue::class)
51-
;
66+
: InvalidDestinationException::assertDestinationInstanceOf($destination, InteropAmqpQueue::class);
5267

5368
InvalidMessageException::assertMessageInstanceOf($message, InteropAmqpMessage::class);
5469

@@ -66,7 +81,9 @@ public function send(PsrDestination $destination, PsrMessage $message)
6681
$amqpProperties['application_headers'] = $appProperties;
6782
}
6883

69-
if ($destination instanceof InteropAmqpTopic) {
84+
if ($this->deliveryDelay) {
85+
$this->delayStrategy->delayMessage($this->context, $destination, $message, $this->deliveryDelay);
86+
} elseif ($destination instanceof InteropAmqpTopic) {
7087
$this->channel->publish(
7188
$message->getBody(),
7289
$amqpProperties,
@@ -92,19 +109,19 @@ public function send(PsrDestination $destination, PsrMessage $message)
92109
*/
93110
public function setDeliveryDelay($deliveryDelay)
94111
{
95-
if (null === $deliveryDelay) {
96-
return;
112+
if (null === $this->delayStrategy) {
113+
throw DeliveryDelayNotSupportedException::providerDoestNotSupportIt();
97114
}
98115

99-
throw DeliveryDelayNotSupportedException::providerDoestNotSupportIt();
116+
$this->deliveryDelay = $deliveryDelay;
100117
}
101118

102119
/**
103120
* {@inheritdoc}
104121
*/
105122
public function getDeliveryDelay()
106123
{
107-
return null;
124+
return $this->deliveryDelay;
108125
}
109126

110127
/**

pkg/amqp-bunny/Tests/AmqpProducerTest.php

+69-8
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44

55
use Bunny\Channel;
66
use Bunny\Message;
7+
use Enqueue\AmqpBunny\AmqpContext;
78
use Enqueue\AmqpBunny\AmqpProducer;
9+
use Enqueue\AmqpTools\DelayStrategy;
810
use Enqueue\Test\ClassExtensionTrait;
911
use Interop\Amqp\AmqpMessage as InteropAmqpMessage;
1012
use Interop\Amqp\Impl\AmqpMessage;
1113
use Interop\Amqp\Impl\AmqpQueue;
1214
use Interop\Amqp\Impl\AmqpTopic;
15+
use Interop\Queue\DeliveryDelayNotSupportedException;
1316
use Interop\Queue\InvalidDestinationException;
1417
use Interop\Queue\InvalidMessageException;
1518
use Interop\Queue\PsrDestination;
@@ -23,7 +26,7 @@ class AmqpProducerTest extends TestCase
2326

2427
public function testCouldBeConstructedWithRequiredArguments()
2528
{
26-
new AmqpProducer($this->createBunnyChannelMock());
29+
new AmqpProducer($this->createBunnyChannelMock(), $this->createContextMock());
2730
}
2831

2932
public function testShouldImplementPsrProducerInterface()
@@ -33,7 +36,7 @@ public function testShouldImplementPsrProducerInterface()
3336

3437
public function testShouldThrowExceptionWhenDestinationTypeIsInvalid()
3538
{
36-
$producer = new AmqpProducer($this->createBunnyChannelMock());
39+
$producer = new AmqpProducer($this->createBunnyChannelMock(), $this->createContextMock());
3740

3841
$this->expectException(InvalidDestinationException::class);
3942
$this->expectExceptionMessage('The destination must be an instance of Interop\Amqp\AmqpQueue but got');
@@ -43,7 +46,7 @@ public function testShouldThrowExceptionWhenDestinationTypeIsInvalid()
4346

4447
public function testShouldThrowExceptionWhenMessageTypeIsInvalid()
4548
{
46-
$producer = new AmqpProducer($this->createBunnyChannelMock());
49+
$producer = new AmqpProducer($this->createBunnyChannelMock(), $this->createContextMock());
4750

4851
$this->expectException(InvalidMessageException::class);
4952
$this->expectExceptionMessage('The message must be an instance of Interop\Amqp\AmqpMessage but it is');
@@ -65,7 +68,7 @@ public function testShouldPublishMessageToTopic()
6568
$message = new AmqpMessage('body');
6669
$message->setRoutingKey('routing-key');
6770

68-
$producer = new AmqpProducer($channel);
71+
$producer = new AmqpProducer($channel, $this->createContextMock());
6972
$producer->send($topic, $message);
7073
}
7174

@@ -80,10 +83,52 @@ public function testShouldPublishMessageToQueue()
8083

8184
$queue = new AmqpQueue('queue');
8285

83-
$producer = new AmqpProducer($channel);
86+
$producer = new AmqpProducer($channel, $this->createContextMock());
8487
$producer->send($queue, new AmqpMessage('body'));
8588
}
8689

90+
public function testShouldDelayMessage()
91+
{
92+
$channel = $this->createBunnyChannelMock();
93+
$channel
94+
->expects($this->never())
95+
->method('publish')
96+
;
97+
98+
$message = new AmqpMessage('body');
99+
$context = $this->createContextMock();
100+
$queue = new AmqpQueue('queue');
101+
102+
$delayStrategy = $this->createDelayStrategyMock();
103+
$delayStrategy
104+
->expects($this->once())
105+
->method('delayMessage')
106+
->with($this->identicalTo($context), $this->identicalTo($queue), $this->identicalTo($message), 10000)
107+
;
108+
109+
$producer = new AmqpProducer($channel, $context);
110+
$producer->setDelayStrategy($delayStrategy);
111+
$producer->setDeliveryDelay(10000);
112+
113+
$producer->send($queue, $message);
114+
}
115+
116+
public function testShouldThrowExceptionOnSetDeliveryDelayWhenDeliveryStrategyIsNotSet()
117+
{
118+
$channel = $this->createBunnyChannelMock();
119+
$channel
120+
->expects($this->never())
121+
->method('publish')
122+
;
123+
124+
$producer = new AmqpProducer($channel, $this->createContextMock());
125+
126+
$this->expectException(DeliveryDelayNotSupportedException::class);
127+
$this->expectExceptionMessage('The provider does not support delivery delay feature');
128+
129+
$producer->setDeliveryDelay(10000);
130+
}
131+
87132
public function testShouldSetMessageHeaders()
88133
{
89134
$channel = $this->createBunnyChannelMock();
@@ -93,7 +138,7 @@ public function testShouldSetMessageHeaders()
93138
->with($this->anything(), ['content_type' => 'text/plain'])
94139
;
95140

96-
$producer = new AmqpProducer($channel);
141+
$producer = new AmqpProducer($channel, $this->createContextMock());
97142
$producer->send(new AmqpTopic('name'), new AmqpMessage('body', [], ['content_type' => 'text/plain']));
98143
}
99144

@@ -106,7 +151,7 @@ public function testShouldSetMessageProperties()
106151
->with($this->anything(), ['application_headers' => ['key' => 'value']])
107152
;
108153

109-
$producer = new AmqpProducer($channel);
154+
$producer = new AmqpProducer($channel, $this->createContextMock());
110155
$producer->send(new AmqpTopic('name'), new AmqpMessage('body', ['key' => 'value']));
111156
}
112157

@@ -123,7 +168,7 @@ public function testShouldPropagateFlags()
123168
$message->addFlag(InteropAmqpMessage::FLAG_IMMEDIATE);
124169
$message->addFlag(InteropAmqpMessage::FLAG_MANDATORY);
125170

126-
$producer = new AmqpProducer($channel);
171+
$producer = new AmqpProducer($channel, $this->createContextMock());
127172
$producer->send(new AmqpTopic('name'), $message);
128173
}
129174

@@ -150,4 +195,20 @@ private function createBunnyChannelMock()
150195
{
151196
return $this->createMock(Channel::class);
152197
}
198+
199+
/**
200+
* @return \PHPUnit_Framework_MockObject_MockObject|AmqpContext
201+
*/
202+
private function createContextMock()
203+
{
204+
return $this->createMock(AmqpContext::class);
205+
}
206+
207+
/**
208+
* @return \PHPUnit_Framework_MockObject_MockObject|DelayStrategy
209+
*/
210+
private function createDelayStrategyMock()
211+
{
212+
return $this->createMock(DelayStrategy::class);
213+
}
153214
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace Enqueue\AmqpBunny\Tests\Spec;
4+
5+
use Enqueue\AmqpLib\AmqpConnectionFactory;
6+
use Enqueue\AmqpTools\RabbitMqDelayPluginDelayStrategy;
7+
use Interop\Queue\PsrContext;
8+
use Interop\Queue\Spec\SendAndReceiveDelayedMessageFromQueueSpec;
9+
10+
/**
11+
* @group functional
12+
*/
13+
class AmqpSendAndReceiveDelayedMessageWithDelayPluginStrategyTest extends SendAndReceiveDelayedMessageFromQueueSpec
14+
{
15+
/**
16+
* {@inheritdoc}
17+
*/
18+
protected function createContext()
19+
{
20+
$factory = new AmqpConnectionFactory(getenv('AMQP_DSN'));
21+
$factory->setDelayStrategy(new RabbitMqDelayPluginDelayStrategy());
22+
23+
return $factory->createContext();
24+
}
25+
26+
/**
27+
* {@inheritdoc}
28+
*/
29+
protected function createQueue(PsrContext $context, $queueName)
30+
{
31+
$queue = parent::createQueue($context, $queueName);
32+
33+
$context->declareQueue($queue);
34+
35+
return $queue;
36+
}
37+
}

0 commit comments

Comments
 (0)