Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit dfd8442

Browse files
committed
Add AMQP adapter for Message component within Symfony's Core
1 parent bbeca51 commit dfd8442

File tree

12 files changed

+548
-0
lines changed

12 files changed

+548
-0
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@
115115
"Symfony\\Bridge\\Monolog\\": "src/Symfony/Bridge/Monolog/",
116116
"Symfony\\Bridge\\ProxyManager\\": "src/Symfony/Bridge/ProxyManager/",
117117
"Symfony\\Bridge\\Twig\\": "src/Symfony/Bridge/Twig/",
118+
"Symfony\\Bridge\\PhpAmqp\\": "src/Symfony/Bridge/PhpAmqp/",
118119
"Symfony\\Bundle\\": "src/Symfony/Bundle/",
119120
"Symfony\\Component\\": "src/Symfony/Component/"
120121
},
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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\Bridge\PhpAmqp;
13+
14+
use Symfony\Bridge\PhpAmqp\Exception\RejectMessageException;
15+
use Symfony\Component\Messenger\Transport\ReceiverInterface;
16+
use Symfony\Component\Messenger\Transport\Serialization\DecoderInterface;
17+
18+
/**
19+
* Symfony Message receiver to get messages from AMQP brokers using PHP's AMQP extension.
20+
*
21+
* @author Samuel Roze <[email protected]>
22+
*/
23+
class AmqpReceiver implements ReceiverInterface
24+
{
25+
private $messageDecoder;
26+
private $connection;
27+
28+
public function __construct(DecoderInterface $messageDecoder, Connection $connection)
29+
{
30+
$this->messageDecoder = $messageDecoder;
31+
$this->connection = $connection;
32+
}
33+
34+
/**
35+
* {@inheritdoc}
36+
*/
37+
public function receive(): iterable
38+
{
39+
while (true) {
40+
if (null === ($message = $this->connection->waitAndGet())) {
41+
continue;
42+
}
43+
44+
try {
45+
yield $this->messageDecoder->decode([
46+
'body' => $message->getBody(),
47+
'headers' => $message->getHeaders(),
48+
]);
49+
50+
$this->connection->ack($message);
51+
} catch (RejectMessageException $e) {
52+
$this->connection->reject($message);
53+
} catch (\Throwable $e) {
54+
$this->connection->nack($message);
55+
}
56+
}
57+
}
58+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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\Bridge\PhpAmqp;
13+
14+
use Symfony\Component\Messenger\Transport\SenderInterface;
15+
use Symfony\Component\Messenger\Transport\Serialization\EncoderInterface;
16+
17+
/**
18+
* Symfony Message sender to send messages to AMQP brokers using PHP's AMQP extension.
19+
*
20+
* @author Samuel Roze <[email protected]>
21+
*/
22+
class AmqpSender implements SenderInterface
23+
{
24+
private $messageEncoder;
25+
private $connection;
26+
27+
public function __construct(EncoderInterface $messageEncoder, Connection $connection)
28+
{
29+
$this->messageEncoder = $messageEncoder;
30+
$this->connection = $connection;
31+
}
32+
33+
/**
34+
* {@inheritdoc}
35+
*/
36+
public function send($message)
37+
{
38+
$encodedMessage = $this->messageEncoder->encode($message);
39+
40+
$this->connection->publish(
41+
$encodedMessage['body'],
42+
$encodedMessage['headers']
43+
);
44+
}
45+
}
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
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\Bridge\PhpAmqp;
13+
14+
/**
15+
* An AMQP connection.
16+
*
17+
* @author Samuel Roze <[email protected]>
18+
*/
19+
class Connection
20+
{
21+
private $amqpConnectionCredentials;
22+
private $exchangeName;
23+
private $queueName;
24+
private $debug;
25+
26+
/**
27+
* @var \AMQPChannel|null
28+
*/
29+
private $amqpChannel;
30+
31+
/**
32+
* @var \AMQPExchange|null
33+
*/
34+
private $amqpExchange;
35+
36+
/**
37+
* @var \AMQPQueue|null
38+
*/
39+
private $amqpQueue;
40+
41+
public function __construct(array $amqpConnectionCredentials, string $exchangeName, string $queueName, bool $debug = false)
42+
{
43+
$this->amqpConnectionCredentials = $amqpConnectionCredentials;
44+
$this->exchangeName = $exchangeName;
45+
$this->queueName = $queueName;
46+
$this->debug = $debug;
47+
}
48+
49+
public static function fromDsn(string $dsn, bool $debug = false)
50+
{
51+
if (false === ($parsedUrl = parse_url($dsn))) {
52+
throw new \InvalidArgumentException(sprintf('The given AMQP DSN "%s" is invalid.', $dsn));
53+
}
54+
55+
$pathParts = explode(trim($parsedUrl['path'] ?? '', '/'), '/');
56+
57+
$amqpOptions = [
58+
'host' => $parsedUrl['host'] ?? 'localhost',
59+
'port' => $parsedUrl['port'] ?? 5672,
60+
'vhost' => $pathParts[0] ?? '/',
61+
'queue_name' => $queueName = $pathParts[1] ?? 'messages',
62+
'exchange_name' => $queueName
63+
];
64+
65+
if (isset($parsedUrl['query'])) {
66+
parse_str($parsedUrl['query'], $parsedQuery);
67+
68+
$amqpOptions = array_merge($amqpOptions, $parsedQuery);
69+
}
70+
71+
return new self($amqpOptions, $amqpOptions['exchange_name'], $amqpOptions['queue_name'], $debug);
72+
}
73+
74+
/**
75+
* @throws \AMQPException
76+
*/
77+
public function publish(string $body, array $headers = [])
78+
{
79+
if ($this->debug) {
80+
$this->setup();
81+
}
82+
83+
$this->exchange()->publish($body, null, AMQP_NOPARAM, [
84+
'headers' => $headers
85+
]);
86+
}
87+
88+
/**
89+
* Waits and gets a message from the configured queue.
90+
*
91+
* @throws \AMQPException
92+
*/
93+
public function waitAndGet() : ?\AMQPEnvelope
94+
{
95+
if ($this->debug) {
96+
$this->setup();
97+
}
98+
99+
$message = null;
100+
101+
try {
102+
$this->queue()->consume(function (\AMQPEnvelope $envelope) use (&$message) {
103+
$message = $envelope;
104+
105+
return false;
106+
});
107+
} catch (\AMQPQueueException $e) {
108+
if ($e->getCode() == 404) {
109+
// If we get a 404 for the queue, it means we need to setup the exchange & queue.
110+
$this->setup();
111+
112+
return $this->waitAndGet();
113+
}
114+
115+
throw $e;
116+
}
117+
118+
return $message;
119+
}
120+
121+
private function channel(): \AMQPChannel
122+
{
123+
if (null === $this->amqpChannel) {
124+
$connection = new \AMQPConnection($this->amqpConnectionCredentials);
125+
126+
if (false === $connection->connect()) {
127+
throw new \AMQPException('Could not connect to the AMQP server. Please verify the provided DSN.');
128+
}
129+
130+
$this->amqpChannel = new \AMQPChannel($connection);
131+
}
132+
133+
return $this->amqpChannel;
134+
}
135+
136+
private function queue() : \AMQPQueue
137+
{
138+
if (null === $this->amqpQueue) {
139+
$this->amqpQueue = new \AMQPQueue($this->channel());
140+
$this->amqpQueue->setName($this->queueName);
141+
$this->amqpQueue->setFlags(AMQP_DURABLE);
142+
}
143+
144+
return $this->amqpQueue;
145+
}
146+
147+
private function exchange() : \AMQPExchange
148+
{
149+
if (null === $this->amqpExchange) {
150+
$this->amqpExchange = new \AMQPExchange($this->channel());
151+
$this->amqpExchange->setName($this->exchangeName);
152+
$this->amqpExchange->setType(AMQP_EX_TYPE_FANOUT);
153+
$this->amqpExchange->setFlags(AMQP_DURABLE);
154+
}
155+
156+
return $this->amqpExchange;
157+
}
158+
159+
public function ack(\AMQPEnvelope $message)
160+
{
161+
return $this->queue()->ack($message->getDeliveryTag());
162+
}
163+
164+
public function reject(\AMQPEnvelope $message)
165+
{
166+
return $this->queue()->reject($message->getDeliveryTag());
167+
}
168+
169+
public function nack(\AMQPEnvelope $message)
170+
{
171+
return $this->queue()->nack($message->getDeliveryTag());
172+
}
173+
174+
public function setup()
175+
{
176+
if (!$this->channel()->isConnected()) {
177+
$this->clear();
178+
}
179+
180+
$this->exchange()->declareExchange();
181+
182+
$this->queue()->declareQueue();
183+
$this->queue()->bind($this->exchange()->getName());
184+
}
185+
186+
private function clear()
187+
{
188+
$this->amqpChannel = null;
189+
$this->amqpQueue = null;
190+
$this->amqpExchange = null;
191+
}
192+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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\Bridge\PhpAmqp\Exception;
13+
14+
/**
15+
* If something goes wrong while consuming and handling a message from the AMQP broker, there are two choices: rejecting
16+
* or re-queuing the message.
17+
*
18+
* If the exception that is thrown by the bus while dispatching the message implements this interface, the message will
19+
* be rejected. Otherwise, it will be re-queued.
20+
*
21+
* @author Samuel Roze <[email protected]>
22+
*/
23+
interface RejectMessageException
24+
{
25+
}

src/Symfony/Bridge/PhpAmqp/LICENSE

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2004-2018 Fabien Potencier
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is furnished
8+
to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.

src/Symfony/Bridge/PhpAmqp/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
AMQP Bridge
2+
===========
3+
4+
Provides integration for [PHP's AMQP library](http://php.net/manual/fa/book.amqp.php) with various
5+
Symfony components.
6+
7+
Resources
8+
---------
9+
10+
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
11+
* [Report issues](https://github.com/symfony/symfony/issues) and
12+
[send Pull Requests](https://github.com/symfony/symfony/pulls)
13+
in the [main Symfony repository](https://github.com/symfony/symfony)

0 commit comments

Comments
 (0)