|
| 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 | +} |
0 commit comments